4 poin oleh GN⁺ 2023-09-18 | 1 komentar | Bagikan ke WhatsApp
  • Java 21, yang dirilis pada 19 September 2023, membawa ekspresi pola fungsional yang lebih dekat ke Kotlin, Rust, dan C# ke dalam Java melalui record patterns dan switch pattern matching
  • Fondasi untuk menangani algebraic data types di Java 21 terbentuk dari akumulasi switch expressions di Java 14, records dan instanceof pattern matching di Java 16, serta sealed classes di Java 17
  • records membuat data lebih aman untuk diurai berkat batasan seperti final, referensi tak berubah, dan getter baku, sementara record pattern memungkinkan data bertingkat diambil langsung di dalam switch
  • sealed classes/interfaces hanya membuka subtipe yang diizinkan sehingga bisa membentuk model yang mendekati sum type; dengan menggabungkan sealed interface dan record, variasi seperti RGB, CMYK, YUV, dan HSL bisa dibatasi
  • switch di Java 21 juga mendukung case null dan guard when, tetapi accessor record yang salah atau exception saat guard dieksekusi dapat berujung pada java.lang.MatchException

Pattern matching yang distabilkan di Java 21

  • Java 21 dirilis pada 19 September 2023 dan mendukung record patterns di switch block dan switch expression
  • Sintaks ini dinilai sebagai titik balik yang memungkinkan Java mengekspresikan pola pemrograman fungsional dengan cara yang mirip Kotlin, Rust, dan C#
  • Perubahan sintaks utama di versi-versi Java terbaru berpuncak pada pattern matching di Java 21
    • Java 14: switch expressions distabilkan
    • Java 16: records, instanceof pattern matching distabilkan
    • Java 17: sealed classes distabilkan
    • Java 21: record patterns, switch pattern matching distabilkan
  • Kumpulan perubahan ini membuat Java mampu menangani algebraic data types dan pola penggunaan idiomatisnya, yang sebelumnya sulit diekspresikan

Konsep minimum yang dibutuhkan dari teori tipe

  • Untuk memahami fitur-fitur Java 21, ada beberapa konsep teori tipe yang perlu diketahui
  • bottom/empty type merepresentasikan himpunan nilai yang tidak dapat dihitung, dan dalam bahasa pemrograman umum biasanya berupa himpunan kosong
    • Nothing di Kotlin tidak bisa memiliki instance karena konstruktornya private
    • Void di Java memiliki konstruktor private, tetapi masih bisa memuat null, sehingga sulit dianggap sebagai bottom type sejati
    • void primitif di Java tidak bisa digunakan sebagai tipe variabel, sehingga dalam hal ini perilakunya lebih dekat
  • top type adalah himpunan universal yang mencakup semua nilai dari semua tipe
    • Di Kotlin, peran ini dipegang oleh Any
    • Object di Java sulit disamakan dengan top type di bahasa lain karena primitive terpisah dari model objek
  • unit type adalah tipe yang hanya memiliki satu nilai
    • void di Java bisa diperlakukan seperti unit type untuk nilai balik method, tetapi tidak bisa diteruskan sebagai tipe parameter
    • Unit di Kotlin didefinisikan sebagai object dan juga bisa digunakan sebagai parameter method
  • boolean type memiliki dua nilai, true dan false, dan meski bisa direpresentasikan sebagai nullable unit type, itu tidak praktis

