Today I cover a few more features that exist in Java that most intermediate Java developers don’t know about. Similarly to part 1, we cover features that may not be useful in day-to-day programming, but are useful to know in the Java language. We will cover final classes and methods, anonymous subclass implementations and the double brace initialization.
Overview & Aims
In this tutorial, we will cover:
- Using the
final
keyword for classes and methods - How to create and use anonymous subclasses
- How to use double brace initialization
- The pros and cons of double brace initialization
Requirements & Expected Knowledge
Compared to part 1, this tutorial requires a little bit more knowledge of the Java language. I’ll assume that:
- You know what an
interface
is - You know the following about subclasses:
- How to create an implementation of an interface using the
implements
keyword - How to extend a class using the
extends
keyword
- How to create an implementation of an interface using the
- You know about the default constructor (covered in part 1)
The final keyword
The final
keyword is a rather simple and heavily underused keyword in Java which has similar properties to const
in C/C++ and val
in most modern programming languages such as Kotlin - it simply prevents a variable from being changed once declared1. An example of the final
keyword for variables is shown below:
1
2
3
4
5
6
7
public void someFunction() {
final int someVariable = 10;
// The following code will fail to compile
// as someVariable is final and cannot be redefined
someVariable = 5;
}
This final
keyword ensures that variables are not modified which can be used to better reason about the code and prevent accidentally changing a value. However, the final
keyword is not merely restricted to variables. Java lets you put the final
keyword on class and method declarations which gives classes protection against subclassing and gives methods protection from being overridden.
Final classes: Subclass prevention
By adding the final
keyword to a class, this prevents that class from being subclassed. This means that you cannot make a class which extends
the final class. To illustrate this, let’s look at an example of a simple final class:
1
2
3
4
5
6
7
8
9
10
11
12
13
public final class IntWrapper {
int i;
public IntWrapper(int i) {
this.i = i;
}
public int getValue() {
return this.i;
}
}
This simple class stores an integer value which can be created using the IntWrapper
constructor and the value of the integer can be retrieved using the getValue()
method. Say we wanted to create a subclass of this class that includes a setter method:
1 2 3 4 5 6 7 8 9 10 11 12 public class IntWrapperWithSetter extends IntWrapper { //Constructor which calls the superclass public IntWrapperWithSetter(int i) { super(i); } public void set(int i) { super.i = i; } }This results in a compilation error because the class
IntWrapper
is final. Using the final keyword means cannot be subclassed, so using theextends
keyword will not work.
Uses for final classes
Using final
for classes are useful if you want to ensure that other developers do not extend your class to add extra functionality. For example, in the standard Java library the classes String
and System
are also final classes, which prevents developers from subclassing them. Using final classes also ensures a higher level of security as it prevents other developers from extending classes that you’ve created.
Contrary to popular belief, making a class final
does not mean that the class is immutable - a final class can do everything that a non-final class can do, it just means it cannot be subclassed.
Final methods: Overriding protection
By adding the final
keyword to a method, this prevents that method from being overridden by a subclass. This prevents unexpected behaviour when that method is called which can be due to the overriding of that method from a subclass. For example, say we have our IntWrapper
class again, except this time the getValue()
method is final (and IntWrapper
is not a final class):
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IntWrapper {
int i;
public IntWrapper(int i) {
this.i = i;
}
public final int getValue() {
return this.i;
}
}
Since the class
IntWrapper
class is not final, it can be subclassed as normal, however overriding the definition ofgetValue()
will now result in a compilation failure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class IntWrapperWithSetter extends IntWrapper { //Constructor which calls the superclass public IntWrapperWithSetter(int i) { super(i); } public void set(int i) { super.i = i; } // This results in a compilation failure because // you cannot override a final method. @Override public int getValue() { return super.i + 10; } }
Anonymous subclasses
An anonymous subclass, also known as an anonymous class, is a class which has no name. They are often used to provide a specific instance of a class which is determinable at runtime, or when having a full class definition would be unnecessary. To create an anonymous subclass, you call the constructor to the parent class that you want to extend and then open a pair of braces. Within these braces, you are free to write your implementation of this class.
An example of an anonymous subclass
Since the concept is a little odd when written in words, let’s work through an example. Say we have our favourite example, IntWrapper
:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IntWrapper {
int i;
public IntWrapper(int i) {
this.i = i;
}
public int getValue() {
return this.i;
}
}
Let’s also have some arbitrary function in some other part of our code which contains an anonymous class to IntWrapper
:
1
2
3
public void someFunction() {
IntWrapper iWrapper = new IntWrapper(10) {};
}
Say we wanted an instance of IntWrapper
that when you call getValue()
, it returns the square of the number that it is storing. To do this in a normal subclass, we would have to override the getValue()
method and write our implementation. This method still remains the same, except instead we write it within our braces:
1
2
3
4
5
6
7
8
9
10
public void someFunction() {
IntWrapper iWrapper = new IntWrapper(10) {
@Override
public int getValue() {
return this.i * this.i;
}
};
}
Now we’ve basically created a custom implementation of IntWrapper
which when you call getValue()
, it returns the square of the number it is storing. Since this is still a subclass, you cannot create an anonymous class of a final class.
Anonymous interface implementations
So far, we’ve learnt how to create an anonymous (unnamed) subclass of a specific class. It is also possible to create an anonymous implementation of an interface, provided you include all of the methods that the interface must implement (as you would a normal class that is an implementation of an interface).
For example, the Runnable
interface contains a method run()
. To create an instance of a runnable, you can use the method above of using the new
keyword, followed by the name of the interface Runnable
, and then an implementation in a pair of braces that implements the run()
method:
1
2
3
4
5
6
7
8
9
10
public void someFunction() {
Runnable runnable = new Runnable() {
@Override
public void run() {
// Code goes here
}
};
}
Uses of anonymous subclasses ‘in the real world’
In Java programs that are commonly used ‘in the real world’, anonymous subclasses and anonymous interface implementations are often used to create simple implementations of classes that serve a single function that is often only used once in some code, thus being excessive to create its own class.
Examples of using an anonymous subclass is for writing multi-threading code. To spawn a new thread, one can create an anonymous subclass of the Thread
class and override the implementation of its run()
method, which will be run parallel to your main code:
1
2
3
4
5
6
7
8
new Thread() {
@Override
public void run() {
//Code goes here
}
}.start();
A common example of using anonymous interface implementations is for GUI action events. For example, if you wanted to run some code when a specific key is pressed in a text area, you can use an anonymous implementation of the KeyListener
interface:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JTextArea textArea = new JTextArea("Hi");
textArea.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
// Code goes here
}
@Override
public void keyReleased(KeyEvent e) {
// Code goes here
}
@Override
public void keyPressed(KeyEvent e) {
// Code goes here
}
});
Anonymous subclasses and generics
The last important thing to cover with anonymous subclasses is how they handle generics. In general, the generic type of the anonymous subclass must be explicitly stated, meaning that using the so called “diamond operator” <>
that was included in Java 7 cannot be used.
1
2
3
4
5
6
7
8
public void someFunction() {
// The type String must be explicitly stated after ArrayList
List<String> list = new ArrayList<String>() {
// Code goes here
};
}
Double brace initialization
This following topic refers to the anti-pattern double brace initialization. It is heavily inefficient in both time and memory performance and it is recommended to avoid using whenever possible. Nonetheless, we will still cover how it works and why it is so inefficient.
Double brace initialization uses a combination of two topics covered in Java Tips & Tricks: default constructors (part 1) and anonymous subclasses (part 2). Simply put, double brace initialization is an anonymous subclass which includes an implementation of the default constructor. For conciseness, the pair of braces used for the default constructor are often placed directly after the pair of braces for creating the implementation of an anonymous subclass, thus giving the impression of using ‘double braces’.
An example of double brace initialization
In this example, I show how double brace initialization is used. We use two pairs of double braces to initialize the contents of a List
in Java:
1
2
3
4
5
6
7
8
public void someFunction() {
List<String> list = new ArrayList<String>() {{
add("hello");
add("world");
}};
}
Because we are in the constructor for the class ArrayList
, we have access to all of the methods in ArrayList
that we normally can access through subclassing. Hence, we can take advantage of the add()
method to add elements directly to the list upon creating an instance of this object.
The problems with double brace initialization
Double brace initialization is heavily regarded as an anti-pattern
for the two following reasons:
It creates an additional class every time you use it
This is due to the normal behaviour for anonymous subclasses. When an anonymous subclass is written in code, Java still has to refer to it as if it were a normal class. To accommodate this, Java creates a class with the name of the parent class, followed by a number. For example, if you used an anonymous subclass in a Main.java
file, compiling this file will produce Main.class
as well as Main$1.class
.
It can lead to memory leaks
Double brace initialization generally leads to memory leaks. What this means is that the anonymous subclass that you create also includes a synthetic field which references the parent object. This is because double brace initialization can refer to private methods and (final) variables from the parent class in which this subclass was declared. To illustrate this, let’s walk through an example:
Say we have a class ParentClass
which includes a method that creates a list using double brace initialization, and also includes a private field containing a very important array (veryImportantArray
).
1
2
3
4
5
6
7
8
9
10
11
12
public class ParentClass {
private int[] veryImportantArray = new int[] {1, 2, 3};
public List<Integer> listOf5() {
List<Integer> list = new ArrayList<Integer>() {{
add(5);
add(5);
}};
return list;
}
}
When calling the function listOf5()
, the resulting list will include a reference to the class ParentClass
, which will include (in our example) veryImportantArray
:
Now, using reflection, it is possible for someone to retrieve the instance of the class ParentClass
and thus, access our very important array - a clear security flaw. If say, our “very important array” is an incredibly large object (memory wise), the list created using double brace initialization will also include a reference to this large object which cannot be garbage collected by the garbage collector, since it is referenced by this list.
Summary
To summarize, we’ve covered how the final
keyword can be used for classes to prevent subclassing as well as methods to prevent method overriding. We’ve taken a look at how anonymous subclasses can be constructed to provide code conciseness, as well as creating implementations of interfaces using anonymous subclasses. We also took a look at a specific use case of anonymous subclasses called double brace initialization and covered why it can lead to undesirable memory leaks.
Unless of course you’re using reflection, in which case anything goes. ↩