Anonymous inner classes
The next example looks a little strange:
//: c08:Parcel6.java
// A method that returns an anonymous inner class.
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
} ///:~
The cont( ) method combines the creation of the return value with the definition of the class that represents that return value! In addition, the class is anonymous; it has no name. To make matters a bit worse, it looks like you’re starting out to create a Contents object:
return new Contents()
But then, before you get to the semicolon, you say, “But wait, I think I’ll slip in a class definition”:
return new Contents() {
private int i = 11;
public int value() { return i; }
};
What this strange syntax means is: “Create an object of an anonymous class that’s inherited from Contents.” The reference returned by the new expression is automatically upcast to a Contents reference. The anonymous inner-class syntax is a shorthand for:
class MyContents implements Contents {
private int i = 11;
public int value() { return i; }
}
return new MyContents();
In the anonymous inner class, Contents is created by using a default constructor. The following code shows what to do if your base class needs a constructor with an argument:
//: c08:Parcel7.java
// An anonymous inner class that calls
// the base-class constructor.
public class Parcel7 {
public Wrapping wrap(int x) {
// Base constructor call:
return new Wrapping(x) { // Pass constructor argument.
public int value() {
return super.value() * 47;
}
}; // Semicolon required
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Wrapping w = p.wrap(10);
}
} ///:~
That is, you simply pass the appropriate argument to the base-class constructor, seen here as the x passed in new Wrapping(x).
The semicolon at the end of the anonymous inner class doesn’t mark the end of the class body (as it does in C++). Instead, it marks the end of the expression that happens to contain the anonymous class. Thus, it’s identical to the use of the semicolon everywhere else.
You can also perform initialization when you define fields in an anonymous class:
//: c08:Parcel8.java
// An anonymous inner class that performs
// initialization. A briefer version of Parcel4.java.
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
} ///:~
If you’re defining an anonymous inner class and want to use an object that’s defined outside the anonymous inner class, the compiler requires that the argument reference be final, like the argument to dest( ). If you forget, you’ll get a compile-time error message.
As long as you’re simply assigning a field, the approach in this example is fine. But what if you need to perform some constructor-like activity? You can’t have a named constructor in an anonymous class (since there’s no name!), but with instance initialization, you can, in effect, create a constructor for an anonymous inner class, like this:
//: c08:AnonymousConstructor.java
// Creating a constructor for an anonymous inner class.
import com.bruceeckel.simpletest.*;
abstract class Base {
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
private static Test monitor = new Test();
public static Base getBase(int i) {
return new Base(i) {
{
System.out.println("Inside instance initializer");
}
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
monitor.expect(new String[] {
"Base constructor, i = 47",
"Inside instance initializer",
"In anonymous f()"
});
}
} ///:~
In this case, the variable i did not have to be final. While i is passed to the base constructor of the anonymous class, it is never directly used inside the anonymous class.
Here’s the “parcel” theme with instance initialization. Note that the arguments to dest( ) must be final since they are used within the anonymous class:
//: c08:Parcel9.java
// Using "instance initialization" to perform
// construction on an anonymous inner class.
import com.bruceeckel.simpletest.*;
public class Parcel9 {
private static Test monitor = new Test();
public Destination
dest(final String dest, final float price) {
return new Destination() {
private int cost;
// Instance initialization for each object:
{
cost = Math.round(price);
if(cost > 100)
System.out.println("Over budget!");
}
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.dest("Tanzania", 101.395F);
monitor.expect(new String[] {
"Over budget!"
});
}
} ///:~
Inside the instance initializer you can see code that couldn’t be executed as part of a field initializer (that is, the if statement). So in effect, an instance initializer is the constructor for an anonymous inner class. Of course, it’s limited; you can’t overload instance initializers, so you can have only one of these constructors.