Многие современные языки поддерживают сопоставление с образцом (pattern matching) на уровне языка. Java в данный момент не поддерживает pattern matching, но есть надежды что в будущем все может измениться.
Сопоставление с образцом раскрывают перед разработчиком возможность писать код более гибко и красивее, при этом оставляя его понятным.
Используя возможности Java 8, можно реализовать некоторые возможности pattern matching в виде библиотеки. При этом можно использовать как утверждение так и выражения.
Constant pattern позволяет проверить на равность с константами. В Java switch позволяет проверить на равность числа, перечисления и строки. Но иногда хочется проверить на равность константы объектов используя метод equals().
switch (data) { case new Person("man") -> System.out.println("man"); case new Person("woman") -> System.out.println("woman"); case new Person("child") -> System.out.println("child"); case null -> System.out.println("Null value "); default -> System.out.println("Default value: " + data); };
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 константами.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.ConstantPattern.matches; matches(data, new Person("man"), () -> System.out.println("man"); new Person("woman"), () -> System.out.println("woman"); new Person("child"), () -> System.out.println("child"); Null.class, () -> System.out.println("Null value "), Else.class, () -> System.out.println("Default value: " + data) );
Tuple pattern позволяет проверить на равность нескольких перемен с константами одновременно.
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 "); };
В данный момент библиотека поддерживает возможность указывать 4 переменные и 6 веток.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.TuplePattern.matches; matches(side, width, "top", 25, () -> System.out.println("top"); "bottom", 30, () -> System.out.println("bottom"); "left", 15, () -> System.out.println("left"); "right", 15, () -> System.out.println("right"); Null.class, () -> System.out.println("Null value"), Else.class, () -> System.out.println("Default value") );
Type test pattern позволяет одновременно сопоставить тип и извлечь значение переменной. В Java для этого нам нужно сначала проверить тип, привести к типу и потом присвоить новой переменной.
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); };
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.VerifyPattern.matches; matches(data, Integer.class, i -> { System.out.println(i * i); }, Byte.class, b -> { System.out.println(b * b); }, Long.class, l -> { System.out.println(l * l); }, String.class, s -> { System.out.println(s * s); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
Guard pattern позволяет одновременно сопоставить тип и проверить на условия.
switch (data) { case Integer i && i != 0 -> System.out.println(i * i); case Byte b && b > -1 -> System.out.println(b * b); case Long l && l < 5 -> System.out.println(l * l); case String s && !s.empty() -> System.out.println(s * s); case null -> System.out.println("Null value "); default -> System.out.println("Default: " + data); };
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами и условиями.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.GuardPattern.matches; matches(data, Integer.class, i -> i != 0, i -> { System.out.println(i * i); }, Byte.class, b -> b > -1, b -> { System.out.println(b * b); }, Long.class, l -> l == 5, l -> { System.out.println(l * l); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
Для упрощения написания условия, разработчик может использовать следующее функции для сравнения: lessThan/lt, greaterThan/gt, lessThanOrEqual/le, greaterThanOrEqual/ge,
equal/eq, notEqual/ne. А для того чтобы опустить условия можно пременить: always/yes, never/no.
matches(data, Integer.class, ne(0), i -> { System.out.println(i * i); }, Byte.class, gt(-1), b -> { System.out.println(b * b); }, Long.class, eq(5), l -> { System.out.println(l * l); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
Deconstruction pattern позволяет одновременно сопоставить тип и разложить объект на составляющие. В Java для этого нам нужно сначала проверить тип, привести к типу, присвоить новой переменной и только тогда через геттеры доступиться к полям класса.
let (int w, int h) = figure; 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); }; for ((int w, int h) : listFigures) { System.out.println("square: " + (w * h)); }
В данный момент библиотека поддерживает возможность проверять значения переменной с 3 типами и раскладывать объект на 3 составляющее.
import org.kl.state.Else; import static org.kl.pattern.DeconstructPattern.matches; import static org.kl.pattern.DeconstructPattern.foreach; import static org.kl.pattern.DeconstructPattern.let; Figure figure = new Rectangle(); let(figure, (int w, int h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rectangle.class, (int w, int h) -> out.println("square: " + (w * h)), Circle.class, (int r) -> out.println("square: " + (2 * Math.PI * r)), Else.class, () -> out.println("Default square: " + 0) ); foreach(listRectangles, (int w, int h) -> { System.out.println("square: " + (w * h)); });
При этом чтобы получить составляющее, класс должен иметь один или несколько деконструирующих методов. Эти методы должны быть помечены аннотаций Extract.
Все параметры должны быть открытыми. Поскольку примитивы нельзя передать в метод по ссылке, нужно использовать обертки на примитивы IntRef, FloatRef и т.д.
import org.kl.type.IntRef; ... @Extract public void deconstruct(IntRef width, IntRef height) { width.set(this.width); height.set(this.height); }
Также используя Java 11, можно выводить типы деконструирующих параметров.
Figure figure = new Rectangle(); let(figure, (var w, var h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rectangle.class, (var w, var h) -> out.println("square: " + (w * h)), Circle.class, (var r) -> out.println("square: " + (2 * Math.PI * r)), Else.class, () -> out.println("Default square: " + 0) ); foreach(listRectangles, (var w, var h) -> { System.out.println("square: " + (w * h)); });
Property pattern позволяет одновременно сопоставить тип и доступиться к полям класса по их именам. Вместо того, чтобы предоставить все поля, можно доступиться к нужным и в любой последовательности.
let (w: int w, h:int h) = figure; switch (figure) { case Rect(w: int w == 5, h: int h == 10) -> out.println("sqr: " + (w * h)); case Rect(w: int w == 10, h: int h == 15) -> out.println("sqr: " + (w * h)); case Circle (r: int r) -> out.println("sqr: " + (2 * Math.PI * r)); default -> out.println("Default sqr: " + 0); }; for ((w: int w, h: int h) : listRectangles) { System.out.println("square: " + (w * h)); }
В данный момент библиотека поддерживает возможность проверять значения переменной с 3 типами и раскладывать объект на 3 составляющее.
import org.kl.state.Else; import static org.kl.pattern.PropertyPattern.matches; import static org.kl.pattern.PropertyPattern.foreach; import static org.kl.pattern.PropertyPattern.let; import static org.kl.pattern.PropertyPattern.of; Figure figure = new Rectangle(); let(figure, of("w", "h"), (int w, int h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rect.class, of("w", 5, "h", 10), (int w, int h) -> out.println("sqr: " + (w * h)), Rect.class, of("w", 10, "h", 15), (int w, int h) -> out.println("sqr: " + (w * h)), Circle.class, of("r"), (int r) -> out.println("sqr: " + (2 * Math.PI * r)), Else.class, () -> out.println("Default sqr: " + 0) ); foreach(listRectangles, of("x", "y"), (int w, int h) -> { System.out.println("square: " + (w * h)); });
Также для упрощения именования полей можно использовать другой способ.
Figure figure = new Rect(); let(figure, Rect::w, Rect::h, (int w, int h) -> { System.out.println("border: " + w + " " + h)); }); matches(figure, Rect.class, Rect::w, Rect::h, (int w, int h) -> out.println("sqr: " + (w * h)), Circle.class, Circle::r, (int r) -> out.println("sqr: " + (2 * Math.PI * r)), Else.class, () -> System.out.println("Default sqr: " + 0) ); foreach(listRectangles, Rect::w, Rect::h, (int w, int h) -> { System.out.println("square: " + (w * h)); });
Position pattern позволяет одновременно сопоставить тип и проверить значение полей в порядке объявления. В Java для этого нам нужно сначала проверить тип, привести к типу, присвоить новой переменной и только тогда через геттеры доступиться к полям класса и проверить на равность.
switch (data) { case Circle(5) -> System.out.println("small circle"); case Circle(15) -> System.out.println("middle circle"); case null -> System.out.println("Null value "); default -> System.out.println("Default value: " + data); };
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами и проверять сразу 4 поля.
import org.kl.state.Else; import org.kl.state.Null; import static org.kl.pattern.PositionPattern.matches; import static org.kl.pattern.PositionPattern.of; matches(data, Circle.class, of(5), () -> { System.out.println("small circle"); }, Circle.class, of(15), () -> { System.out.println("middle circle"); }, Null.class, () -> { System.out.println("Null value "); }, Else.class, () -> { System.out.println("Default value: " + data); } );
Также если разработчик не хочет проверять некоторые поля, эти поля должны быть помечены аннотаций @Exclude. Эти поля должны быть объявлены последними.
class Circle { private int radius; @Exclude private int temp; }
Static pattern позволяет одновременно сопоставить тип и деконструировать объект используя фабричные методы.
Optional some = ...; switch (some) { case Optional.empty() -> System.out.println("empty value"); case Optional.of(var v) -> System.out.println("value: " + v); default -> System.out.println("Default value"); };
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами и раскладывать объект на 3 составляющее.
import org.kl.state.Else; import static org.kl.pattern.StaticPattern.matches; import static org.kl.pattern.StaticPattern.of; Optional some = ...; matches(figure, Optinal.class, of("empty"), () -> System.out.println("empty value"), Optinal.class, of("of") , (var v) -> System.out.println("value: " + v), Else.class, () -> System.out.println("Default value") );
При этом чтобы получить составляющее, класс должен иметь один или несколько деконструирующих методов, помеченные аннотаций Extract.
@Extract public void of(IntRef value) { value.set(this.value); }
Sequence pattern позволяет проще обрабатывать последовательности данных.
List<Integer> list = ...; switch (list) { case Ranges.head(var h) -> System.out.println("list head: " + h); case Ranges.tail(var t) -> System.out.println("list tail: " + t); case Ranges.empty() -> System.out.println("Empty value"); default -> System.out.println("Default value"); };
Используя библиотеку можно писать следующим образом.
import org.kl.state.Else; import org.kl.range.Ranges; import static org.kl.pattern.SequencePattern.matches; List<Integer> list = ...; matches(figure, Ranges.head(), (var h) -> System.out.println("list head: " + h), Ranges.tail(), (var t) -> System.out.println("list tail: " + t), Ranges.empty() () -> System.out.println("Empty value"), Else.class, () -> System.out.println("Default value") );
Также для упрощения кода, можно использовать следующее функции.
import static org.kl.pattern.CommonPattern.with; import static org.kl.pattern.CommonPattern.when; Rectangle rect = new Rectangle(); with(rect, it -> { it.setWidth(5); it.setHeight(10); }); when( side == Side.LEFT, () -> System.out.println("left value"), side == Side.RIGHT, () -> System.out.println("right value") );
Как можно видеть pattern matching сильный инструмент, который намного упрощает написание кода. Используя лямбды-выражения, ссылки на метод и вывод типов параметров лямбды можно сэмулировать возможности pattern matching самыми средствами языка.
Исходной код библиотеки открыт и доступный на github.
ссылка на оригинал статьи https://habr.com/ru/post/460773/
Добавить комментарий