Product type dan Java records

  • product type adalah tipe yang menggabungkan dua atau lebih tipe penyusun, dan jumlah tipe penyusunnya disebut arity atau degree
  • struct di C adalah contoh product type
    • Tipe penyusunnya bisa berulang seperti int, char *, double, int
    • Tipe yang berulang bisa dibedakan jika dipandang sebagai pasangan terurut bersama nama field
  • Tuple di Python atau Rust juga bisa dipandang sebagai product type; dalam kasus ini, indeks berperan sebagai nama komponen
  • record class yang distabilkan di Java 16 adalah contoh product type yang baik
    • Field pada record bersifat final, dan record tidak bisa diwariskan
    • State record ditetapkan saat pembuatan dan dipertahankan setelahnya
    • Namun, jika record berisi mutable data type, ketakterubahan isi datanya sendiri tidak otomatis terjamin
  • Java class biasa dapat mencampurkan state public/private, state tersembunyi akibat pewarisan, field mutable/static, dan getter nonstandar, sehingga komponennya sulit digeneralisasi
  • records menjamin struktur yang membuat fitur bahasa seperti pattern matching bisa bekerja dengan stabil melalui batasan berikut
    • record secara implisit adalah final class dan tidak bisa diwariskan
    • record tidak bisa extend class selain java.lang.Record
    • record component tidak bisa diberi visibility modifier
    • referensi component selalu final dan diperlakukan tak berubah
    • getter bawaan menggunakan nama field apa adanya, sehingga getter untuk field a adalah a()
    • backing field secara implisit bersifat private dan diakses melalui getter

Mengurai data bertingkat dengan record pattern

  • switch pattern di Java 21 mengurai nested record data tanpa perlu mengulang pemeriksaan instanceof dan cast eksplisit
  • Contohnya menggunakan record A(Record inner), record B(char b), dan record SomeOtherRecord()
    • Cara lama mengharuskan cast setelah if (r instanceof A), lalu mengulang instanceof dan cast lagi untuk nilai di dalamnya
    • switch pattern dapat langsung mengekstrak nilai bertingkat seperti case A(B(char a)) -> String.valueOf(a)
  • switch block memiliki struktur yang lebih jelas daripada if-else ladder dan cocok untuk mengambil data yang bersarang dalam dengan cepat
  • Untuk menjalankannya langsung di Java 21, letakkan kode di main.java dan gunakan perintah berikut
java --enable-preview --source 21 main.java

Sum type dan sealed classes/interfaces

  • Untuk mengekspresikan pilihan yang terbatas, Java enum bisa digunakan, tetapi representasi warna dengan struktur data berbeda seperti RGB, HSL, YUV, dan CMYK sulit ditangani hanya dengan enum
  • Dengan polimorfisme berbasis pewarisan, kita bisa membuat abstract class Color serta class RGB, CMYK, YUV, dan HSL, tetapi hierarki class biasa bersifat terbuka
    • Pengguna library bisa membuat class baru seperti RYB yang mewarisi Color
    • Jika API tidak dirancang untuk diperluas, variasi baru ini dapat menyebabkan crash atau bug halus di kode yang jauh dari sumbernya
  • sealed classes digunakan untuk mengekspresikan konsep sum type di Java
    • sum type adalah tipe yang pada suatu waktu hanya bisa menjadi salah satu dari komponennya
    • Ini juga disebut tagged union type
  • Dengan modifier sealed dan klausa permits, hanya class tertentu yang boleh mewarisi
public sealed class Color permits RGB, CMYK, YUV, HSL {
}
  • Dalam hierarki sealed class, pewaris langsung maupun tidak langsung harus memiliki salah satu dari sealed, non-sealed, atau final; jika tidak, akan terjadi compile error
    • sealed: hanya tipe yang disebut dalam permits yang bisa mewarisi
    • non-sealed: bisa diwarisi seperti class biasa
    • final: menjadi leaf pada pohon pewarisan dan tidak bisa diperluas lagi

Menggunakan sealed interface bersama record

  • Destructuring pada switch pattern bekerja untuk records, tetapi record tidak bisa mewarisi class selain Record
  • Solusinya adalah menggunakan sealed interface
    • sealed interface bekerja mirip dengan sealed class
    • record dan enum juga bisa implement sealed interface
  • Dalam contoh, Color dibuat sebagai sealed interface dan RGB, CMYK, YUV, serta HSL diimplementasikan sebagai record
public sealed interface Color permits RGB, CMYK, YUV, HSL {
    String getDescription();
}

record RGB(int red, int green, int blue) implements Color {
    public String getDescription() {
        return "RGB Color: (" + red + ", " + green + ", " + blue + ")";
    }
}
  • Setelah itu, nilai dari tiap record bisa langsung diekstrak di dalam switch
