Anonymous Classes
Anonymous classes enable you to make your code more concise. They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.
This section covers the following topics:
- Declaring Anonymous Classes
- Syntax of Anonymous Classes
- Accessing Local Variables of the Enclosing Scope, and Declaring and Accessing Members of the Anonymous Class
- Examples of Anonymous Classes
Declaring Anonymous Classes
While local classes are class declarations, anonymous classes are expressions, which means that you define the class in another expression. The following example, HelloWorldAnonymousClasses
, uses anonymous classes in the initialization statements of the local variables frenchGreeting
and spanishGreeting
, but uses a local class for the initialization of the variable englishGreeting
:
public class HelloWorldAnonymousClasses { interface HelloWorld { public void greet(); public void greetSomeone(String someone); } public void sayHello() { class EnglishGreeting implements HelloWorld { String name = "world"; public void greet() { greetSomeone("world"); } public void greetSomeone(String someone) { name = someone; System.out.println("Hello " + name); } } HelloWorld englishGreeting = new EnglishGreeting(); HelloWorld frenchGreeting = new HelloWorld() { String name = "tout le monde"; public void greet() { greetSomeone("tout le monde"); } public void greetSomeone(String someone) { name = someone; System.out.println("Salut " + name); } }; HelloWorld spanishGreeting = new HelloWorld() { String name = "mundo"; public void greet() { greetSomeone("mundo"); } public void greetSomeone(String someone) { name = someone; System.out.println("Hola, " + name); } }; englishGreeting.greet(); frenchGreeting.greetSomeone("Fred"); spanishGreeting.greet(); } public static void main(String... args) { HelloWorldAnonymousClasses myApp = new HelloWorldAnonymousClasses(); myApp.sayHello(); } }
Syntax of Anonymous Classes
As mentioned previously, an anonymous class is an expression. The syntax of an anonymous class expression is like the invocation of a constructor, except that there is a class definition contained in a block of code.
Consider the instantiation of the frenchGreeting
object:
HelloWorld frenchGreeting = new HelloWorld() { String name = "tout le monde"; public void greet() { greetSomeone("tout le monde"); } public void greetSomeone(String someone) { name = someone; System.out.println("Salut " + name); } };
The anonymous class expression consists of the following:
-
The
new
operator -
The name of an interface to implement or a class to extend. In this example, the anonymous class is implementing the interface
HelloWorld
. -
Parentheses that contain the arguments to a constructor, just like a normal class instance creation expression. Note: When you implement an interface, there is no constructor, so you use an empty pair of parentheses, as in this example.
-
A body, which is a class declaration body. More specifically, in the body, method declarations are allowed but statements are not.
Because an anonymous class definition is an expression, it must be part of a statement. In this example, the anonymous class expression is part of the statement that instantiates the frenchGreeting
object. (This explains why there is a semicolon after the closing brace.)
Accessing Local Variables of the Enclosing Scope, and Declaring and Accessing Members of the Anonymous Class
Like local classes, anonymous classes can capture variables; they have the same access to local variables of the enclosing scope:
-
An anonymous class has access to the members of its enclosing class.
-
An anonymous class cannot access local variables in its enclosing scope that are not declared as
final
or effectively final. -
Like a nested class, a declaration of a type (such as a variable) in an anonymous class shadows any other declarations in the enclosing scope that have the same name. See Shadowing for more information.
Anonymous classes also have the same restrictions as local classes with respect to their members:
-
You cannot declare static initializers or member interfaces in an anonymous class.
-
An anonymous class can have static members provided that they are constant variables.
Note that you can declare the following in anonymous classes:
-
Fields
-
Extra methods (even if they do not implement any methods of the supertype)
-
Instance initializers
-
Local classes
However, you cannot declare constructors in an anonymous class.
Examples of Anonymous Classes
Anonymous classes are often used in graphical user interface (GUI) applications.
Consider the JavaFX example HelloWorld.java
(from the section Hello World, JavaFX Style from Getting Started with JavaFX). This sample creates a frame that contains a Say 'Hello World' button. The anonymous class expression is highlighted:
import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class HelloWorld extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Hello World!"); Button btn = new Button(); btn.setText("Say 'Hello World'"); btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } }); StackPane root = new StackPane(); root.getChildren().add(btn); primaryStage.setScene(new Scene(root, 300, 250)); primaryStage.show(); } }
In this example, the method invocation btn.setOnAction
specifies what happens when you select the Say 'Hello World' button. This method requires an object of type EventHandler<ActionEvent>
. TheEventHandler<ActionEvent>
interface contains only one method, handle. Instead of implementing this method with a new class, the example uses an anonymous class expression. Notice that this expression is the argument passed to the btn.setOnAction
method.
Because the EventHandler<ActionEvent>
interface contains only one method, you can use a lambda expression instead of an anonymous class expression. See the section Lambda Expressions for more information.
Anonymous classes are ideal for implementing an interface that contains two or more methods. The following JavaFX example is from the section Customization of UI Controls. The highlighted code creates a text field that only accepts numeric values. It redefines the default implementation of the TextField
class with an anonymous class by overriding the replaceText
and replaceSelection
methods inherited from theTextInputControl
class.
import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.stage.Stage; public class CustomTextFieldSample extends Application { final static Label label = new Label(); @Override public void start(Stage stage) { Group root = new Group(); Scene scene = new Scene(root, 300, 150); stage.setScene(scene); stage.setTitle("Text Field Sample"); GridPane grid = new GridPane(); grid.setPadding(new Insets(10, 10, 10, 10)); grid.setVgap(5); grid.setHgap(5); scene.setRoot(grid); final Label dollar = new Label("$"); GridPane.setConstraints(dollar, 0, 0); grid.getChildren().add(dollar); final TextField sum = new TextField() { @Override public void replaceText(int start, int end, String text) { if (!text.matches("[a-z, A-Z]")) { super.replaceText(start, end, text); } label.setText("Enter a numeric value"); } @Override public void replaceSelection(String text) { if (!text.matches("[a-z, A-Z]")) { super.replaceSelection(text); } } }; sum.setPromptText("Enter the total"); sum.setPrefColumnCount(10); GridPane.setConstraints(sum, 1, 0); grid.getChildren().add(sum); Button submit = new Button("Submit"); GridPane.setConstraints(submit, 2, 0); grid.getChildren().add(submit); submit.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { label.setText(null); } }); GridPane.setConstraints(label, 0, 1); GridPane.setColumnSpan(label, 3); grid.getChildren().add(label); scene.setRoot(grid); stage.show(); } public static void main(String[] args) { launch(args); } }
Local Classes
Local classes are classes that are defined in a block, which is a group of zero or more statements between balanced braces. You typically find local classes defined in the body of a method.
This section covers the following topics:
- Declaring Local Classes
- Accessing Members of an Enclosing Class
- Local Classes Are Similar To Inner Classes
Declaring Local Classes
You can define a local class inside any block (see Expressions, Statements, and Blocks for more information). For example, you can define a local class in a method body, a for
loop, or an if
clause.
The following example, LocalClassExample
, validates two phone numbers. It defines the local class PhoneNumber
in the method validatePhoneNumber
:
public class LocalClassExample { static String regularExpression = "[^0-9]"; public static void validatePhoneNumber( String phoneNumber1, String phoneNumber2) { final int numberLength = 10; // Valid in JDK 8 and later: // int numberLength = 10; class PhoneNumber { String formattedPhoneNumber = null; PhoneNumber(String phoneNumber){ // numberLength = 7; String currentNumber = phoneNumber.replaceAll( regularExpression, ""); if (currentNumber.length() == numberLength) formattedPhoneNumber = currentNumber; else formattedPhoneNumber = null; } public String getNumber() { return formattedPhoneNumber; } // Valid in JDK 8 and later: // public void printOriginalNumbers() { // System.out.println("Original numbers are " + phoneNumber1 + // " and " + phoneNumber2); // } } PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1); PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2); // Valid in JDK 8 and later: // myNumber1.printOriginalNumbers(); if (myNumber1.getNumber() == null) System.out.println("First number is invalid"); else System.out.println("First number is " + myNumber1.getNumber()); if (myNumber2.getNumber() == null) System.out.println("Second number is invalid"); else System.out.println("Second number is " + myNumber2.getNumber()); } public static void main(String... args) { validatePhoneNumber("123-456-7890", "456-7890"); } }
The example validates a phone number by first removing all characters from the phone number except the digits 0 through 9. After, it checks whether the phone number contains exactly ten digits (the length of a phone number in North America). This example prints the following:
First number is 1234567890 Second number is invalid
Accessing Members of an Enclosing Class
A local class has access to the members of its enclosing class. In the previous example, the PhoneNumber
constructor accesses the member LocalClassExample.regularExpression
.
In addition, a local class has access to local variables. However, a local class can only access local variables that are declared final. When a local class accesses a local variable or parameter of the enclosing block, itcaptures that variable or parameter. For example, the PhoneNumber
constructor can access the local variable numberLength
because it is declared final; numberLength
is a captured variable.
However, starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final. For example, suppose that the variable numberLength
is not declared final, and you add the highlighted assignment statement in the PhoneNumber
constructor:
PhoneNumber(String phoneNumber) { numberLength = 7; String currentNumber = phoneNumber.replaceAll( regularExpression, ""); if (currentNumber.length() == numberLength) formattedPhoneNumber = currentNumber; else formattedPhoneNumber = null; }
Because of this assignment statement, the variable numberLength
is not effectively final anymore. As a result, the Java compiler generates an error message similar to "local variables referenced from an inner class must be final or effectively final" where the inner class PhoneNumber
tries to access the numberLength
variable:
if (currentNumber.length() == numberLength)
Starting in Java SE 8, if you declare the local class in a method, it can access the method's parameters. For example, you can define the following method in the PhoneNumber
local class:
public void printOriginalNumbers() { System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2); }
The method printOriginalNumbers
accesses the parameters phoneNumber1
and phoneNumber2
of the method validatePhoneNumber
.
Shadowing and Local Classes
Declarations of a type (such as a variable) in a local class shadow declarations in the enclosing scope that have the same name. See Shadowing for more information.
Local Classes Are Similar To Inner Classes
Local classes are similar to inner classes because they cannot define or declare any static members. Local classes in static methods, such as the class PhoneNumber
, which is defined in the static methodvalidatePhoneNumber
, can only refer to static members of the enclosing class. For example, if you do not define the member variable regularExpression
as static, then the Java compiler generates an error similar to "non-static variable regularExpression
cannot be referenced from a static context."
Local classes are non-static because they have access to instance members of the enclosing block. Consequently, they cannot contain most kinds of static declarations.
You cannot declare an interface inside a block; interfaces are inherently static. For example, the following code excerpt does not compile because the interface HelloThere
is defined inside the body of the methodgreetInEnglish
:
public void greetInEnglish() { interface HelloThere { public void greet(); } class EnglishHelloThere implements HelloThere { public void greet() { System.out.println("Hello " + name); } } HelloThere myGreeting = new EnglishHelloThere(); myGreeting.greet(); }
You cannot declare static initializers or member interfaces in a local class. The following code excerpt does not compile because the method EnglishGoodbye.sayGoodbye
is declared static
. The compiler generates an error similar to "modifier 'static' is only allowed in constant variable declaration" when it encounters this method definition:
public void sayGoodbyeInEnglish() { class EnglishGoodbye { public static void sayGoodbye() { System.out.println("Bye bye"); } } EnglishGoodbye.sayGoodbye(); }
A local class can have static members provided that they are constant variables. (A constant variable is a variable of primitive type or type String
that is declared final and initialized with a compile-time constant expression. A compile-time constant expression is typically a string or an arithmetic expression that can be evaluated at compile time. See Understanding Class Members for more information.) The following code excerpt compiles because the static member EnglishGoodbye.farewell
is a constant variable:
public void sayGoodbyeInEnglish() { class EnglishGoodbye { public static final String farewell = "Bye bye"; public void sayGoodbye() { System.out.println(farewell); } } EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye(); myEnglishGoodbye.sayGoodbye(); }