В этот вопрос пользователь @Holger предоставил ответ, который показывает необычное использование анонимных классов, о котором я не знал.
В этом ответе используются потоки, но этот вопрос касается не потоков, поскольку эта анонимная конструкция типа может использоваться в других контекстах, то есть:
String s = "Digging into Java intricacies";
Optional.of(new Object() { String field = s; })
.map(anonymous -> anonymous.field) // anonymous implied type
.ifPresent(System.out::println);
К моему удивлению, это компилирует и печатает ожидаемый результат.
Примечание. Мне хорошо известно, что с древних времен можно построить анонимный внутренний класс и использовать его элементы следующим образом:
int result = new Object() { int incr(int i) {return i + 1; } }.incr(3);
System.out.println(result); // 4
Однако это не то, о чем я прошу здесь. Мой случай отличается, потому что анонимный тип распространяется по цепочке методов Optional
.
Теперь я могу представить очень полезное использование этой функции... Много раз мне нужно было выполнить некоторую операцию map
по конвейеру Stream
, а также сохранить исходный элемент, т.е. предположим, что у меня есть список людей:
public class Person {
Long id;
String name, lastName;
// getters, setters, hashCode, equals...
}
List<Person> people = ...;
И мне нужно сохранить JSON-представление моих экземпляров Person
в каком-то репозитории, для которого мне нужна строка JSON для каждого экземпляра Person
, а также каждый Person
id:
public static String toJson(Object obj) {
String json = ...; // serialize obj with some JSON lib
return json;
}
people.stream()
.map(person -> toJson(person))
.forEach(json -> repository.add(ID, json)); // where the ID?
В этом примере я потерял поле Person.id
, так как я преобразовал каждого человека в соответствующую строку json.
Чтобы обойти это, я видел, что многие люди используют какой-то класс Holder
или Pair
, или даже Tuple
, или просто AbstractMap.SimpleEntry
:
people.stream()
.map(p -> new Pair<Long, String>(p.getId(), toJson(p)))
.forEach(pair -> repository.add(pair.getLeft(), pair.getRight()));
Хотя это достаточно хорошо для этого простого примера, для него все еще требуется наличие общего класса Pair
. И если нам нужно распространять 3 значения через поток, я думаю, что мы могли бы использовать класс Tuple3
и т.д. Использование массива также является опцией, однако оно не является безопасным, если все значения одного типа.
Итак, используя подразумеваемый анонимный тип, тот же самый код может быть переписан следующим образом:
people.stream()
.map(p -> new Object() { Long id = p.getId(); String json = toJson(p); })
.forEach(it -> repository.add(it.id, it.json));
Это волшебство! Теперь мы можем иметь как можно больше полей, а также сохранять безопасность типов.
Во время тестирования я не смог использовать подразумеваемый тип в отдельных строках кода. Если я изменю свой исходный код следующим образом:
String s = "Digging into Java intricacies";
Optional<Object> optional = Optional.of(new Object() { String field = s; });
optional.map(anonymous -> anonymous.field)
.ifPresent(System.out::println);
Я получаю ошибку компиляции:
Error: java: cannot find symbol
symbol: variable field
location: variable anonymous of type java.lang.Object
И этого следует ожидать, потому что в классе Object
нет члена с именем field
.
Итак, я хотел бы знать:
- Является ли это документированным где-то или есть что-то об этом в JLS?
- Какие существуют ограничения, если таковые имеются?
- Действительно ли можно написать такой код?
- Есть ли сокращенный синтаксис для этого, или это лучшее, что мы можем сделать?