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 runtime. By
comparison, classes in Smalltalk are real, runtime entities in the
language that are themselves described by “metaclasses”
and “metaclass 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 runtime 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. You don’t have to worry about any of
this unless you are interested in loading new kinds of classes
dynamically at runtime. 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 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 ask
for the name of the object’s class:
String s = "Boofa!"; Class mycls= s.getClass( ); System.out.println( mycls.getName( ) ); // "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 previous 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.
(newInstance( )
has to be able to return any kind
of constructed object.) 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. It doesn’t allow us to pass any arguments to a
constructor. (But see Section 7.3.4 later in this chapter.)
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 these 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 ) { ... } }
Here we have an application loading a class implementation
(Printer
which implements the
Typewriter
interface) knowing only its name.
Imagine the name was entered by the user or looked up from a
configuration file.