Khusus interface dengan hanya satu method abstract atau yang dikenal dengan Functional Interface, kita bisa bikin implementasi dari interface tanpa perlu bikin class baru. Kita medeklarasikan method seperti sebuah variable. Action dalam method tersebut sifatnya lazy, ga akan langsung tereksekusi sebelum method dari interface tersebut dipanggil. Ada beberapa cara menulis implementasi dari Functional Interface pada Java selain bikin class baru secara terpisah. Diantaranya adalah Anonymous Class, Lambda Function, dan Method Reference.
Functional Interface
Sebelum lebih jauh, kita perlu mengenali Functional Interface. Functional Interface adalah interface yang memiliki satu abstract method. Meskipun ada beberapa default method atau static method, itu juga dihitung sebagai Functional Interface. Yang penting abstract method-nya cuma satu. Ini biasa ditemukan pada API Stream dan Optional. Kita bisa bikin Functional Interface, atau menggunakan Functional Interface yang sudah ada di package Java. Diantaranya yang paling sering digunakan sebagai berikut:
Interface | Karakteristik |
---|---|
Consumer<T> |
Memiliki satu argument tanpa return type |
BiConsumer<T,U> |
Memiliki dua argument tanpa return type |
Function<T,R> |
Memiliki satu argument dan return type |
BiFunction<T,U,R> |
Memiliki dua argument dan return type |
UnaryOperator<T> |
Memiliki satu argument dan return type yang sama dengan argument |
BinaryOperator<T> |
Memiliki dua argument yang sama dan return type yang sama dengan argument |
Predicate<T> |
Memiliki satu argument dan return boolean |
BiPredicate<T,U> |
Memiliki dua argument dan return boolean |
Supplier<T> |
Tanpa argument dengan return type |
Runnable |
Tanpa argument dan tanpa return type |
Dan beberapa varian lainnya yang spesifik untuk tipe data tertentu seperti IntConsumer, DoubleFunction, dll. Selain itu kita juga bisa membuat Functional Interface sendiri asalkan memenuhi syarat yaitu memiliki hanya satu abstract method dan ditambah anotasi @FunctionalInterface
di interface. Anotasi tersebut optional, fungsinya untuk memastikan bahwa ini hanya mengandung satu abstract method sehingga jika suatu saat ada yang menambahkan abstract method lain di interface tersebut maka akan compile error.
Anonymous Class
Cara pertama menulis implementasi dari Functional Interface adalah menggunakan Anonymous Class dengan menulis “new” keyword beserta nama interface dan method yang di-override. Contohnya seperti ini:
Runnable anonym = new Runnable(){
@Override
public void run(){
System.out.println("executed");
}
};
CompletableFuture.runAsync(anonym);
Pada code di atas, kita membuat variable function untuk runAsync
yang menerima parameter Runnable menggunakan Anonymous Class. Kekurangan dari cara ini adalah code jadi verbose dan susah dibaca. Jumlah line jadi panjang dan susah di-maintain. Selain itu, menggunakan Anonymous Class tidak di-optimize oleh JVM sehingga performanya lebih jelek karena akan membuat class file saat runtime😓.
Lambda Function
Bagi yang berpengalaman ngoding pakai Javascript atau Typescript mungkin lebih familiar dengan ini karena mirip dengan Arrow Function. Bedanya, di sini menggunakan operator ->
. Ini pendekatannya lebih ke arah Functional Programming. Cara kerjanya mirip seperti Anonymous Class namun lebih simple, readable, dan di-optimize oleh compiler karena menggunakan mekanisme invokedynamic sehingga penggunaan memory lebih kecil. Ada beberapa cara menggunakan Lambda Function:
Lambda Function: No Argument
Untuk function tanpa argument, maka kita bisa menulisnya dengan symbol () ->
disertai isi function setelahnya. Oh ya, kalau Cuma satu line kita bisa menulisnya singkat tanpa method body {}
.
Runnable lambdaNoParam = () -> System.out.println("executed");
CompletableFuture.runAsync(lambdaNoParam);
Pada code di atas, kita membuat implementasi Runnable menggunakan Lambda Function sehingga lebih simple dibanding Anonymous Class.
Lambda Function: With Body
Jika functionnya lebih dari satu line, maka isi functionnya harus dibungkus dengan symbol {}
.
Runnable lambdaWithBody = () -> {
System.out.println("executed");
System.out.println("again");
};
CompletableFuture.runAsync(lambdaWithBody);
Pada code di atas kita membuat implementasi Runnable dengan Function Body karena lebih dari satu line. Oh ya, karena Runnable itu returnnya void, maka kita ga perlu return apa-apa di sini. Misalkan kita menggunakan Functional Interface yang ada return type maka kita wajib tulis return di akhir statement di dalam body seperti method pada umumnya.
Lambda Function: With Argument
Untuk Lambda Function yang memiliki satu argument, kita bisa tulis dengan format arg -> action
.
IntConsumer lambdaWithParam = i -> System.out.println("i = " + i);
IntStream.of(1,2,3).forEach(lambdaWithParam);
Pada code di atas kita membuat function untuk forEach
method dari IntStream yang menerima IntConsumer sebagai parameter. IntConsumer adalah Functional Interface yang menerima int sebagai parameter. Kita bisa menulis implementasinya sesuai format menggunakan Lambda Function.
Lambda Function: Multiple Argument
Jika argument lebih dari satu, maka ditulis di dalam kurung (arg1, arg2) -> action
.
BiConsumer<String, Integer> lambdaTwoParam = (k, v) -> System.out.println("k = " + k + " v = " + v);
Map.of("satu", 1).forEach(lambdaTwoParam);
Pada code di atas kita membuat function untuk forEach
method dari Map yang menerima BiConsumer sebagai parameter. BiConsumer adalah Functional Interface yang menerima dua argument generic, yaitu sesuai tipe data key dan tipe data value dari Map pada kasus ini.
Lambda Function: With Return Value
Untuk Lambda Function yang memiliki return value, kita harus pastikan perintah yang dieksekusi return value yang sama dengan tipe data dari function di interface. Jika hanya satu line, ga perlu tulis return.
Supplier<RuntimeException> lambdaWithReturn = () -> new RuntimeException("empty");
Optional.empty().orElseThrow(lambdaWithReturn);
Pada code di atas kita membuat function untuk orElseThrow
dari Optional yang menerima Supplier sebagai parameter. Karena hanya satu line, kita ga perlu tulis return di akhir statement sehingga penulisannya jadi lebih simple.
Lambda Function: With Body and Return Value
Untuk Lambda Function yang memiliki return value dan lebih dari satu line, kita harus pastikan perintah yang dieksekusi return value yang sama dengan tipe data dari function di interface dan tulis return di akhir statement.
Supplier<RuntimeException> lambdaWithBodyReturn = () -> {
String x = "empty";
return new RuntimeException(x);
};
Optional.empty().orElseThrow(lambdaWithBodyReturn);
Karena lebih dari satu line, kita perlu tulis Function Body dan return di akhir statement.
Method Reference
Selain lewat Lambda Function, kita juga bisa pakai Method Reference. Ini adalah bentuk penulisan lain yang lebih simple dari Lambda Function, di mana kita hanya perlu tulis nama method aja jika actionnya berasal dari sebuah method. Tapi ada syaratnya untuk bisa menggunakan Method Reference. Parameter dan return typenya harus cocok. Misalkan jika Functional Interface tersebut memiliki parameter int dan return typenya void, maka method yang direferensikan untuk interface tersebut juga harus memilki parameter int dan return type void. Selain dari itu maka tidak bisa.
Method Reference: Static Method
Jika parameter dan return typenya cocok, maka kita hanya perlu tulis dengan format ClassName::methodName
. Misalkan kita ada class yang memiliki static method execute()
tanpa parameter dan return value.
Class MyClass
public class MyClass{
public static void execute(){
System.out.println("executed");
}
}
Contoh penggunaan
Runnable methodReferenceStatic = MyClass::execute;
CompletableFuture.runAsync(methodReferenceStatic);
Pada code di atas kita membuat function untuk method runAsync
dari CompletableFuture menggunakan Method Reference dari MyClass karena parameter Functional Interface Runnable tidak memiliki parameter dan return type. Begitu juga dengan static method execute
pada MyClass yang tidak memiliki parameter dan return type sehingga memenuhi syarat dan dapat ditulis menggunakan Method Reference untuk parameter Runnable. Hal yang sama juga berlaku untuk Functional Interface lainnya asalkan parameter dan return type cocok.
Method Reference: Instance Method
Selain dengan static method juga bisa dengan instance method asalkan parameter dan return type cocok. Formatnya variableName::methodName
.
Class AnotherClass
public class AnotherClass{
public void execute(){
System.out.println("executed");
}
}
Contoh penggunaan
AnotherClass anotherClass = new AnotherClass();
Runnable methodReferenceInstance = anotherClass::execute;
CompletableFuture.runAsync(methodReferenceInstance);
Pada contoh di atas kita dapat menggunakan Method Reference dari method objek variable anotherClass
karena Runnable tidak memiliki parameter dan return type sebagaimana method execute
dari objek anotherClass
. Bedanya dengan Static Method di sini kita mereferensikannya lewat nama objek, bukan nama class.
Method Reference: Instance Method this
Object
Jika yang direferenesikan adalah method dari object yang sama tempat function tersebut dibuat, maka format Method Reference ditulis this::methodName
.
public void instanceMethod(){
Runnable methodReference2 = this::execute;
CompletableFuture.runAsync(methodReference2);
IntConsumer myIntConsumer2 = this::intReference;
DoubleSupplier myDoubleSupplier2 = this::doubleReference;
}
public void execute(){
System.out.println("executed");
}
public void intReference(int i){
System.out.println("i = " + i);
}
public double doubleReference(){
return Double.min(1, 3);
}
Method Reference: Instance Method of Parameter Type
Jika argument-nya cuma satu dan kita ingin menggunakan instance method dari parameter tersebut, kita juga bisa menggunakannya sebagai Method Reference dengan format ClassName::methodName
. “className” di sini adalah nama class dari parameter type yang digunakan.
Consumer<AnotherClass> executeConsumer = AnotherClass::execute;
Optional.of(new AnotherClass()).ifPresent(executeConsumer);
Pada code di atas kita menuliskan function untuk ifPresent
dari Optional yang menerima Consumer sebagai parameter. Consumer adalah Functional Interface yang menerima parameter type dari value yang kita bungkus di Optional. Di sini kita membungkus objek AnotherClass ke dalam Optional sehingga parameter dari Consumer di dalam method ifPresent
adalah AnotherClass. Jadi saat mengeksekusi ifPresent
menggunakan method dari parameter tersebut kita bisa menulisnya menggunakan Method Reference.
Method Reference: Constructor Reference
Selain method, kita juga bisa menggunakan constructor dengan syarat yang sama. Formatnya ClassName::new
.
Class IntConstructor
public class IntConstructor{
private final int i;
public IntConstructor(int i){
this.i = i;
}
public void execute(){
System.out.println("i = " + i);
}
}
Contoh penggunaan
Optional<Integer> intOpt = Optional.of(1);
Function<Integer, IntConstructor> methodReferenceConstructor = IntConstructor::new;
Optional<IntConstructor> opt = intOpt.map(methodReferenceConstructor);
Pada code di atas kita memiliki class IntConstructor yang menerima int sebagai parameter untuk constructor. Kita juga memiliki objek Optional yang membungkus int lalu membungkusnya kembali ke dalam IntConstructor via method map
dari Optional. Method map
menerima Function yang memiliki parameter type sesuai value yang dibungkus dan return type sesuai value yang ingin dihasilkan. Dalam hal ini kita ingin menghasilkan objek IntConstructor. Karena constructor dari class IntConstructor memiliki parameter int sesuai value dari Optional yang kita buat maka kita bisa menggunakan Method Reference di sini.
Verdict
Itulah macam-macam Functional Interface dan berbagai cara membuat implementasinya mulai dari Anonymous Class, Lambda Function, hingga Method Reference. Penulisan menggunakan Anonymous Class sudah tidak dianjurkan lagi karena akan membuat class file baru saat runtime dan penggunaan memorinya tidak dioptimasi oleh JVM. Codenya juga jadi lebih verbose karena banyak karakter yang harus diketik. Lambda Function dan Method Reference lebih optimal dibanding Anonymous Class karena menggunakan mekanisme invokedynamic dan penggunaan memorinya lebih baik. Penulisannya juga lebih simple, apalagi jika hanya satu line. Best Practice yang gw tahu, Method Reference lebih dianjurkan jika memenuhi persyaratannya dibanding Lambda Function karena dinilai lebih readable. Jika tidak memungkinkan menggunakan Method Reference barulah gunakan Lambda Function.