switch (color) {
  case RGB(int red, int green, int blue) -> {
  }
  case CMYK(double cyan, double magenta, double yellow, double black) -> {
  }
  case YUV(int y, int u, int v) -> {
  }
  case HSL hsl -> {
    System.out.println(hsl.getDescription());
  }
  case null -> {
    System.out.println("How did color become null?!");
  }
}
  • Java 21 memungkinkan penanganan case null di switch block dan expression, sehingga tidak perlu pemeriksaan null terpisah sebelum switch
  • Jika Color adalah sealed type, Java dapat mengetahui apakah semua case sudah ditangani, sehingga exhaustive switch dimungkinkan tanpa default case

Guard clause dan when

  • Java 21 mendukung guard clause untuk menambahkan kondisi tambahan pada switch arm
  • Guard clause mengintegrasikan kondisi ke dalam case label dengan keyword when
switch (color) {
  case RGB(int red, int green, int blue) when red > 200 -> {
    System.out.println("Very red.");
  }
  case RGB rgb when rgb.green > 100 -> {
    System.out.println("Sort of green...");
  }
  case RGB rgb -> {
    System.out.println("Not that red...");
  }
}
  • Sebelumnya, kita harus menambahkan lagi if (red > 200) di dalam isi case RGB(...)
  • Java mencocokkan case pertama yang bernilai true secara eager, jadi case yang lebih spesifik harus ditempatkan lebih dulu, lalu diikuti case yang kurang spesifik
  • Setelah RGB case dengan guard, case RGB rgb biasa tetap diperlukan untuk menjaga exhaustiveness

Kapan MatchException terjadi

  • Pattern matching di Java 21 juga melibatkan java.lang.MatchException
  • Jika record accessor melempar exception, switch pattern akan gagal dan dapat memunculkan MatchException
record R(int i) {
    public int i() {
        return i / 0;
    }
}

static void exampleAnR(R r) {
    switch(r) {
        case R(var i): System.out.println(i);
    }
}
  • Pada contoh di atas, accessor i() melempar ArithmeticException, sehingga switch block melempar MatchException
  • Menurut JEP 441, record accessor yang selalu melempar exception adalah kasus yang sangat tidak lazim, dan exhaustive pattern switch yang melempar MatchException juga sangat jarang terjadi
  • Bahkan pada exhaustive switch, exception bisa muncul jika tidak ada satu pun variant yang ditentukan cocok dengan selector
    • JEP 441 menjelaskan kegagalan pencocokan pada exhaustive switch untuk enum sebagai situasi ketika enum class berubah setelah switch dikompilasi
  • Exception saat guard clause dieksekusi juga dapat menyebabkan MatchException
static void example(Object obj) {
    switch (obj) {
        case R r when (r.i / 0 == 1): System.out.println("It's an R!");
        default: break;
    }
}

Cakupan yang tersisa

  • Dengan menggabungkan records, sealed types, switch pattern matching, dan guard clause di Java 21, building block pemrograman fungsional bisa diterapkan ke dalam kode Java
  • Beberapa topik seperti cara generics berinteraksi dengan switch patterns tidak dibahas
  • Tulisan berikutnya akan membahas quirks dan contoh praktis yang bisa digunakan untuk memperbaiki cara menulis kode Java

