Pencocokan pola di Java 8

Banyak bahasa modern mendukung pencocokan pola di tingkat bahasa.



Bahasa Java tidak terkecuali. Dan Java 16 akan menambahkan dukungan untuk pencocokan pola untuk operator instanceof sebagai fitur akhir.



Semoga kedepannya pencocokan pola akan diperluas ke konstruksi bahasa lain.



Pencocokan pola memberi pengembang kemampuan untuk menulis kode dengan lebih fleksibel dan lebih cantik, sekaligus menjaganya tetap dapat dimengerti.



Tetapi bagaimana jika Anda tidak dapat beralih dari satu alasan atau lainnya ke versi baru Java. Untungnya, dengan menggunakan kapabilitas Java 8, Anda bisa mengimplementasikan beberapa kapabilitas pencocokan pola dalam bentuk pustaka.



Mari kita lihat beberapa pola dan bagaimana mereka dapat diimplementasikan menggunakan pustaka sederhana.



Pola konstanta memungkinkan Anda memeriksa kesetaraan dengan konstanta. Di Java, pernyataan switch memungkinkan Anda memeriksa persamaan angka, enumerasi, dan string. Tapi terkadang Anda ingin memeriksa kesetaraan konstanta objek menggunakan metode 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);
};

      
      





Kode serupa dapat ditulis sebagai berikut. Pada saat yang sama, di balik terpal, nilai dibandingkan dan diperiksa dalam pernyataan if. Anda dapat menggunakan formulir pernyataan dan ekspresi.



Juga sangat mudah untuk bekerja dengan rentang nilai.



import static org.kl.jpml.pattern.ConstantPattern.*;

matches(data).as(
      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)
);

matches(data).as(
      or(1, 2),    () ->  System.out.println("1 or 2"),
      in(3, 6),    () ->  System.out.println("between 3 and 6"),
      in(7),       () ->  System.out.println("7"),        
      Null.class,  () ->  System.out.println("Null value "),
      Else.class,  () ->  System.out.println("Default value: " + data)
);

      
      





Pola tupel memungkinkan Anda memeriksa kesetaraan beberapa variabel dengan konstanta pada saat yang bersamaan.



var (side, width) = border;

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 ");
};

for ((side, width) : listBorders) {
      System.out.println("border: " + [side + "," + width]); 	
}

      
      





Dalam hal ini, selain digunakan dalam bentuk sakelar, itu dapat diuraikan menjadi yang cocok atau diteruskan secara berurutan dalam satu lingkaran.



import static org.kl.jpml.pattern.TuplePattern.*;

let(border, (String side, int width) -> {
    System.out.println("border: " + side + "," + width);
});

matches(side, width).as(
      of("top",    25),  () -> System.out.println("top"),
      of("bottom", 30),  () -> System.out.println("bottom"),
      of("left",   15,  () -> System.out.println("left"),       
      of("right",  15),  () -> System.out.println("right"),         
      Null.class,    () -> System.out.println("Null value"),
      Else.class,    () -> System.out.println("Default value")
);

foreach(listBorders, (String side, int width) -> {
     System.out.println("border: " + side + "," + width); 	
}

      
      





Pola pengujian tipe memungkinkan Anda untuk mencocokkan tipe dan mengekstrak nilai variabel secara bersamaan.



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);
};

      
      





Di Java, untuk ini kita perlu memeriksa jenisnya terlebih dahulu, mentransmisikan ke jenisnya dan kemudian menetapkannya ke variabel baru. Pola ini membuat kode Anda lebih mudah.



import static org.kl.jpml.pattern.VerifyPattern.matches;

matches(data).as(
      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); }
);

      
      





Pola pelindung memungkinkan Anda untuk mencocokkan jenis dan memeriksa kondisi pada saat yang bersamaan.



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);
};

      
      





Desain serupa dapat diimplementasikan sebagai berikut. Untuk mempermudah penulisan kondisi, Anda dapat menggunakan fungsi perbandingan berikut: lessThan / lt, lebih besarThan / gt, lessThanOrEqual / le, lebih besarThanOrEqual / ge, equal / eq, notEqual / ne. Dan untuk menghilangkan ketentuannya, Anda dapat mengubah: selalu / ya, tidak pernah / tidak.



import static org.kl.jpml.pattern.GuardPattern.matches;

matches(data).as(           
      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); }
);

matches(data).as(           
      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); }
);

      
      





Pola dekonstruksi memungkinkan Anda memetakan tipe secara bersamaan dan mendekomposisi objek menjadi komponennya.



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));
}

      
      





Di Java, untuk ini, pertama-tama kita perlu memeriksa tipe, mentransmisikan ke tipe, menetapkannya ke variabel baru, dan baru kemudian mengakses bidang kelas melalui getter.



import static org.kl.jpml.pattern.DeconstructPattern.*;

Figure figure = new Rectangle();

let(figure, (int w, int h) -> {
      System.out.println("border: " + w + " " + h));
});

matches(figure).as(
      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));
});

      
      





Selain itu, untuk mendapatkan komponen, kelas harus memiliki satu atau lebih metode dekonstruksi. Metode ini harus diberi anotasi Ekstrak...

Semua parameter harus terbuka. Karena primitif tidak bisa diteruskan ke metode dengan referensi, Anda perlu menggunakan pembungkus untuk primitif IntRef, FloatRef, dll.



