Java's ʻObject class is a very important class in the superclass of all classes, but isn't it rare to
newitself? In this article, I'll show you two code examples where
new Object ()` makes sense.
The following is a code example described in the comment "Implementing a combination of mathematics in Java --Qiita". A method that enumerates and returns k
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 that is called directly from the outside is the two-argument combination
, and the five-argument combination
is used internally. The 5-argument combination
is recalling itself, but of the 5 arguments, the 3 arguments (data
, comb
, result
) specify exactly the same value as the argument.
If Java can define methods nested, you shouldn't have to write the same arguments on the recall call. Unfortunately, you can't nest methods in Java.
So let's rewrite it 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 written insidenew 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.
The fixed 3 variables (data
, comb
, result
) can be referenced from within the inner class, so there is no need to pass them as arguments for each call.
I am creating a useless instance with new Object ()
, but the overhead is small because I only create one. Stack consumption is reduced because there are fewer arguments on the recall call.
Suppose 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;
}
}
Furthermore, suppose you have the following list of Person
s.
List<Person> list = List.of(
new Person("Yamada", "18"),
new Person("Ichikawa", "72"),
new Person("Sato", "39"),
new Person("Tanaka", "9"));
Consider sorting this by ʻage in ascending order. Since ʻage
is a string, it must be written like this to sort it 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 because ʻInteger.parseInt (p.getAge ()) is executed twice each time the values are compared. Even with the fast sorting algorithm, ʻInteger.parseInt
will be executed $ 2n \ log n $ times.
Map.Entry
Pairing an instance of Person
with an integerized version of ʻagesolves this problem. 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());
The integer ʻageonly needs to be calculated once, but it is intuitively difficult to understand where
Map.Entry` is used.
Records Java 14 adds a feature called Records that makes it easy to define immutable classes for data storage. If you use this, it will be like this.
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 exaggerated because it defines a class for temporary storage.
new Object()
This is 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 location that is only valid in this expression. Although it is an anonymous class, it is recognized in this expression as a full-fledged class with fields ʻintAgeand
person`.
How was that? You can see that the ʻObject` class also has various uses. I can't recommend it because it feels tricky, but I've been using it since I knew I could write it like this.
Recommended Posts