[Java] Create a temporary class with new Object() {}

3 minute read

Introduction

The Java Object class is a very important superclass of all classes, but isn’t it rare to new itself? This article introduces two code examples where new Object() makes sense.

Define a method inside a method

The following is a code example described in the comment of Implementing a Combination of Mathematics in Java-Qiita”. This method enumerates and returns the k number of combinations from the string array data.

    static List<String[]> combination(String[] data, int k) {
        List<String[]> result = new ArrayList<String[]>();
        combination(data, 0, new String[k], 0, result);
        return result;
    }

    static void combination(String[] data, int di, String[] comb, int ci, List<String[]> result) {
        if (ci == comb.length) {
            result.add(comb.clone());
            return;
        }
        for (; di <= data.length-(comb.length-ci); di++) {
            comb[ci] = data[di];
            combination(data, di + 1, comb, ci + 1, result);
        }
    }

It consists of two methods, the one called directly from the outside is the two-argument combination, and the five-argument combination is used internally. The 5-argument combination calls itself again, but of the 5 arguments, the 3 argument (data, comb, result) specifies exactly the same value. If Java can define a method by nesting it, it is not necessary to write the same argument when calling again. Unfortunately, Java doesn’t allow nested methods. So I’ll rewrite this using new Object().

    static List<String[]> combination(String[] data, int k) {
        int length = data.length;
        List<String[]> result = new ArrayList<String[]>();
        String[] comb = new String[k];
        new Object() {
            void combination(int di, int ci) {
                if (ci == k) {
                    result.add(comb.clone());
                    return;
                }
                for (; di <= length-(k-ci); di++) {
                    comb[ci] = data[di];
                    combination(di + 1, ci + 1);
                }
            }
        }.combination(0, 0);
        return result;
    }

The 5 argument combination is described inside the new Object() {} and the fixed 3 arguments (data, comb, result) are deleted. The new Object() {} in this code defines an anonymous inner class and at the same time creates an instance of that class. The .combination(0, 0) after the new Object() {} calls the combination(int di, int ci) defined in the inner class. Fixed 3 variables (data, comb, result) can be referenced from the inner class, so it is not necessary to pass them as arguments for each call. The useless instance is created with new Object(), but the overhead is small because only one is created. Since the number of arguments when re-invoking is reduced, the stack consumption is reduced.

Class for temporary data storage

If you have a class like this:

public class Person {

    private final String name;
    private final String age;

    public Person(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public String getAge() {
        return this.age;
    }
}

And suppose you have a list of Person like this:

List<Person> list = List.of(
    new Person("Yamada", "18"),
    new Person("Ichikawa", "72"),
    new Person("Sato", "39"),
    new Person("Tanaka", "9"));

Consider sorting this in ascending order by age. Since age is a string, it must be written like this to sort as an integer.

    List<Person> result = list.stream()
        .sorted(Comparator.comparingInt(p -> Integer.parseInt(p.getAge())))
        .collect(Collectors.toList());

This works fine, but it’s not very efficient as Integer.parseInt(p.getAge()) is executed twice each time you compare values. Even with the fast sorting algorithm, Integer.parseInt will be executed $ 2n \log n $ times.

Map.Entry

You can solve this problem by pairing an instance of Person and an integer of age. In such cases, the Map.Entry class is often used as a temporary storage location.

    List<Person> result = list.stream()
        .map(p -> Map.entry(Integer.parseInt(p.getAge()), p))
        .sorted(Comparator.comparingInt(Entry::getKey))
        .map(Entry::getValue)
        .collect(Collectors.toList());

It only needs to calculate the integer age once, but it’s hard to intuitively understand where Map.Entry is used.

Records

Java14 has added a feature called Records that allows you to easily define immutable classes for storing data. This is what it looks like.

    record PersonWithAge(Person p, int age) {
        PersonWithAge(Person p) {
            this(p, Integer.parseInt(p.getAge()));
        }
    }

    List<Person> result = list.stream()
        .map(PersonWithAge::new)
        .sorted(Comparator.comparingInt(PersonWithAge::age))
        .map(PersonWithAge::p)
        .collect(Collectors.toList());

It’s easier to understand, but it also feels a bit overkill as it defines a class for temporary storage.

new Object()

Here’s what happens when you use new Object().

    List<Person> result = list.stream()
        .map(p -> new Object() {
            int intAge = Integer.parseInt(p.getAge());
            Person person = p;
        })
        .sorted(Comparator.comparingInt(obj -> obj.intAge))
        .map(obj -> obj.person)
        .collect(Collectors.toList());

new Object() {...} is a temporary storage area that is valid only within this expression. Although it is an anonymous class, it is recognized in this expression as a full-fledged class with fields intAge and person.

Summary

How was that? You can see that the Object class has various uses. I don’t recommend it because it feels tricky, but I’ve been using it since I knew I could write it like this.