Double brace initialization java. Куда указывает this?
Пишу свой data класс, чтобы потом все поля конвертировать в Map. Условно имею что-то похожее:
public class Variables {
private EEO eeo;
public EEO getEeo() {
if (eeo == null) eeo = new EEO();
return eeo;
}
public Map<String, Object> toMap() {
var map = new HashMap<String, Object>();
for (Field declaredVariable : this.getClass().getDeclaredFields()) {
if (declaredVariable != null) {
Map<String, Object> declaredEntries = new HashMap<>();
for (Field declaredField : declaredVariable.getClass().getDeclaredFields()) {
Object field = null;
try {
field = declaredField.get(eeo);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (field instanceof AbstractMap.SimpleEntry) {
AbstractMap.SimpleEntry<String, Object> entry = (AbstractMap.SimpleEntry<String, Object>) field;
declaredEntries.put(entry.getKey(), entry.getValue());
}
}
map.put(declaredVariable.getClass().getSimpleName(), declaredEntries);
}
}
return map;
}
public class EEO {
public AbstractMap.SimpleEntry<String, String> target;
public AbstractMap.SimpleEntry<String, String> getTarget() {
if (target == null) target = new AbstractMap.SimpleEntry<>("target", null);
return target;
}
}
}
Помимо класса EEO будут еще подобные. Решил использовать рефлексию, дабы можно было расширить в дальнейшем.
Столкнулся с такой проблемой - если инициализировать данный класс через double brace, то this в методе toMap() будет указывать не на данный объект Varibles, а на объект класса Test где была произведена инициализация такого класса:
public class Test {
public void test() {
var variables = new Variables() {{
getEeo().getTarget().setValue("trgt");
getEeo().getSource().setValue("src");
}}.toMap();
}
}
Соответвственно вся рефлексия валится, так как читает поля не с того объекта.
В случае с обычной инициализацией через new Variables(); и дальнейшим построчным setEeo() и тд все работает правильно, this указывает на инстанс Variables.
var variables = new Variables();
variables.getEeo().getTarget().setValue("trgt");
variables.getEeo().getSource().setValue("src");
variables.toMap();
Почему это так работает? И есть ли какое-то решение, помимо приведенного в конце?
Ответы (1 шт):
Вы неправильно интерпретируете то, что происходит.
this в toMap в этом примере не указывает на объект Test и getClass не возвращает класс Test. Дело в том, что вы создаете анонимный внутренний класс, когда используете эту конструкцию:
new Variables() {
...
}
и именно его вы видите. Класс анонимный в том смысле, что вы ему имя не дали и обращаться по имени не можете. Компилятор сгенерировал для него имя типа Test$1 и скорее всего его вы приняли за класс Test. Но это отдельный класс унаследованный от Variables.
Есть альтернативный вариант сделать то, что вы хотите без использвования анонимного внутреннего класса - это использовать лямбду с обратным вызовом для инициализации. По краткости практически то же самое. Вот демонстрация подхода (и ваш подход для сравнения):
class Variables {
public Variables() {}
public Variables(java.util.function.Consumer<Variables> initializer) {
initializer.accept(this);
}
public void getEeo() {
System.out.println("in getEeo()");
}
public Variables toMap() {
System.out.println("in toMap(): " + this);
return this;
}
}
public class MyClass {
public static void main(String args[]) {
Variables v1 = new Variables() {{
getEeo();
}}.toMap();
Variables v2 = new Variables(vars -> {
vars.getEeo();
}).toMap();
}
}