Untuk mengurangi overhead menggunakan refleksi, caching dan trik digunakan dengan kelas LambdaMetafactory standar.



@Extract
public void deconstruct(IntRef width, IntRef height) {
      width.set(this.width);
      height.set(this.height);
 }

      
      





Pola properti memungkinkan Anda untuk secara bersamaan mencocokkan tipe dan mengakses bidang kelas dengan namanya.



let (w: int w, h:int h) = figure;
 
switch (figure) {
      case Rectangle(w: int w == 5,  h: int h == 10) -> out.println("sqr: " + (w * h));
      case Rectangle(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));
}

      
      





Ini adalah bentuk pola dekonstruksi yang disederhanakan, di mana Anda hanya perlu mendekonstruksi bidang kelas tertentu.



Untuk mengurangi overhead menggunakan refleksi, caching dan trik digunakan dengan kelas LambdaMetafactory standar.



import static org.kl.jpml.pattern.PropertyPattern.*;  

Figure figure = new Rectangle();

let(figure, of("w", "h"), (int w, int h) -> {
      System.out.println("border: " + w + " " + h));
});

matches(figure).as(
      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));
});

      
      





Anda juga dapat menggunakan metode lain dengan referensi metode untuk menyederhanakan penamaan bidang.



Figure figure = new Rect();

let(figure, Rect::w, Rect::h, (int w, int h) -> {
      System.out.println("border: " + w + " " + h));
});

matches(figure).as(
      Rect.class,    Rect::w, Rect::h, (int w, int h) -> System.out.println("sqr: " + (w * h)),
      Circle.class,  Circle::r, (int r)  -> System.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));
});

      
      







Pola posisi memungkinkan Anda untuk secara bersamaan mencocokkan tipe dan memeriksa nilai bidang dalam urutan deklarasi.



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);
};

      
      





Di Java, untuk ini, pertama-tama kita perlu memeriksa tipe, mentransmisikan ke tipe, menetapkannya ke variabel baru, dan baru kemudian mengakses bidang kelas melalui getter dan memeriksa kesetaraan.

Untuk mengurangi overhead menggunakan refleksi, digunakan caching.



import static org.kl.jpml.pattern.PositionPattern.*;

matches(data).as(           
      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); }
);

      
      





Selain itu, jika pengembang tidak ingin memvalidasi beberapa bidang, bidang ini harus ditandai dengan anotasi Mengecualikan... Bidang ini harus dideklarasikan terakhir.



class Circle {
      private int radius;
      	  
      @Exclude
      private int temp;
 }

      
      





Pola statis memungkinkan Anda mencocokkan tipe dan mendekonstruksi objek secara bersamaan menggunakan metode pabrik.



 
switch (some) {
      case Result.value(var v) -> System.out.println("value: " + v)
      case Result.error(var e) -> System.out.println("error: " + e)
      default                    -> System.out.println("Default value")
};

      
      





Mirip dengan pola dekonstruksi, tetapi nama metode dekonstruksi yang dianotasi Ekstrakharus ditentukan secara eksplisit.



Untuk mengurangi overhead menggunakan refleksi, caching dan trik digunakan dengan kelas LambdaMetafactory standar.



import static org.kl.jpml.pattern.StaticPattern.*;

matches(figure).as(
      Result.class, of("value"), (var v) -> System.out.println("value: " + v),
      Result.class, of("error"), (var e) -> System.out.println("error: " + e),
      Else.class, () -> System.out.println("Default value")
); 

      
      





Pola urutan memudahkan untuk memproses urutan data.



List<Integer> list = ...;
  
switch (list) {
      case empty()     -> System.out.println("Empty value")
      case head(var h) -> System.out.println("list head: " + h)
      case tail(var t) -> System.out.println("list tail: " + t)         
      default          -> System.out.println("Default value")
};

      
      





Menggunakan metode pustaka, Anda cukup bekerja dengan urutan data.



import static org.kl.jpml.pattern.SequencePattern.*;

List<Integer> list = List.of(1, 2, 3);

matches(figure).as(
      empty() ()      -> System.out.println("Empty value"),
      head(), (var h) -> System.out.println("list head: " + h),
      tail(), (var t) -> System.out.println("list tail: " + t),      
      Else.class, ()  -> System.out.println("Default value")
);   

      
      





Selain itu, untuk menyederhanakan kode, Anda dapat menggunakan fungsi berikut, yang dapat dilihat dalam bahasa modern sebagai fitur atau fungsi bahasa.



import static org.kl.jpml.pattern.CommonPattern.*;

var rect = lazy(Rectangle::new);
var result = elvis(rect.get(), 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")
);
   
repeat(3, () -> {
   System.out.println("three time");
)
   
int even = self(number).takeIf(it -> it % 2 == 0);
int odd  = self(number).takeUnless(it -> it % 2 == 0);

      
      





Seperti yang Anda lihat, pencocokan pola adalah alat yang ampuh yang membuat penulisan kode jauh lebih mudah. Dengan menggunakan kapabilitas Java 8, Anda dapat meniru kapabilitas pencocokan pola melalui bahasa tersebut.



Kode sumber perpustakaan dapat dilihat di github: link . Saya akan dengan senang hati menerima umpan balik dan saran untuk perbaikan.



All Articles