The Class Class

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.