WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips,
March 14, 2000.
This issue of the JDC Tech Tips is written by Glen McCluskey.
These tips were developed using JavaTM 2 SDK, Standard Edition,
v 1.2.2, and are not guaranteed to work with other versions.
USING JAVA.LANG.CLASS
In object-oriented programming, a class is a user-defined type,
with a set of methods that operate on instances (objects) of the
class. For example, you might have a class Point
in an application:
public class Point {
int x;
int y;
}
and the application operates on instances of Point
, like (37,47) and (153,89).
java.lang.Class
is one of the standard classes in the JavaTM
programming language, and instances of java.lang.Class
represent
other classes and interfaces. This tip could use Class
to refer
to java.lang.Class
, but that terminology might be confusing.
So you'll see java.lang.Class
throughout the discussion.
java.lang.Class
gives you the ability to find out information
about classes that are currently loaded into the JavaTM Virtual
Machine1 (JVM). It also gives you a way to dynamically load
additional classes into your application. Here's an example,
it's a short application that uses java.lang.Class
features:
public class ClassDemo1 {
public static void main(String args[]) {
Class cls = null;
// load a class by name (like java.util.ArrayList)
try {
cls = Class.forName(args[0]);
}
catch (ClassNotFoundException exc) {
System.err.println(exc);
}
// display the hierarchy back to java.lang.Object
do {
System.out.println(cls);
cls = cls.getSuperclass();
} while (cls != null);
}
}
This program uses Class.forName
to load a class into the JVM; if the
class is already loaded, it finds the java.lang.Class
instance that
represents the class. Then the program calls Class.getSuperclass
repeatedly to find the class's superclass
. The result of this
process is the class hierarchy for a given class. For example, if
you invoke the program with an argument of "java.util.ArrayList
",
the output is:
class java.util.ArrayList
class java.util.AbstractList
class java.util.AbstractCollection
class java.lang.Object
which is the class hierarchy of ArrayList
up through
java.lang.Object
. In other words, you can find out about the class
properties of ArrayList
from within a running program.
java.lang.Class
is a class whose instances represent other
classes and interfaces, such as ArrayList
, String
, and Cloneable
.
java.lang.Class
doesn't have a public constructor, so you can't say:
Class c = new Class(...);
Instead, you obtain java.lang.Class
instance references using
Class.forName
as illustrated above. Class.forName
is used to load
a class at run time, based on a string containing the class name
(such as "java.util.ArrayList
").
Another way you can obtain a java.lang.Class
instance reference is
to use the ".class
" notation, as in:
Class c = java.lang.String.class;
Because Java arrays fit into the class hierarchy rooted at
java.lang.Object
, you can also say:
Class c = int[].class;
A final way to obtain a java.lang.Class
reference is through an
object reference. In this case, you say:
Class c = obj.getClass();
or:
int vec[] = new int[10];
Class c = vec.getClass();
Once you have a java.lang.Class instance reference, you can create
new instances of the class that it represents. Here's an example:
public class ClassDemo2 {
public static void main(String args[]) {
Class cls = null;
Object obj = null;
// load a class by name
try {
cls = Class.forName(args[0]);
}
catch (ClassNotFoundException exc) {
System.err.println(exc);
}
// create a new instance of that class
try {
obj = cls.newInstance();
}
catch (IllegalAccessException exc1) {
System.err.println(exc1);
}
catch (InstantiationException exc2) {
System.err.println(exc2);
}
}
}
Class.newInstance
is used to create new instances of the class
represented by the java.lang.Class instance
. It assumes the
existence of a default (no parameter) constructor for the class.
You could ask why you can't simply use "new" to create a new
class instance, and not go to all the trouble of using
java.lang.Class
and newInstance
. The key point about the above
approach is that it's dynamic; you can load classes by name into
a program and create new instances of these classes.
If you load classes using a string name, like "pkg.classname
", then
you might wonder how the loaded classes are manipulated. One way is
to program in terms of interfaces. Suppose that you have an
interface A
, and two classes B
and C
that implement that interface.
Class B
uses an implementation that is the most space efficient,
while class C
uses more space but runs faster than B
. You can
dynamically load B
or C
, but then program in terms of the
interface A
. So the line above that reads:
obj = cls.newInstance();
might better read:
A aref = null;
... load B or C by name ...
aref = (A)cls.newIntance();
A complete example looks like this:
// file A.java
public interface A {
void f();
}
// file B.java
public class B implements A {
public void f() {
System.out.println("B.f");
}
}
// file C.java
public class C implements A {
public void f() {
System.out.println("C.f");
}
}
// file ClassDemo3.java
public class ClassDemo3 {
public static void main(String args[]) {
A aref = null;
Class cls = null;
// load a class (B or C)
try {
cls = Class.forName(args[0]);
}
catch (ClassNotFoundException exc) {
System.err.println(exc);
}
// create a new instance of that class
try {
aref = (A)cls.newInstance();
}
catch (IllegalAccessException exc1) {
System.err.println(exc1);
}
catch (InstantiationException exc2) {
System.err.println(exc2);
}
// call f() in the loaded class
aref.f();
}
}
If you say:
$ java ClassDemo3 C
then the output is:
C.f
that is, the f
method in the loaded class C
is called through
the interface reference.
In ClassDemo1
above, getSuperclass
finds the superclass of
a class represented by a java.lang.Class
instance. There are
other query methods that you can use with java.lang.Class
. For
example, a common one is illustrated by this program:
public class ClassDemo4 {
public static void classify(Object obj) {
if (obj == null) {
System.out.println("null");
}
else if (obj.getClass().isArray()) {
System.out.println("array");
}
else {
System.out.println("non-array");
}
}
public static void main(String args[]) {
classify(null);
classify(new int[10]);
classify(new String());
}
}
In this example there's an object reference passed to the classify
method, and you'd like to know whether it represents an array.
A final area worth mentioning is reflection. The Java programming lanugage system
defines a separate package for reflection (java.lang.reflect
), but
it relies on methods found in java.lang.Class
. One way to
distinguish java.lang.reflect facilities from those in
java.lang.Class
is to note that the java.lang.reflect
operates
on individual fields and methods in a class instance.
java.lang.Class
is concerned with a class as a whole unit.
For example, this program displays a list of all the public methods
found in String
:
import java.lang.reflect.*;
public class ClassDemo5 {
public static void main(String args[]) {
Class cls = java.lang.String.class;
Method methlist[] = cls.getMethods();
for (int i = 0; i < methlist.length; i++)
System.out.println(methlist[i]);
}
}
java.lang.reflect.Method
is a class found in the reflection package.
Class.getMethods
gets a list of all the methods in the
class. You could go deeper into the internals of a method, for
example, you could obtain a list of all the method's parameters in
a form that could be manipulated within an application. To do that,
you would use facilities such as Method.getParameterTypes
.
In other words, in this simple demo program, the names of all
methods and their parameters are displayed. However, to find out
which of the methods has a third parameter of type "int", use
reflection facilities within java.lang.reflect.Method
.
java.lang.Class
is a powerful mechanism for examining the properties
of classes, and for dynamically loading and manipulating those
classes.
OVERLOAD RESOLUTION
Suppose that you're doing some Java programming, and you have code
like this:
public class OverDemo1 {
static void f(float f) {}
static void f(double d) {}
public static void main(String args[]) {
f(37);
}
}
What happens here? It's possible to convert an integer value (37)
to either a float
or a double
, so which of the f methods is called?
Or is this program invalid? (The equivalent C++ program gives a
compile error.) The answers to these questions relate to
"overload resolution," that is, the rules that are applied
to determine which method is called when there is a set of
identically-named methods.
The first rule says that a method is "applicable" in a given
overloading case if the number of parameters in the method
declaration matches the number of arguments in the method
invocation, and the type of each argument can be converted to the
type of the corresponding method parameter.
You might think that a rule stating that the number of parameters
and arguments must match would be obvious, but not necessarily so.
In C++, functions are allowed default parameters, so that you can
declare a function by saying:
void f(int, double = 12.34);
and then call it with:
f(37);
which is converted by the C++ compiler to:
f(37, 12.34);
The rule about conversions can be illustrated by a slight variation
on the first example:
public class OverDemo2 {
static void f(float f) {}
static void f(String s) {}
public static void main(String args[]) {
f(37);
}
}
In this example, 37 can be converted to a float
through a "widening
primitive conversion." But there's no corresponding way to convert
37 to a String
, so there's no issue of choosing one f method over
the other. The compiler chooses f(float)
.
The second rule is that a method declaration must be "accessible,"
that is, available at the point where the method is invoked. Here
is another example:
class A {
public static void f(float f, double d) {}
private static void f(double d, float f) {}
}
public class OverDemo3 {
public static void main(String args[]) {
A.f(37, 47);
} }
At the point where A.f
is invoked within main, only one of the
f
methods from A
is accessible; the other one is private and not
available outside of the A
class. This particular example will
trigger an ambiguity error if both f
methods are public. Note that
it's never an error to simply declare more than one method with the
same name but different parameter types. The error occurs at the
point of method invocation if the compiler cannot determine which
method to invoke.
The third and final rule is the trickiest. Suppose that after the
first two rules above are applied, there are still two or more
methods with the same name that could possibly be called. For
example, in the OverDemo1
program above, neither of the two rules
already described removes either of the f methods from
consideration. Both methods have the right number of parameters,
it's possible to convert 37 to a float
or a double
, and both
methods are accessible.
So the third rule is to choose the most "specific" method. The
rule is: if any method still under consideration has parameter
types that are assignable to another method that's also still
in play, then the other method is removed from consideration.
This process is repeated until no other method can be eliminated.
If the result is a single "most specific" method, then that method
is called. If there's more than one method left, the call is
ambiguous.
Suppose that you have the methods:
f(float)
f(double)
In this case, the parameter types for the first method are
assignable to the parameter types of the second method, that is,
it's legal to say:
double = float
through a widening primitive conversion. By contrast, saying:
float = double
is not valid without a cast. Based on this third rule, f(double)
is
removed from the set of possible methods to call, and therefore
f(float)
is called. You can confirm this behavior with another demo
program:
public class OverDemo4 {
static void f(float f)
{System.out.println("float");}
static void f(double d)
{System.out.println("double");}
public static void main(String args[]) {
f(37);
}
}
which prints the value "float" when you run it.
Suppose that you have another case, with two methods:
f(float, double)
f(double, float)
and a call:
f(37, 47)
Here, the first parameter type of the first method (float)
can be assigned to the first parameter type of the second method
(double)
. But this doesn't work for the second parameter; you
can't assign double to float. If you start with the second
parameter and go in reverse order, you find the opposite to be
true. That is, the first parameter type of the second method
(double)
cannot be assigned to the first parameter type of the
first method (float)
. But you can assign the second parameter
float
to double
. So neither of the f
methods can be removed from
consideration. Therefore the f(37, 47)
call is ambiguous.
In section 15.11.2.2 of the Java Language Specification there's
a short paragraph that helps explain this rule:
The informal intuition is that one method declaration is more
specific than another if any invocation handled by the first
method could be passed on to the other one without a
compile-time error.
Another example will help to clarify this rule:
class A {}
class B extends A {}
class C extends B {}
public class OverDemo5 {
static void f(A a) {
System.out.println("f(A)");
}
static void f(B b) {
System.out.println("f(B)");
}
public static void main(String args[]) {
C cref = new C();
f(cref);
}
}
In this example, f(B)
is called, because it is more specific than
f(A)
. Any invocation of f(B)
could also be handled by f(A), but the
reverse is not true.
One final point about overload resolution: it's usually a good idea
to avoid being clever with this mechanism, even if you understand it
thoroughly. If you have a case where the parameter types are
completely distinct, as in:
void f(int)
void f(String)
this usage is good, but a case like:
void f(float)
void f(double)
called with f(37) starts to get tricky and confusing.
Note
The names on the JDCSM
mailing list
are used for internal Sun MicrosystemsTM
purposes only. To remove your name from the list, see
Subscribe/Unsubscribe
below.
Feedback
Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster
Subscribe/Unsubscribe
The JDC Tech Tips are sent to you because you elected to
subscribe when you
registered as a JDC member. To unsubscribe from JDC email, go
to the following
address and enter the email address you wish to remove from
the mailing list:
http://developer.java.sun.com/unsubscribe.html
To become a JDC member and subscribe to this newsletter go to:
http://java.sun.com/jdc/
_______
1 As used on this web site, the
terms "Java
virtual machine" or "JVM" mean a virtual
machine for the Java
platform.