Java does not have structural types. The closest you could map the values to, are instances of anonymous classes. But there are significant drawbacks. Starting with Java 16, using record
would be the better solution, even if it’s a named type and might be slightly more verbose.
E.g. assuming
class Person {
int id;
String name, address;
public Person(String name, String address, int id) {
this.id = id;
this.name = name;
this.address = address;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
you can do
List<Person> lst = List.of(
new Person("Pava", "India", 1), new Person("tiwari", "USA", 2));
var result = lst.stream()
.map(p -> {
record NameAndAddress(String name, String address){}
return new NameAndAddress(p.getName(), p.getAddress());
})
.collect(Collectors.toList());
result.forEach(x -> System.out.println(x.name() + " " + x.address()));
The anonymous inner class alternative would look like
List<Person> lst = List.of(
new Person("Pava", "India", 1), new Person("tiwari", "USA", 2));
var result = lst.stream()
.map(p -> new Object(){ String address = p.getAddress(); String name = p.getName();})
.collect(Collectors.toList());
result.forEach(x -> System.out.println(x.name + " " + x.address));
but as you might note, it’s still not as concise as a structural type. Declaring the result
variable using var
is the only way to refer to the type we can not refer to by name. This requires Java 10 or newer and is limited to the method’s scope.
It’s also important to keep in mind that inner classes can create memory leaks due to capturing a reference to the surrounding this
. In the example, each object also captures the value of p
used for its initialization. The record
doesn’t have these problems and further, it automatically gets suitable equals
, hashCode
, and toString
implementations, which implies that printing the list like System.out.println(result);
or transferring it to a set like new HashSet<>(result)
will have meaningful results.
Also, it’s much easier to move the record
’s declaration to a broader scope.
Prior to Java 10, lambda expressions are the only Java feature that supports declaring variables of an implied type, which could be anonymous. E.g., the following would work even in Java 8:
List<String> result = lst.stream()
.map(p -> new Object(){ String address = p.getAddress(); String name = p.getName();})
.filter(anon -> anon.name.startsWith("ti"))
.map(anon -> anon.address)
.collect(Collectors.toList());