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 languages like traditional C++, objects are formulated by and
instantiated from classes, but classes are really just artifacts of the
compiler. In those languages, you see classes mentioned only in 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 object type you
use; this Class
object is responsible
for producing instances of that type. But you don’t generally have to
worry about that unless you are interested in loading new kinds of classes
dynamically at runtime or using a highly abstracted API that wants a
“type” instead of an actual argument. The Class
object is also the basis for “reflecting”
on a class to find its methods and other properties, allowing you to find
out about an object’s structure or invoke its methods programmatically at
runtime. We’ll discuss reflection in the next section.
We get the Class
associated with
a particular object with the getClass()
method:
String
myString
=
"Foo!"
Class
stringClass
=
myString
.
getClass
();
We can also get the Class
reference for a particular class statically, using the .class
notation:
Class
stringClass
=
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 its full name:
String
s
=
"Boofa!"
;
Class
stringClass
=
s
.
getClass
();
System
.
out
.
println
(
stringClass
.
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
)
stringClass
.
newInstance
();
}
catch
(
InstantiationException
e
)
{
...
}
catch
(
IllegalAccessException
e
)
{
...
}
Here, newInstance()
has a
return type of Object
, so we have to
cast it to a reference of the appropriate type. This is fine, but we’ll
see in the next chapter that the Class
class is a generic class, which means that we can parameterize it to be
more specific about the Java type we’re dealing with; that is, we can get
the newInstance()
method to return the
correct type directly without the cast. We’ll show this here, but don’t
worry if it doesn’t make any sense yet:
Class
<
String
>
stringClass
=
String
.
class
;
try
{
String
s2
=
stringClass
.
newInstance
();
// no cast necessary
}
catch
(
InstantiationException
e
)
{
...
}
catch
(
IllegalAccessException
e
)
{
...
}
A couple of exceptions can be thrown here. An InstantiationException
indicates that we’re trying to instantiate an abstract
class or an interface. IllegalAccessException
is
a more general exception that indicates that 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.
(In the next section, we’ll learn how to do just that using the Reflection
API.)
All of 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 loaded by a string 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. This kind of class loading is the basis for many kinds of
configurable systems in Java.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.