![Java: The Verbosity](https://images.ctfassets.net/7wjlcr9d5ii8/5vMFJPBCfzRe4CJ3Y1BS2Z/a345f1779f7927871bb4e025e8872eca/OIG2.jpg?w=1024&h=1024&fl=progressive&q=100&fm=jpg&fit=thumb&f=faces)
Java merupakan salah satu bahasa pemrograman mature yang masih bertahan dari era 90-an hingga saat ini. Bertahun-tahun Java masih menempati posisi top 3 bahasa pemrograman yang paling diminati. Perusahaan-perusahaan besar banyak mengandalkan bahasa pemrograman satu ini untuk berbagai platform mulai dari mobile android, web service hingga aplikasi desktop. Meskipun bahasa-bahasa baru juga bermunculan dengan beragam fitur tapi Java selalu bisa bertahan di atas hingga saat ini. Namun seiring berjalannya waktu bahasa ini mulai terlihat "ketinggalan" dibanding bahasa-bahasa pemrograman modern yang lebih simple. Gw pun merasa bahasa Java ini sedikit verbose untuk melakukan sebuah hal sederhana. Salah satu jokes menyebutkan "Java itu semakin ribet semakin bagus" ๐. Sebenarnya revolusi sudah dilakukan sejak Java 8, tapi tetap saja masih belum sesederhana bahasa-bahasa lainnya. Berikut ini gw rangkum beberapa fitur yang menurut gw cukup verbose di Java dibandingkan beberapa bahasa pemrograman lainnya yang pernah gw pakai.
Salah satu pattern dalam bahasa pemrograman untuk menghindari infamous NullPointerException adalah Null Object Pattern, contoh implementasinya adalah dengan Null-Safe Operator. Teknisnya dengan mengganti null menjadi sebuah default object, jadi saat menerima object null pada method berantai tidak akan terjebak dengan NullPointerException. Sebenarnya sejak Java 8 sudah ada Null-Safe Operator ini yaitu dengan Optional Chaining, yang diadopsi dari Optional milik library Goggle Guava. Namun penulisannya masih verbose dibandingkan Null-Safe Operator di "saudara mudanya", Groovy. Sebagai contoh pada kelas Pojo sebagai berikut:
Class City
public class City{
private Province province;
private String cityName;
public Province getProvince(){
return province;
}
public void setProvince(Province province){
this.province = province;
}
public String getCityName(){
return cityName;
}
public void setCityName(String cityName){
this.cityName = cityName;
}
}
Class Province
public class Province{
private String provinceName;
private Country country;
public String getProvinceName(){
return provinceName;
}
public void setProvinceName(String provinceName){
this.provinceName = provinceName;
}
public Country getCountry(){
return country;
}
public void setCountry(Country country){
this.country = country;
}
}
Class Country
public class Country{
private String countryName;
public String getCountryName(){
return countryName;
}
public void setCountryName(String countryName){
this.countryName = countryName;
}
}
Dari kelas-kelas di atas bisa dilihat bahwa City memiliki property Province dan Province memiliki property Country. Jadi kalau misalkan kita memanggil countryName dari data city, maka biasanya dilakukan seperti berikut:
City city = new City();
System.out.println(city.getProvince().getCountry().getCountryName());
Dari code di atas, maka akan menghasilkan NullPointerException karena method getProvince() akan mengembalikan null. Untuk menanganinya bisa dengan cara berikut:
City city = new City();
if(city != null){
if(city.getProvince() != null){
if(city.getProvince().getCountry() != null){
if(city.getProvince().getCountry().getCountryName() != null){
System.out.println(city.getProvince().getCountry().getCountryName());
}
}
}
}
Tapi dari cara di atas code-nya sangat verbose dan cenderung jadi susah dibaca, apalagi nanti kalau di dalam Country ada property lagi dengan object yang nullable, Null-Checking jadi makin panjang dong ๐. Untung di Java 8 bisa di-handle dengan Null-Safe Operator menggunakan Optional API.
Optional<City> city = Optional.of(new City());
city.map(City::getProvince)
.map(Province::getCountry)
.map(Country::getCountryName)
.ifPresent(System.out::println);
Dengan Optional, return objek dibungkus ke dalam Optional dan akan mengembalikan objek Optional.EMPTY jika isinya null, begitu seterusnya. Sudah lebih mudah dibaca kan? Masih lebih mending lah daripada Null-Checking sebelumnya. Walaupun udah agak mendingan tapi ini masih verbose code-nya. Sekarang bandingkan dengan Null-Safe Operator di Groovy.
def city = new City();
def countryName = city?.getProvince?.getCountry?.getCountryName;
if(countryName) println(countryName);
Lebih straight forward dan simple kan dibanding Java? Cukup menggunakan "?" untuk melakukan null-check. Malah Javascript yang ga ada "hubungan saudara" dengan Groovy yang mengadopsi cara ini di EcmaScript terbaru. Sebenarnya fitur ini pernah di-propose sebelum Java 8 release, tapi Java lebih memilih mengadopsi Optional dari Google Guava.
Nullish Coalescing ini ga jauh berbeda dengan Null-Safe Operator, bedanya Nullish Coalescing ini mengganti nilai Null menjadi "default value". Misalkan dari contoh sebelumnya kita ingin meng-assign "Unknown" pada variable countryName jika salah satu method mengembalikan value null. Contoh-nya seperti ini:
City city = new City();
String countryName = null;
if(city.getProvince() != null){
if(city.getProvince().getCountry() != null){
countryName = city.getProvince().getCountry().getCountryName();
}
}
System.out.println(countryName == null ? "Unknown" : countryName);
Dengan menggunakan cara old-school Null-Checking ditambah dengan Ternary Operator, code-nya masih sulit dibaca. Sebenarnya di Java 8 juga sudah ada Nullish Coalescing seperti ini, masih menggunakan Optional seperti berikut.
Optional<City> city = Optional.ofNullable(new City());
String countryName = city.map(City::getProvince)
.map(Province::getCountry)
.map(Country::getCountryName)
.orElse("Unknown");
System.out.println(countryName);
Masih agak mendingan sih, tapi mari kita lihat lagi Nullish Coalescing di Groovy dengan Elvis Operator:
def city = new City();
def countryName = city?.getProvince?.getCountry?.getCountryName?: "Unknown";
println(countryName);
Jauh lebih singkat lagi dan syntax-nya ga verbose kayak Java. Hanya dengan menambahkan "?:" sudah cukup bagi Groovy.
Ini sebenarnya if else biasa tapi dengan syntax yang lebih singkat. Ga terlalu berpengaruh besar sih terhadap development, tapi lumayan membantu untuk beberapa kasus, meski ini dinilai sulit dibaca. Kalau di Javascript bisa menggunakan symbol '&&'. Jika statement pertama bernilai true, dia akan mengeksekusi statement selanjutnya, jika statement pertama bernilai false, dia tidak akan mengeksekusi statement selanjutnya. Contohnya seperti berikut:
let str = "hello";
str === "hello" && console.log("str is hello");
// will print "str is hello" because 'str === "hello"' is true
Bisa kebalikannya dengan menggunakan symbol '||'. Jika statement pertama bernilai true dia tidak akan mengeksekusi statement selanjutnya, jika statement pertama bernilai false baru dia akan mengeksekusi statement selanjutnya. Contohnya seperti berikut:
let str = "hello";
str === "hello" || console.log("str is not hello");
// doesn't print anything becaues 'str === "hello"' is true
Sedangkan di Java saat ini bisa dilakukan dengan keyword if biasa.
String str = "hello";
if(str.equals("hello")) System.out.println("str is hello");
// will print "str is hello"
Ini cukup membantu saat melakukan refactor method. Misalkan kita punya sebuah method yang sudah dipakai di banyak tempat. Lalu kemudian method itu ada penambahan parameter, yang mana kalau itu diubah akan berpengaruh ke semua code yang memakai method tersebut. Solusinya saat ini di Java dengan melakukan overload. Jadi code yang sebelumnya menggunakan method tersebut dengan parameter yang lama tidak terganggu business code-nya. Misalnya awalnya kita punya method seperti ini:
public void doSomething(int i){
System.out.println("i = " + i);
}
Lalu ada perubahan dengan menambahkan sebuah parameter String, maka solusinya seperti ini:
public void doSomething(int i, String str){
System.out.println("i = " + i);
if(str != null) System.out.println("str = " + str);
}
public void doSomething(int i){
doSomething(i, null);
}
Kalau di Javascript kita bisa menambahkan default value pada parameter:
function doSomething(i, str = null){
console.log("i = " + i);
if(str) console.log("str = " + str);
}
Di Java untuk membuat immutable data model butuh beberapa set-up di kelas Pojo seperti berikut:
public class Country{
private final String countryName;
private final int countryCode;
public Country(String countryName, int countryCode){
this.countryName = countryName;
this.countryCode = countryCode;
}
public String getCountryName(){
return countryName;
}
public int getCountryCode(){
return countryCode;
}
}
Country country = new Country("Indonesia", 62);
Dapat dilihat bahwa kita perlu menambahkan keyword 'final' pada masing-masing field dan butuh sebuah constructor yang memiliki parameter semua final field. Kita juga harus mengingat urutan parameter di constructor-nya saat pembuatan objek. Mending kalau property-nya dikit, kalau banyak bisa salah-salah urutannya. Solusinya bisa dengan menggunakan Builder Pattern.
public class Country{
private final String countryName;
private final int countryCode;
public Country(String countryName, int countryCode){
this.countryName = countryName;
this.countryCode = countryCode;
}
public static CountryBuilder builder(){
return new CountryBuilder();
}
public String getCountryName(){
return this.countryName;
}
public int getCountryCode(){
return this.countryCode;
}
public static class CountryBuilder{
private String countryName;
private int countryCode;
CountryBuilder(){
}
public Country.CountryBuilder countryName(String countryName){
this.countryName = countryName;
return this;
}
public Country.CountryBuilder countryCode(int countryCode){
this.countryCode = countryCode;
return this;
}
public Country build(){
return new Country(countryName, countryCode);
}
}
}
Country country = Country.builder().countryCode(62).countryName("Indonesia").build();
Dengan begini kita bisa meng-assign masing-masing property tanpa harus tau urutannya dan lebih enak dibaca. Tapi muncul masalah baru, code-nya jadi makin verbose, sebelumnya aja udah verbose. Sekarang coba lihat immutable data model di bahasa lain, Groovy misalnya:
@Immutable
class Country{
def countryName;
int countryCode;
}
def country = new Country(countryCode: 62, countryName: "Indonesia");
Lebih singkat dan padat tanpa perlu bikin builder. Sekarang lihat Javascript:
const country = {
countryName: "Indonesia",
countryCode: 62
};
Object.freeze(country);
Untuk urusan ini Javascript terlihat lebih keren karena Javascript properties-nya sangat fleksibel. Deklarasi object literal seperti ini kalau di Java mirip seperti Map. Namun, untuk beberapa case deklarasi objek seperti ini sulit di-maintenance. Overall Groovy yang lebih baik sih, IMHO. Sedangkan untuk Java sendiri bisa menggunakan library semacam Lombok untuk mengurangi boilerplate saat pembuatan builder tadi. Hasilnya jadi seperti ini:
@Builder
@Value
public class Country{
String countryName;
int countryCode;
}
Country country = Country.builder().countryCode(62).countryName("Indonesia").build();
Dalam hal ini Java bisa lebih baik dari sebelumnya, thanks to Project Lombok ๐. Sebenarnya setelah di-compile hasilnya sama seperti code sebelumnya, karena saat di-compile Lombok bakal men-generate code sesuai annotation yang digunakan. Annotasi @Builder akan men-generate Inner Class Builder, dan annotasi @Value akan men-generate keyword private final pada field beserta getter, constructor, toString, equals dan hashcode method. Lumayan membantu untuk mempermudah development. Nampaknya Java harus mengadopsi Lombok ini agar bisa digunakan secara native tanpa harus pasang library dulu ๐.
Terlepas dari verbosity-nya, nyatanya hingga sekarang Java masih menjadi salah satu bahasa favorit dalam pembuatan berbagai aplikasi berbagai platform. Platform Independence masih menjadi kunci sukses Java hingga sekarang. Perusahaan-perusahaan terutama perbankan rata-rata masih bergantung pada Java. Beberapa tahun terakhir sejak Java 8 dirilis hingga sekarang sudah Java 14, Java juga sering merilis versi terbaru dan melakukan lebih banyak improvement. Setidaknya Java masih worth it hingga beberapa tahun ke depan. Oh ya, untuk pembahasan mengenai Optional juga bisa dibaca pada tulisan Jebakan Optional