The last method of Object we need to discuss
is getClass(). This method returns a reference
to the Class object that produced the object
instance.
A good measure of the complexity of an object-oriented language
is the degree of abstraction of its class structures. We know that
every object in Java is an instance of a class, but what exactly is a
class? In C++, objects are formulated by and instantiated from
classes, but classes are really just artifacts of the compiler. Thus,
you see classes only mentioned in C++ source code, not at run-time. By
comparison, classes in Smalltalk are real, run-time entities in the
language that are themselves described by "meta-classes"
and "meta-class classes." Java strikes a happy medium between these
two languages with what is, effectively, a two-tiered system that uses
Class objects.
Classes in Java source code are represented at run-time by
instances of the java.lang.Class class. There's a
Class object for every class you use; this
Class object is responsible for producing instances
for its class. This may sound overwhelming, but you don't have
to worry about any of it unless you are interested in loading new
kinds of classes dynamically at run-time. The
Class object is also the basis for
"reflecting" on a class to find out its methods and other properties;
we'll discuss this feature in the next section.
We can get the Class associated with
a particular object with the getClass() method:
String myString = "Foo!" Class c = myString.getClass();
We can also get the Class reference for a particular class statically,
using the special .class notation:
Class c = String.class;
The .class reference looks like a static field that exists in every
class. However, it is really resolved by the compiler.
One thing we can do with the Class object
is to ask for the name of the object's class:
String s = "Boofa!"; Class strClass = s.getClass(); System.out.println( strClass.getName() ); // prints "java.lang.String"
Another thing that we can do with a Class is
to ask it to produce a new instance of its type of object. Continuing with
the above example:
try {
String s2 = (String)strClass.newInstance();
}
catch ( InstantiationException e ) { ... }
catch ( IllegalAccessException e ) { ... }
newInstance() has a return type of
Object, so we have to cast it to a reference of the
appropriate type. A couple of problems can occur here. An
InstantiationException indicates we're trying
to instantiate an abstract class or an
interface. IllegalAccessException is a more general
exception that indicates we can't access a constructor for the
object. Note that newInstance() can create only an
instance of a class that has an accessible default constructor.
There's no way for us to pass any arguments to a constructor.
All this becomes more meaningful when we add the capability to look
up a Class by name. forName()
is a static method of Class
that returns a Class object given its name as
a String:
try {
Class sneakersClass = Class.forName("Sneakers");
}
catch ( ClassNotFoundException e ) { ... } A ClassNotFoundException is thrown if the class
can't be located.
Combining the above tools, we have the power to load new kinds of classes dynamically. When combined with the power of interfaces, we can use new data types by name in our applications:
interface Typewriter {
void typeLine( String s );
...
}
class Printer implements Typewriter {
...
}
class MyApplication {
...
String outputDeviceName = "Printer";
try {
Class newClass = Class.forName( outputDeviceName );
Typewriter device = (Typewriter)newClass.newInstance();
...
device.typeLine("Hello...");
}
catch ( Exception e ) {
}