Class Classes
Goals
- Understand the types and uses of inner classes.
- Learn to work with
Class<T>
instances.
Concepts
- anonymous class
- inner class
- shadowed members
- singleton
- static inner class
Language
.class
Library
java.lang.Class
java.lang.Class.cast(Object obj)
java.lang.Class.getName()
java.lang.Class.isInstance(Object obj)
java.lang.Object
java.lang.Object.getClass()
Lesson
Inner Classes
You've been taught that every new class you write should go in a separate .java
file named after the class, and generally that is true. But you can also define a class inside another class; this is called an inner class. Why would you want to do this? It has mostly to do with program organization. Perhaps the class relates only to the outer class (the class in which it is defined) and not appropriate for use by other classes.
Static Inner Classes
We could create a Tank
class for storing fuel for a MotorVehicle
, and because this would be different than other tanks (an oil tank or an army tank), we could define it inside the MotorVehicle
class itself, creating a static inner class.
MotorVehicle
class with a Tank
static inner class.public class MotorVehicle extends Vehicle {
private final Tank tank;
public Tank getTank() {
return tank;
}
public MotorVehicle() {
tank = new Tank(60);
}
…
public static class Tank { //static inner class
private final long capacity;
public long getCapacity() {
return capacity;
}
private long amount = 0;
public long getAmount() {
return amount;
}
public void add(final long amount) {
checkArgument(this.amount + amount <= getCapacity());
this.amount += amount;
}
public void drain(final long amount) {
checkArgument(this.amount - amount >= 0);
this.amount -= amount;
}
/** @param capacity The tank capacity, in liters. */
private Tank(final long capacity) {
this.capacity = capacity;
}
}
}
final MotorVehicle motorVehicle = new MotorVehicle();
final MotorVehicle.Tank tank = motorVehicle.getTank();
tank.add(20); //add fuel to the tank
Non-Static Inner Classes
We can also create inner classes that are not static. These classes can only be instantiated in the context of an instance of the outer class. In other words, you cannot create one by itself; its outer class must be instantiated. An instance of a non-static inner class has access to all the instance variables and methods of the outer class. This is one way to create “helper functionality”.
MotorVehicle
class with a Tank
static inner class.public class MotorVehicle extends Vehicle {
private boolean on = false;
public boolean isOn() {
return on;
}
private final Tank tank;
public Tank getTank() {
return tank;
}
private final Engine engine;
public MotorVehicle() {
tank = new Tank(60);
engine = new Engine();
}
/** Starts the vehicle. */
public void go() {
on = true;
try {
engine.run();
} finally {
on = false;
}
}
…
private class Engine { //non-static inner class
private boolean on = false;
public boolean isOn() {
return on;
}
/** Runs the engine as long as there is fuel.
* The vehicle must be running.
* @throws IllegalStateException if the vehicle is not running.
*/
public void run() {
checkState(MotorVehicle.this.isOn(), "Vehicle must be on for engine to run.");
on = true;
try {
while(getTank().getAmount() > 0) {
getTank().drain(1); //use some fuel
//TODO move wheels
}
} finally {
on = false;
}
}
}
}
final MotorVehicle motorVehicle = new MotorVehicle();
motorVehicle.getTank().add(20); //add fuel to the tank
motorVehicle.go();
Anonymous Classes
Java allows you to extend a class (or implement an interface) on the fly without creating a separate format class definition. The class is anonymous because it has no name, which implies that no one can make another instance of that class. Essentially you define the class and instantiate it in one fell swoop. Normally this is done to implement an abstract method on the fly for a utility class.
Consider an AbstractEngine
that is defined in a separate class, requiring the implementation of a showWarning(…)
method for indicating that the engine is overheating, for example. A MotorVehicle
class could create an anonymous subclass of AbstractEngine
, implementing the showWarning(…)
method on the fly.
MotorVehicle
class implementing AbstractEngine
as an anonymous class.public abstract class AbstractEngine {
…
public abstract void showWarning(@Nonnull String message);
}
public class MotorVehicle extends Vehicle {
private final AbstractEngine engine;
public MotorVehicle() {
engine = new AbstractEngine() { //anonymous class
@Override
public void showWarning(@Nonnull String message) {
System.out.println(message);
}
}; //note the semicolon to end the expression
}
}
Because the anonymous class is defined in the scope of the MotorVehicle
, it has access to all the variables and methods of the MotorVehicle
instance, just like an inner class. Things get more interesting then if the MotorVehicle
already has a way to display warnings to the user. In this case, the anonymous class can simply delegate to the MotorVehicle
's functionality.
AbstractEngine
instance delegating to enclosing MotorVehicle
class instance.public class MotorVehicle extends Vehicle {
private final AbstractEngine engine;
protected void reportProblem(@Nonnull String message) {
System.out.println(message);
}
public MotorVehicle() {
engine = new AbstractEngine() { //anonymous class
@Override
public void showWarning(@Nonnull String message) {
reportProblem(message);
}
};
}
}
You can create anonymous implementations of interfaces on the fly as well. When learning about generics you learned how it could be useful to create a read-only, empty list and cast it to some generic type on the fly so that it could be used in various situations. Rather than creating a separate EmptyLinkedList<E>
class, you could create an anonymous implementation of LinkedList<E>
to use as the shared empty list instance.
Creating an anonymous instance of a LinkedList<E>
and using it in a Zoo
class.public class DataStructureUtils {
//instance of anonymous implementation of LinkedList<E>, stored for future use
private final static LinkedList<Object> EMPTY_LINKED_LIST = new LinkedList<Object>() {
@Override public getLength() {
return 0;
}
@Override public add(E element) {
throw new UnsupportedOperationException("This list is read-only.");
}
…
};
/** @return A shared instance of a read-only, empty linked list. */
@SuppressWarnings("unchecked")
public static <E> LinkedList<E> emptyLinkedList() {
return (LinkedList<E>)EMPTY_LINKED_LIST;
}
}
public class Zoo {
…
public LinkedList<Animal> getAnimals() {
if(animalCount == 0) { //if there are no animals in the zoo
return DataStructureUtils.emptyLinkedList();
}
}
}
A Class is a Class<T>
When you create a new FooBar
class, the resulting object is an instance of FooBar
, as you would expect.
final FooBar fooBar = new FooBar();
System.out.println(fooBar instanceof FooBar); //prints "true"
You also know that if FooBar
has any static variables, they live independent of any instance of FooBar
. That's because the FooBar
class itself is an object! For every class (and interface) you access, from FooBar
to Animal
to Vehicle
, Java will create an object to represent that class. The object representing the class is accessed via a .class
member that is present for every class. For example, FooBar.class
provides a reference to the object that represents the FooBar
class.
Every object is an instance of some class, and in fact every class is an instance of the Class<T>
class. Note that Class<T>
uses generics. The generic type <T>
refers to the type of the class, and you'll soon see how this can be useful.
final Class<FooBar> fooBarClass = FooBar.class;
System.out.println(fooBarClass instanceof FooBar); //prints "false"
System.out.println(fooBarClass instanceof Class); //prints "true"
The Class<T>
class has all sorts of interesting properties, such as java.lang.Class.getName()
which returns the full name of the class.
System.out.println(FooBar.class.getName()); //prints "com.example.FooBar"
Checking Class<T>
Instances
Another useful method is java.lang.Class.isInstance(Object obj)
, which checks to see if some object is an instance of the given class. This method functions identically to the instanceof
operator, with the added benefit that you can use it for some instance of a Class<>
even if you don't know what class it is!
Casting Class<T>
Instances
Once you know that an object is the instance of some Class<T>
, you can cast the instance to type T
using the java.lang.Class.cast(Object obj)
method.
Getting a Class<T> from an Instance
There is another way to find a class object. If you only have a reference to an object, you can get a reference to that object's class instance by calling java.lang.Object.getClass()
. This is possible because the method getClass()
is defined in the java.lang.Object
class, and every class extends from Object
—even Class<T>
!
final Truck truck = new Truck();
final Class<Truck> truckClass1 = Truck.class;
final Class<? extends Truck> truckClass2 = truck.getClass();
System.out.println(truckClass1 == truckClass2); //prints "true"
Review
In the Real World
- Inner classes have their uses, but in real life, if an inner class is to be accessed publicly, a separate class may wind up being a better choice.
Self Evaluation
- What is the difference between static and non-static inner classes?
- How would you access a variable of an outer class from an inner class that shadowed the variable?
Class.class
refers to an object that is an instance of what class?
Task
In your Booker application create a getPublicationsByType(…)
method. One of its parameters will be a linked list, using generics to allow a linked list of any publication type. The second parameter will be a Class<>
, but it must of a Publication
or subclass. Pick out all the publications of the given type and return a new linked list (of the captured type of the class) containing only publications of that type. Here's how it the method might be called:
final LinkedList<Book> books = getPublicationsByType(publicationList, Book.class);
Create appropriate unit tests of getPublicationsByType(…)
, including the following
- Create a list of books, magazines, and journals.
- Call
getPublicationsByType(publicationList, Periodical.class)
, storing the result in aLinkedList<Periodical>
. - Ensure that the returns list is the expected size, and that it contains only periodicals.
Finally refactor each of your methods that prints a certain type of publication, such as printBooks()
. Rather than searching through the list and printing the correct type of publication, delegate to your new getPublicationsByType(…)
method to retrieve a list containing only publications of the correct type. Then iterate through the new list and print those publications with no further need to check types or perform any additional casting.