Muitos idiomas modernos suportam a correspondência de padrões no nível do idioma. Atualmente, Java não suporta correspondência de padrões, mas há esperança de que as coisas possam mudar no futuro.
A comparação com o exemplo revela ao desenvolvedor a oportunidade de escrever um código de maneira mais flexível e bonita, deixando-o compreensível.
Usando os recursos do Java 8, você pode implementar alguns dos recursos de correspondência de padrões na forma de uma biblioteca. Nesse caso, você pode usar a instrução e a expressão.
Padrão constante permite verificar a igualdade com constantes. Em Java, uma opção permite verificar números iguais, enumerações e seqüências de caracteres. Mas, às vezes, quero verificar a igualdade das constantes dos objetos usando o método 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); };
Para diferentes opções de ramificação, você precisa usar as sobrecargas do método fósforos (). Portanto, no momento, a biblioteca suporta a capacidade de verificar os valores de uma variável com 6 constantes.
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) );
O padrão de tupla permite verificar a igualdade de várias alterações com constantes simultaneamente.
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 "); };
No momento, a biblioteca suporta a capacidade de especificar 4 variáveis e 6 ramificações.
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") );
O padrão de teste de tipo permite corresponder simultaneamente ao tipo e extrair o valor da variável. Em Java, para isso, precisamos primeiro verificar o tipo, converter o tipo e depois atribuí-lo a uma nova variável.
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); };
No momento, a biblioteca suporta a capacidade de verificar os valores de uma variável com 6 tipos.
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); } );
O padrão de proteção permite combinar simultaneamente o tipo e verificar as condições.
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); };
No momento, a biblioteca suporta a capacidade de verificar os valores de uma variável com 6 tipos e condições.
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); } );
Para simplificar a gravação da condição, o desenvolvedor pode usar as seguintes funções para comparação: lessThan / lt, GreaterThan / gt, lessThanOrEqual / le, GreaterThanOrEqual / ge,
igual / eq, notEqual / ne. E para omitir as condições, você pode tentar: sempre / sim, nunca / não.
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); } );
O padrão de desconstrução permite combinar simultaneamente o tipo e decompor o objeto em componentes. Em Java, para isso, precisamos primeiro verificar o tipo, converter para um tipo, atribuí-lo a uma nova variável e acessar somente os campos da classe por meio de getters.
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)); }
No momento, a biblioteca suporta a capacidade de verificar os valores de uma variável com 3 tipos e decompor o objeto em 3 componentes.
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)); });
Além disso, para obter um componente, uma classe deve ter um ou mais métodos de desconstrução. Esses métodos devem ser rotulados com extrato de anotação.
Todos os parâmetros devem estar abertos. Como as primitivas não podem ser passadas para um método por referência, você precisa usar wrappers para as primitivas IntRef, FloatRef, etc.
import org.kl.type.IntRef; ... @Extract public void deconstruct(IntRef width, IntRef height) { width.set(this.width); height.set(this.height); }
Também usando o Java 11, é possível inferir tipos de parâmetros de desconstrução.
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)); });
O padrão de propriedade permite corresponder simultaneamente ao tipo e acessar os campos da classe por seus nomes. Em vez de fornecer todos os campos, você pode acessar os campos necessários em qualquer ordem.
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)); }
No momento, a biblioteca suporta a capacidade de verificar os valores de uma variável com 3 tipos e decompor o objeto em 3 componentes.
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)); });
Você também pode usar outro método para simplificar os campos de nomeação.
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)); });
O padrão de posição permite combinar simultaneamente o tipo e verificar o valor dos campos na ordem da declaração. Em Java, para isso, precisamos primeiro verificar o tipo, convertê-lo em um tipo, atribuí-lo a uma nova variável e somente acessar os campos da classe por meio de getters e verificar a igualdade.
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); };
No momento, a biblioteca suporta a capacidade de verificar os valores de uma variável com 6 tipos e verificar 4 campos ao mesmo tempo.
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); } );
Além disso, se o desenvolvedor não quiser verificar alguns campos, esses campos deverão ser marcados com anotações @Exclude. Esses campos devem ser declarados por último.
class Circle { private int radius; @Exclude private int temp; }
O padrão estático permite combinar simultaneamente o tipo e desconstruir o objeto usando métodos de fábrica.
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"); };
No momento, a biblioteca suporta a capacidade de verificar os valores de uma variável com 6 tipos e decompor o objeto em 3 componentes.
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") );
Ao mesmo tempo, para obter o componente, a classe deve ter um ou mais métodos de desconstrução, denominados Extrair anotações.
@Extract public void of(IntRef value) { value.set(this.value); }
O padrão de sequência facilita o processamento de seqüências de dados.
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"); };
Usando a biblioteca, você pode escrever da seguinte maneira.
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") );
Além disso, para simplificar o código, você pode usar as seguintes funções.
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") );
Como você pode ver, a correspondência de padrões é uma ferramenta poderosa que facilita a escrita de códigos. Usando expressões lambda, referências de método e derivação de tipos de parâmetros lambda, é possível emular as possibilidades de correspondência de padrões pelos próprios meios da linguagem.
O código fonte da biblioteca está aberto e disponível no github .