Comments 42
Печально смотреть, как Java, хороший язык, со стройными изначально принципами ООП, все больше и больше превращают в PHP. Даже термин специальный придумали — деконструкция, ужас какой.
Простите, а при чем тут PHP?
Главное использовать подходящий к задаче в текущий момент.
switch (figure) {
case Rectangle(int w, int h) -> out.println("square: " + (w * h));
case Circle(int r) -> out.println("square: " + (2 * Math.PI * r));
default -> out.println("Default square: " + 0);
};
В ООП это было бы
out.println(String.format("square: %f", figure.square()));
или
figure.printSquare();
Но никак не так как в примерах из статьи. Тоесть, либо одно, либо другое, вместе никак.
Ну а если в программе возникает необходимость писать подобный код
switch (data) {
case Integer i -> System.out.println(i * i);
case Byte b -> System.out.println(b * b);
case Long l -> System.out.println(l * l);
case String s -> System.out.println(s * s);
case null -> System.out.println("Null value ");
default -> System.out.println("Default value: " + data);
};
это значит лишь то что объектно-ориентированный дизайн зашел куда-то совсем не туда. Ну или это код внутри самой JDK, что врядли.
>В ООП это было бы
>out.println(String.format(«square: %f», figure.square()));
Не было бы. Представьте на мгновение, что figure — это не ваши классы, что они написаны сторонним разработчиком, и менять (а то и посмотреть на их код) вы не можете.
>зашел куда-то совсем не туда
А даже если и зашел? Если часть этого кода не ваша, у вас мало шансов поменять там дизайн.
И да, то что тут рассмотрено — это библиотека, она не может превращать Java как язык во что-либо.
Совершенно независимо от качества примеров автора (и ограниченности данной библиотеки), вы похоже не понимаете, что такое pattern matching, и зачем он нужен.
Я прекрасно понимаю что это за паттерн и зачем он нужен. И что он принципиально не совместим с Java.
Не было бы. Представьте на мгновение, что figure — это не ваши классы, что они написаны сторонним разработчиком, и менять (а то и посмотреть на их код) вы не можете.
В этом случае тем более никак и никогда не могу использовать паттерн матчинг, потому что все что я вижу из библиотеки — это интерфейсы. я не вижу внутренностей объектов. Попытка использовать тяжелую артиллерию вроде рефлекшена для того чтобы подсмотреть внутренности объектов в библиотеке — это ядерная бомба замедленного действия. В следующей версии библиотеки автор поменяет свои объекты, и весь паттерн матчинг рухнет как карточный домик.
Если я хочу что-то добавить к коду библиотеки, я оборачиваю библиотечные классы своими классами, но никак не лезу рефлекшеном внутрь библиотечных классов.
Вы просто не понимаете суть ООП, если для вас добавление чего-то к библиотечному коду это обязательно означает разобрать объект на кусочки.
Простите, а как связаны pattern matching и залезание рефлексией внутрь чужих объектов?
Совершенно не похоже. Вы бы хоть википедию что-ли прочитали? В тамошней вполне приличной формулировке матчинг — это поиск паттерна в последовательности токенов . Не в объекте — это лишь частный случай, а в последовательности объектов. Типичный случай — дерево (ну или список из головы и хвоста).
сопоставление с образцом используется в конструировании потока управления данными, например. писать отдельный класс под каждый промежуточный вариант — та еще тухлятина.
те же конечные автоматы гораздо удобнее получаются.
еще хорошо для _лаконичных_ деконструкций. когда метод возвращает например два значения — и распаковка pair в две отдельных переменных это тоже сопоставление с образцом. особенно если нужна только одна из них дальше.
да, всегда можно написать иначе. ну на то и сахар, чтоб вкуснее было.
Не было бы. Представьте на мгновение, что figure — это не ваши классы, что они написаны сторонним разработчиком, и менять (а то и посмотреть на их код) вы не можете.
это делается через визитор. Вот такие свичи это практичеси всегда быдлокод.
в конструировании потока управления данными,
не очень оптимально использовать рефлекшн, потому что он дольше чем обычный доступ к переменным. Тут прикручивается стирание классов например Питона к яве. Так берите Питон для этой задачи.
> это делается через визитор. Вот такие свичи это практичеси всегда быдлокод.
Визитор это и есть такой свитч, только obscured.
Ну и да, визитор — это неудобный (частный, для ООП) способ реализации матчинга, если угодно.
примеры из статьи — искуственные и показывают что можно делать (но отнюдь не что нужно).
Приведите нормальный пример, который доказывает нужность этой концепции и этой библиотеки. Пока что все абсолютно примеры доказывают её вредность и ненужность.
Ну может вот этот только можно использовать
switch (side, width) {
case "top", 25 -> System.out.println("top");
case "bottom", 30 -> System.out.println("bottom");
case "left", 15 -> System.out.println("left");
case "right", 15 -> System.out.println("right");
case null -> System.out.println("Null value ");
default -> System.out.println("Default value ");
};
сопоставление с образцом используется в конструировании потока управления данными, например. писать отдельный класс под каждый промежуточный вариант — та еще тухлятина.
Сопоставление с образцом используется в функциональных языках, потому что в них нет понятия инкапсуляции и типов (в том понимании в котором они используются в Java например).
В Java да, именно что под каждый промежуточный вариант надо делать свой класс. Так принято в Java. Но ненадо в Java тащить чужеродные концепции. Если не умеете программировать на Java, и очень хочется использовать pattern matching — идите в Haskel, Lisp, Scala и т.п.
ОК, вам не нужен синтаксический сахар.
Я знаю как жавистам больно от введения «var».
Простите. Придётся учиться, энтерпрайз не повод продолжать писать как 50 лет назад.
Препроцессор какой-нибудь, типа ретролямбд, скорее всего.
Она реализует Scala-like pattern matching для Java 8+
короче вроде универсального визитора? Достаёт значения через рефлекшн и конструирует реальные инстанции обьекта и сравнивает по equals? Ну так джава код и не предназначет для массивной сериализации инстанций)))
боже какие замороты с тем что в питоне есть просто так.
Притащенная соответственно функциональщиками, людьми, которые не знают что такое инкапсуляция.
есть также принцип разделения функций. Я тоже взглянув с перва подумал что это быдлокод со свичами, заменяющими полиморфизм, но тут можно выделить произвольную проверку в отдельный класс — принцип визитора
И кстати много кода не нужно. Тут в один небольшой класс поместится весь функционал вышеперечисленного, если с рефлекшном, а если без, то как вы достаёте поля?
А всё, на гитхабе кто посмотрит тот поймёт что достают рефлекшном например через fields. Можно делать одним классом вроде кода ниже только расширить.
package utils;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class AnyValidator {
private Class lastClass = null;
private String lastResult = "";
private Object dummy = new Integer(10);
public boolean validateFields(Object instance, Map<String, Object> mp) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
lastClass = instance.getClass();
Iterator<Map.Entry<String, Object>> it = mp.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry<String, Object>)it.next();
String fieldName = entry.getKey();
Object goodValue = entry.getValue();
boolean isGoodField = equalsObjectField(fieldName, instance, goodValue);
if (! isGoodField) {
lastResult = "validateFields: field " + fieldName + " is wrong";
return false;
}
}
return true;
}
public boolean equalsObjectField(String fieldName, Object instance, Object goodValue) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field field = lastClass.getDeclaredField(fieldName);
Object fieldValue = field.get(instance);
if (goodValue == null)
return fieldValue == null;
return goodValue.equals(fieldValue);
}
public static void main(String[] args) {
AnyValidator dummy = new AnyValidator();
AnyValidator val = new AnyValidator();
Map<String, Object> mp = new HashMap<String, Object> ();
mp.put("dummy" , new Integer(10));
mp.put("lastResult" , "");
mp.put("lastClass" , null);
try {
boolean res = val.validateFields(dummy, mp);
System.out.println(res);
dummy.dummy = new Integer(11);
res = val.validateFields(dummy, mp);
System.out.println(res);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Ограничения на количество веток вызвано тем что, что для каждого случая используется разные перегрузки функции matches().
Накладные расходы добавляются с рефлексией, но без неё не как.
Написано на чистой Java 8.
Не рассматривали, в качестве оптимизации, замену рефлексии возможностями java.lang.invoke
, а, особенно, LambdaMetaFactory, т.е. генерацию на лету для вызовов методов реализаций неких функциональных интерфейсов? + кэширование соответствующих вещей
Допустим, в Вашем DeconstructPattern
вызов verifyExtractMethods(..)
основывается на (для каждого вызова) анализе класса для поиска аннотированных методов (takeExtractMethods(..)
) и дальнейшем их вызове через открытие доступа и рефлективый вызов. Вместо этого же можно было результат поиска метода кэшировать, причём для вызова самого метода генерируя объект функ. интерфейса, а ля
interface Extractor {
void extract(Object... parameters);
}
или даже JDKшный Consumer<Object[]>
,
что позволило бы избавиться от постоянных накладных расходов на открытие доступа к рефлективной сущности, да и сделало код более явным.
Как по мне, учитывая то, что эта библиотека, в первую очередь, синтаксический сахар, вопрос производительности тут критичен, потому что, даже принимая во внимание магию JIT, сахар с сравнительно большим оверхедом проигрывает более многословным, но примитивных, с точки зрения генерируемого байткода, способам достижения такой же логики.
Если же углубляться в оптимизации, то можно попробовать генерировать в рантайме полностью свои сущности для доступа к каким-то вещам, или даже, используя Intrumentation API, при наличии соответствующего агента, заменять при загрузке засахаренных классов вызовы сложных методов байткодом подобного же, но без сахара (e.g, для всё того же Deconstructor'а производить, фактически, инструкции доступа к полям.
В целом же необычных подход к добавлению сахара :)
При вашем желании, могу накидать Issues на гитхабе
Можете более детально описать как именно можно закэшировать, вместо прямого поиска и вызова метода через рефлексию?
На гитхаб можете писать Issues, буду рад.
Можете более детально описать как именно можно закэшировать, вместо прямого поиска и вызова метода через рефлексию?
Что касается кэша, то, с точки зрения реализации, тут варианты любые, в зависимости от ваших же предпочтений (JDK: Map, Guava: Cache и т.д.). Я, скорее, говорил о подходе, что, учитывая, что у вас зачастую происходит вызов методов с одинаковыми параметрами, для которых происходит полный анализ класса, уместно результат этих самых вычислений сохранять для дальнейшего переиспользования.
Для примера, тот самый <V> List<Method> takeExtractMethods(V value)
уместно заменить на List<Method> getExtractMethods(Class<?> value)
(простите мне моё переименование, не позволяет тут совесть take
оставить :) ) (кстати, генерик тоже тут излишен), который будет анализировать содержимое класса (ваше нынешнее тело метода) только в том случае, если в кэше для данного класса это ещё не найдено, после чего это значение будет заноситься в этот самый кэш, дабы при повторном вызове не пришлось производить эту затратную операцию. С этой точки зрения особенно хороша, как по мне, реализация Guava, так как она и функциональна (V get(K, Callable<? extends V>)
, где K
и V
типы ключей и значений соотв-но) и позволяет удобно настраивать режим освобождения кэша.
На гитхаб можете писать Issues, буду рад.
Отлишно, постараюсь накидать в ближайшее время
И внедрить в проект.
Что касается идеи, в целом — да, я просто описал это в общем (так это у вас не в одном месте может быть полезно).
Что касается доступа к полям, тут можно улучшить производительность через Invoke API, но LambdaMetaFactory
использовать не получится, так как её реализация в OpenJDK
(и, полагаю, иных тоже) не допускает генерацию классов для инструкций доступа к полям (инструкции putfield
, putstatic
, getfield
, getstatic
). Поэтому тут получится лишь создавать MethodHandle
, который, однако, также показывает незначительный прирост в производительности.
З другой стороны паттерн матчинг является одной с приоритетных направлений Java.
Есть уже два jep:
pattern matching for instanceof.
pattern matching for switch.
Но ждать прийдется какия минимум к LTS Java 17 а то и больше.
З другой стороны паттерн матчинг является одной с приоритетных направлений Java.
думайте что хотите, для кого приоритетное, для кого — антипаттерн, и ту задачу в бизнес логике которую вы прикручиваете этим матчингом можно сделать другим способом без него более чистым кодом.
Доступ к полям практично делать аннотациями как в Spring, причём подозреваю так как функционал спринга довольно широк там есть решение для этого матчинга причём более чистое.
Реализация сопоставления с образцом в Java