![Java: Easter Eggs (Things You Might Not Know)](https://images.ctfassets.net/7wjlcr9d5ii8/4VhtZEWDGMenOiOp1mJFkv/bc1460953634d2ae122797f46a4e21df/surprise.png?w=1024&h=768&q=100&fm=png&fit=thumb&f=faces)
Selama 3 tahun gw kerja di bidang Engineering spesialis Java, ada beberapa hal yang menurut gw sederhana tapi ga semua orang tahu tentang ini, termasuk gw dulunya. Beberapa hal diantaranya gw temui berdasarkan pengalaman pribadi dan sebagiannya lagi gw temui lagi setelah iseng wara-wiri di internet. Ya namanya Engineering kan kerja sambil belajar, pasti ada aja hal-hal baru yang didapat. Langsung aja deh berikut beberapa hal yang menurut gw mungkin ga banyak diketahui orang-orang:
Apa return dari object berikut?
Object object;
if(true){
object = 1;
} else {
object = 1.0;
}
Sudah pasti 1 kan. Lalu apa value dari object berikut dengan logic yang sama menggunakan ternary operator?
Object object = true ? 1 : 1.0;
Salah, bagi yang nebak 1, value-nya adalah 1.0 karena Ternary Operator itu ga hanya sekedar if else aja, tapi juga mengimplementasi Numeric Type Promotion, dimana dalam hal ini Integer akan dipromosikan secara implisit jadi Double. Makanya hasilnya 1.0
Lanjut nih, ini dari pengalaman pribadi gw sendiri. Kira-kira apa value dari Integer i berikut?
Integer integer = null;
Integer i;
if(true){
i = integer;
} else {
i = 1;
}
Sudah pasti null. Lalu apa value dari Integer i melalui Ternary Operator berikut dengan menggunakan logic yang sama?
Integer integer = null;
Integer i = true ? integer : 1;
Apakah null? Bukan. Apakah 1? Juga bukan. Logic di atas akan menghasilkan NullPointerException. Karena saat melakukan Ternary Operator, Java akan melakukan Unboxing Numeric Object menjadi Primitive Type terlebih dahulu. Karena null tidak bisa di-unboxing jadi Primitive Type, makanya jadi NullPointerException
Kali ini tentang fitur unggulan sejak Java 8, yaitu Stream. Misalkan kita ingin melakukan looping melalui stream seperti berikut:
Stream<Integer> integerStream = Stream.of(1, 2, 3);
integerStream.filter(num -> num > 1).forEach(System.out::println);
Code di atas berjalan sesuai semestingnya. Bagaimana kalau kita menggunakan integerStream tersebut 2×?
Stream<Integer> integerStream = Stream.of(1, 2, 3);
integerStream.filter(num -> num > 1).forEach(System.out::println);
integerStream.filter(num -> num > 1).forEach(System.out::println);
Yup, code tersebut akan menghasilkan IllegalStateException, karena method forEach() adalah terminal operation, dan terminal operation hanya bisa dieksekusi sekali tiap stream. Contoh method terminal operation lainnya pada stream adalah collect(), reduce(), allMatch(), anyMatch(), noneMatch(), count(), sum(), min(), max(), dan beberapa lainnya. Setelah melakukan eksekusi pada salah satu terminal operation method tersebut, stream akan menutup segala operasinya dan object stream tersebut tidak reusable lagi untuk operasi apapun.
Coba perhatikan code berikut:
private static void get(int... numbers){
//
}
private static void get(int i, int... numbers){
//
}
Code tersebut tidak akan masalah saat di-compile. Tapi saat salah satunya dipanggil seperti ini:
get(1);
get(1,2,3);
Code tersebut tidak akan bisa di-compile karena terjadi ambigu, tidak jelas method mana yang akan dieksekusi.
Sekarang coba compile code berikut:
private static void get(){
class Person{
private int age;
}
Person person = new Person();
person.age = 1;
}
Pada code di atas, kita menulis sebuah class di dalam sebuah method. Apakah code di atas bisa dijalankan? Bisa dong. Kalau bikin anonymous class saat bikin concrete object dari sebuah abstract mungkin udah banyak yang tahu, terutama bagi yang pernah ngoding pake Java di bawah Java 8, mungkin udah biasa. Tapi ini mungkin kayaknya banyak banget yang belum tahu, kalau kita sebenarnya juga bisa bikin class di dalam method.
Dari operasi berikut, kira-kira apa value dari variable three?:
short one = 1;
short two = 2;
short three = one + two;
Apakah 3? Bukan. Code di atas akan menghasilkan compile error. Jawabannya sama kayak point nomor 1, disebabkan oleh Numeric Type Promotion. Ketika meng-assign sebuah angka real ke sebuah variable (short, int, byte, char), Java secara implisit mengkonversinya menjadi Integer. Makanya variable one dan two dianggap sebuah Integer dan hasilnya tidak bisa di-assign langsung ke variable three.
Static block digunakan untuk mengeksekusi code tertentu secara statis saat Class pertama kali digunakan. Static block mirip dengan constructor, bedanya eksekusinya hanya sekali. Contoh:
public class HaloUtils{
static{
System.out.println("Hi");
}
public static final String halo = "halo";
public static String hola = "hola";
}
Sekarang apa yang terjadi saat code berikut di-compile?
String hola = HaloUtils.hola;
Hasilnya adalah muncul “Hi” di console. Lalu bagaimana kalau code sebelumnya diubah menjadi code berikut dan di-compile?
HaloUtils hola = new HaloUtils();
Sama, hasilnya juga muncul “Hi” di console. Lanjut ganti code sebelumnya ke code berikut, apa yang terjadi saat di-compile?
String hola = HaloUtils.halo;
Disini, “Hi” tidak akan muncul di console. Static block tidak dieksekusi ketika yang pertama kali diakses adalah variable Constant, yang ada keyword “static final”.
Silakan paste code berikut di IDE:
if(true) \u007b
System.out.println("Hi");
\u007d
Pasti bakal error kan? Abaikan saja, lanjut save dan compile. Tidak compile error kan? Code berjalan seperti seharusnya. Jadi, Java itu bisa meng-compile code dari unicode escape. Pada code di atas “\u007b” artinya “{“ dan “\u007d” artinya “}”.
Para engineer mungkin sudah familiar dengan code berikut:
abstract class Windows{
public abstract int version();
}
class WindowsX extends Windows{
@Override
public int version(){
return 10;
}
}
Pada code di atas class WindowsX meng-override method version() dari ancestor-nya, Windows. Bagaimana kalau annotasi @Override ini dihapus, lalu compile dan jalankan? Code masih berjalan sesuai semestinya, karena annotasi @Override ini sebenarnya ga wajib, hanya untuk kebutuhan development doang. Fungsinya hanya sebagai penanda bagi developer bahwa method ini merupakan bawaan dari class ancestor-nya.
perhatikan code berikut ini:
int ax = 1;
double bx = 2.2;
ax += bx;
bx += ax;
Apakah code di atas akan menghasilkan error karena menjumlahkan Integer dengan Double? Kalau tidak, kira-kira berapa value akhir dari ax dan bx? Jawabannya ax = 3 dan bx = 5.2. Udah pada tau dong maksud dari ax += bx artinya nilai ax akan ditambah jadi nilai bx dan di-assign ke nilai ax yang baru. Lanjut lagi, sekarang perhatikan code berikut:
int ax = 1;
double bx = 2.2;
ax = ax + bx;
bx = ax + bx;
Sekarang berapa nilai ax dan bx? Apakah sama kayak code sebelumnya? Tidak, karena compile error. Jadi sebenarnya terdapat sedikit perbedaan antara ax += bx dengan ax = ax + bx walaupun kelihatannya persis dan hasilnya biasanya sama. Pada code sebelumnya, ax += bx tidak compile error karena secara implisit Java akan melakukan Cast pada value bx sebelum melakukan operasi dengan value ax. Behind the Scene, yang sebenarnya terjadi pada code pertama kira-kira seperti berikut:
ax = (int)((ax) + (bx));
bx = (double)((bx) + (ax));
Makanya hasil akhir dari ax pada code sebelumnya adalah 3, bukan 3.2 karena udah di-cast ke integer secara implisit sesuai data type yang akan di-assign. Sama halnya dengan bx yang hasil akhirnya 5.2 bukan 5, karena di-cast sesuai data type-nya bx, yaitu double saat melakukan assignment. Sedangkan bx = ax + bx tidak compile error karena Numeric Type Promotion, int dipromosikan secara otomatis menjadi double.
Segini dulu deh tulisannya. 10 juga udah lumayan banyak. Udah ngantuk gw, ini ngetiknya udah tengah malam. Lain kali mungkin bakal gw update lagi. Semoga bermanfaat and happy engineering.