1 komentar

 
GN⁺ 2023-09-18
Komentar Hacker News
  • Fitur terbesar Java 21 adalah rilis virtual thread: https://openjdk.org/jeps/444
    Entah kenapa ini tidak disebutkan di artikel. Kalau ada fitur yang bisa menarik pengembang Go yang sudah ada ke Java, mungkin inilah dia, dan sepertinya juga bisa meyakinkan orang-orang yang tidak menyukai pola konkurensi bergaya reaktif

    • Sepertinya pengembang Go yang sudah ada tidak akan kembali ke Java. Saya bekerja dengan Java selama 10 tahun lalu pindah ke Go, dan tidak berniat kembali
      Aplikasi dan library Java terlalu sulit untuk dinalar dan dipahami dibandingkan Go, karena pewarisan, packaging, OOP, build tool, dan sebagainya
      Go itu sederhana serta mudah dipahami, dibaca, dan dipelihara. Packaging-nya mirip cara menata file dalam satu folder di komputer, dan tooling-nya juga terintegrasi ke dalam bahasa. Tidak terasa seperti baru cukup enak dipakai kalau ada IDE seperti IntelliJ
      Mungkin sekarang sudah berubah, tetapi sebagian besar library Java yang saya lihat belakangan ini masih tampak seperti itu
    • Karena Java 21, saya menantikan rilis JRuby berikutnya yang memasukkan virtual thread. Charles Nutter mendemokan dampaknya terhadap fiber Ruby dalam presentasi JRuby bulan Agustus, dan dampaknya cukup besar
      Ada banyak hal yang saya sukai dari JVM dan ekosistem tooling-nya, tetapi menulis kode Java sekarang kurang menarik bagi saya. JRuby memberi sebagian keunggulan dari kedua sisi
      Presentasinya ada di sini, dan demo virtual thread muncul sekitar menit ke-45
      https://youtu.be/pzm6I4liJlg?si=GtxQ4MThEaNDfC67
    • Saya tidak begitu yakin pengembang Golang yang sudah ada akan pindah ke Java karena fitur ini. Namun dari sisi arah, saya penasaran mengapa komunitas Go punya begitu sedikit container konkurensi, sementara komunitas Java punya banyak
      Bahkan sync.Map pun bukan sesuatu seperti ConcurrentMap serbaguna milik Java, melainkan dikhususkan untuk dua kasus penggunaan tertentu. Java memiliki set konkurensi, queue, barrier, phaser, fork-join pool, dan sebagainya. Meski ada goroutine, container seperti ini tetap cukup berguna; setidaknya fork-join bukan implementasi yang sesederhana itu. Memakai mutex di mana-mana terasa terlalu level rendah
      Saya tahu ada implementasi pihak ketiga, tetapi konkurensi terlalu sulit dibuat dengan benar, jadi saya ragu mengadopsi paket pihak ketiga kecuali sudah pada tingkat matang dengan banyak pengguna dan pengembang di belakangnya, seperti JCTools milik Java atau Google Guava
    • Kontras antara Executor.newVirtualThreadPerTaskExecutor dan go menunjukkan dengan baik alasan utama mengapa pengembang Go tidak akan pindah ke Java
      Koreksinya, sebenarnya lebih mendekati try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(...) } ketimbang go
    • Java 21 juga menyediakan structured concurrency sebagai pratinjau (https://openjdk.org/jeps/453). Ini memakai implementasi virtual thread, dan hanya dari contohnya saja terlihat cukup bagus serta mengurangi banyak rasa sakit saat menangani konkurensi berbasis thread
  • Menurut saya judul posting blog itu kurang tepat. Subjudul tersembunyinya adalah "Algebraic data types in Java", dan itu jauh lebih sesuai untuk menjelaskan isinya. Judul yang lebih baik mungkin Algebraic data types in Java 21
    Mungkin karena judulnya, cukup banyak komentar di sini keluar dari topik. Saya ingin melihat lebih banyak pembahasan tentang algebraic data type, kelebihan dan kekurangan implementasi Java, serta perbandingan teknis dengan bahasa lain

    • Saya berharap ada bahasa yang lebih populer dengan algebraic data type, tetapi saya tidak yakin benar-benar ingin melihat algebraic data type masuk ke Java
      Kode Java yang sudah ada tidak akan hilang, jadi kalau kode seperti ini tercampur secara acak, saya bertanya-tanya apakah itu benar-benar akan membuatnya lebih baik
    • Awalnya saya memang memberi judul seperti itu, tetapi saya mengubahnya pada saat terakhir, dan hasilnya sepertinya membuat arah artikel meleset
  • Fitur sealed classes yang dijelaskan di sini terasa benar-benar keliru
    Logikanya, kalau ada interface biasa, siapa pun bisa membuat class baru yang mengimplementasikannya, dan kode seperti if (x instanceof Foo) { ... } else if (x instanceof Bar) { ... } akan rusak saat runtime jika seseorang menambahkan class baru. Jadi dengan fitur sealed interface baru, jika tidak ada yang bisa membuat class baru yang mengimplementasikan interface itu, pernyataan if tidak akan rusak
    Tapi bukankah pemrograman berorientasi objek sudah memikirkan dan menyelesaikan masalah seperti ini? Saya tahu OOP sekarang sudah tidak sepopuler dulu, tetapi Java adalah bahasa berorientasi objek
    Solusinya adalah menambahkan method ke interface dan membuat semua class mengimplementasikannya. Dengan begitu, alih-alih mencantumkan semua pilihan dalam if/switch raksasa, cukup panggil method tersebut
    Cara ini lebih baik daripada mencegah kode diperluas; justru memungkinkan ekstensi. Implementasi baru hanya perlu mengimplementasikan method itu, dan karena compiler memaksanya, tidak mungkin terlewat tanpa sengaja
    Contoh ruang warna dalam artikel (RGB, CMYK, dll.) adalah contoh tandingan yang sangat bagus. Jika saya menulis kode yang memakai ruang warna, pengguna atau pelanggan mungkin perlu memakai ruang warna aneh dan langka yang tidak saya pikirkan. Saya tidak ingin membuat kode yang hanya mendukung ruang warna yang dicantumkan dalam if/switch raksasa, dan karena struktur seperti itu menjadi tidak bisa diperluas

    • Solusi menambahkan method ke interface menjadi bermasalah jika kita tidak bisa mengetahui sebelumnya semua operasi yang akan diperlukan di masa depan
      Sealed classes menyelesaikan masalah ini. Namun sebagai gantinya muncul masalah baru: "bagaimana jika kita membutuhkan lebih banyak class turunan dan tidak bisa mengetahui semuanya di awal?" Pada akhirnya, pertanyaannya adalah apakah ada cara untuk mencapai keduanya
      Masalah ini disebut expression problem [1]
      Ada bahasa bertipe statis yang bisa memecahkan expression problem, dan Java termasuk salah satunya [2]. Namun cara melakukannya di Java masih sangat rumit dan merepotkan sehingga hampir tidak digunakan. Jika ingin tetap berada di dunia Haskell atau JVM, Scala jauh lebih baik untuk hal ini
      [1] https://en.wikipedia.org/wiki/Expression_problem
      [2] https://koerbitz.me/posts/Solving-the-Expression-Problem-in-...
    • Pendekatan sealed classes juga memungkinkan siapa pun memperluas kode, tetapi perluasannya terjadi pada dimensi yang berbeda dari pendekatan method interface
      Method interface dan virtual call sangat tidak fleksibel ketika kita ingin menambahkan operasi baru, bukan class baru. Bahkan untuk menambahkan satu operasi baru saja, kita harus mendatangi semua implementasi dan menambahkan method baru, dan bisa merusak implementasi yang tidak kita punya aksesnya. Method-method yang tidak saling terkait juga harus didefinisikan dalam satu class, sehingga keterbacaan kode sangat memburuk, dan virtual call juga tidak gratis sehingga berdampak pada performa
      Dalam kasus ini, sealed class jauh lebih mudah diperluas. Cukup tambahkan switch baru di satu tempat, dan kompatibilitas ke belakang pun tidak rusak
      Inilah expression problem yang terkenal itu
      https://pkolaczk.github.io/in-defense-of-switch/
    • Saya memahami rekomendasi untuk memakai dispatch polimorfik alih-alih instanceof, dan saya juga pernah melihat Bob Martin membahasnya panjang lebar, tetapi saya tidak setuju
      Untuk melakukan dispatch polimorfik seperti ini, objek harus menangani sendiri banyak concern
      Dalam video game, Car bisa memiliki .render(), .collide(), .playSound(). Nanti ketika Dog ditambahkan, cukup implementasikan tiga method ini, dan Renderer, PhysicsEngine, SoundEngine tidak perlu diubah atau dikompilasi ulang. Programmer lain juga bisa menambahkan entitas tanpa memasukkan bug ke kode berharga saya. Kelihatannya bagus
      Namun sekarang Car dan Dog harus mengetahui semuanya tentang grafis, fisika, dan suara. Dan entitas tidak ada secara terisolasi. Mobil dan anjing harus dirender dalam urutan yang benar dan bisa saling menutupi. Tabrakan juga harus diperiksa satu sama lain. Seperti yang saya alami dalam game jam sungguhan, bisa juga terjadi situasi di mana orang yang menangani suara harus masuk ke dalam semua objek hanya untuk menambahkan perilaku suara
      Jauh lebih baik bekerja di dalam Physics.collideAll() saat memikirkan fisika dan melakukan penanganan khusus dengan instanceof jika perlu, lalu bekerja di dalam Graphics.renderAll() saat memikirkan grafis
      Dalam pengembangan web backend Java sehari-hari pun mirip. Saat menentukan cara mengubah objek Java menjadi respons HTTP di REST controller, lebih baik melihat semuanya dalam satu method dan memetakan {instanceof Forbidden} ke 403 serta {instanceof NotFound} ke 404. Saya tidak ingin memasukkan getCode() atau hal khusus REST ke dalam class Java itu sendiri
    • Mengizinkan ekstensi tidak selalu masuk akal. Ada alasan mengapa String bersifat final, bahkan bisa saja dianggap bahwa final seharusnya menjadi default dan hanya class yang memang mengizinkan subclassing yang harus dinyatakan secara eksplisit sebagai open
      Dalam pemrograman fungsional, contoh klasik sum type adalah list. Di sini hanya ada Element(T head, List tail) dan Nil(). Tidak ada alasan untuk memperluasnya, dan jika benar-benar diperluas, saat digabungkan dengan semua fungsi yang menangani list, hasilnya bisa menjadi kode yang salah
      Selain itu, visitor pattern yang mirip dengan pattern matching sangat verbose dan bergantung pada hack yang memanfaatkan semantik dispatch method biasa di Java. Dalam hal ini, menurut saya pattern matching berkali-kali lebih mudah dibaca
    • Fitur seperti ini memiliki kasus penggunaan yang valid
      Misalnya, kita bisa membayangkan interface keamanan yang memvalidasi token keamanan
      Jika itu interface biasa, mudah saja mengimplementasikannya untuk mengabaikan token (mengizinkan semuanya), mencuri token, atau memasukkan backdoor. Jika class seperti itu diinjeksikan ke tempat pemeriksaan keamanan dilakukan, keamanan bisa ditembus
      Dengan sealed interface, implementasi baru yang tidak disetujui tidak bisa ada. Jika kita menerima objek yang mengklaim mengimplementasikan interface itu, ada jaminan bahwa objek tersebut adalah salah satu implementasi terverifikasi yang benar-benar melakukan pemeriksaan keamanan. Dengan kata lain, satu kategori bug keamanan dan exploit telah dihapus seluruhnya
  • Ini tulisan yang bagus dari sudut pandang orang yang tahu sum type, tetapi belum begitu memahami sum type di Java
    Namun, saya tidak yakin apakah hanya dengan sum type orang akan kembali menyukai Java. Kemungkinan null yang luas masih tetap ada, dan dalam tulisan ini pun beberapa kali muncul

    • Di Java, kemungkinan null memang masalah besar, tetapi framework nullability berbasis anotasi efektif dan sudah tersebar di seluruh ekosistem. Secara pribadi, saya menganggapnya hampir wajib
      Saya sangat menantikan https://jspecify.dev/ yang diupayakan Google, Meta, Microsoft, dan lainnya untuk menstandarkan anotasi, dimulai dari @Nullable
    • Jika Valhalla masuk, nullability eksplisit juga akan hadir, jadi masalah itu pun akan tertangani
    • Java juga belum menjadi bahasa yang berorientasi ekspresi
  • Jawaban tulisan itu untuk “mengapa disebut product type?” tidak salah, tetapi jika dijelaskan lebih intuitif dan ringkas, jumlah total nilai yang mungkin pada product type adalah hasil perkalian jumlah nilai yang mungkin dari tipe-tipe penyusunnya
    Hal yang sama juga berlaku jika product diganti dengan sum
    Menariknya, jumlah total fungsi unik berbentuk a -> b, jika hanya dilihat dari input dan output, bisa dihitung sebagai eksponen. Yaitu (jumlah nilai yang mungkin dari b) ^ (jumlah nilai yang mungkin dari a)

    • Secara lebih intuitif dan ringkas, product type setara dengan hasil kali Kartesius dari himpunan
    • Map dan list juga merupakan contoh lain dari exponential type. Karena fungsi murni secara teoretis dapat digantikan dengan lookup map terhadap nilai yang sudah dihitung sebelumnya, intuisi bahwa hal-hal ini sama dengan fungsi terasa alami. Dalam konteks ini, list adalah map khusus yang kuncinya berupa bilangan bulat
      Jika ditulis secara matematis, pada list Bool, sisi kiri adalah jumlah elemen dan sisi kanan adalah jumlah seluruh kemungkinan
      0 : 1
      1 : 2
      2 : 4
      3 : 8
      4 : 16
      5 : 32
      Dan seterusnya
  • Saya menunggu Project Valhalla selesai sehingga akhirnya value type masuk ke Java. Dengan begitu, Java akan memiliki sum type, value type, dan coroutine, sehingga tampaknya akan menjadi salah satu bahasa yang cukup bagus

  • Java pada dasarnya bukan bahasa yang benar-benar buruk
    Masalahnya ada pada orang-orangnya. Rekayasa berlebihan yang masif, terlalu banyak konsep abstrak yang membuat codebase sulit dipahami, sihir kode dalam bentuk anotasi seperti pernyataan GOTO terbalik, serta framework DI adalah masalahnya
    Yang perlu diperbaiki bukan bahasanya, melainkan ekosistemnya. Di dalam ekosistem Java, perlu semacam gerakan “reformasi”
    Berpindah ke Kotlin, Clojure, atau Scala saja tidak cukup

    • Dalam bahasa apa pun, kita bisa membuat HammerFactoryFactory yang menghasilkan HammerFactory. Namun ekosistem Java memicu dan mendorong cara menyelesaikan masalah seperti ini. Menurut saya C# juga mirip
      Satu hal yang benar-benar dibutuhkan Java adalah fungsi independen, atau fungsi dengan namespace. Kadang kita tidak memerlukan class; fungsi di dalam modul atau namespace saja sudah cukup, tetapi entah mengapa itu tidak bisa dilakukan
  • Penulis menjelaskan mengapa Records dibutuhkan, dengan menunjukkan bahwa sebagian besar objek Java menjadikan semua field sebagai private dan hanya mengaksesnya melalui metode accessor baca/tulis
    Namun, tidak ada konvensi yang dipaksakan di tingkat bahasa untuk mendefinisikan accessor, sehingga meskipun getter untuk foo diberi nama getBar, itu tetap berfungsi, tetapi bisa membingungkan orang yang ingin mengakses bar
    Scala mendukung pattern matching untuk objek yang mengimplementasikan metode unapply. Apakah pendekatan ini dianggap berbahaya? Mengapa Java tidak mengikuti jalur ini?

    • Ini juga masalah standardisasi. Standardisasi Java lambat seperti C++. Jika melihat catatan kaki terakhir pada JEP record pattern, ada isyarat bahwa sesuatu seperti unapply mungkin sedang disiapkan, jadi harapan belum sepenuhnya hilang
  • Java selalu merupakan bahasa yang bagus. Yang membuat ingin muntah adalah ekosistem bergaya enterprise. Saya pernah melihat puluhan class dan interface digunakan hanya untuk mengimplementasikan satu baris logika