Home Java Tips & Tricks (2/2)
Post
Cancel

Java Tips & Tricks (2/2)

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
  • 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 the extends 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 of getValue() 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:

Double brace initialization

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.


  1. Unless of course you’re using reflection, in which case anything goes. 

This post is licensed under CC BY 4.0 by the author.

Java Tips & Tricks (1/2)

Making a better programming language