Java Opsional sebagai Jenis Pengembalian

1. Perkenalan

The Opsional jenis diperkenalkan di Jawa 8. Ini menyediakan cara yang jelas dan eksplisit untuk menyampaikan pesan bahwa mungkin tidak ada nilai, tanpa menggunakan nol .

Saat mendapatkan jenis pengembalian Opsional , kami cenderung memeriksa apakah nilainya hilang, yang mengarah ke lebih sedikit NullPointerException dalam aplikasi. Namun, tipe Opsional tidak cocok di semua tempat.

Meskipun kami dapat menggunakannya di mana pun kami mau, dalam tutorial ini, kami akan fokus pada beberapa praktik terbaik menggunakan Opsional sebagai tipe pengembalian.

2. Opsional sebagai Jenis Pengembalian

Sebuah pilihan jenis bisa menjadi tipe kembali untuk sebagian besar metode kecuali beberapa skenario dibahas nanti dalam tutorial.

Seringkali, mengembalikan Opsional tidak apa-apa:

public static Optional findUserByName(String name) { User user = usersByName.get(name); Optional opt = Optional.ofNullable(user); return opt; }

Ini berguna karena kita bisa menggunakan API Opsional dalam metode panggilan:

public static void changeUserName(String oldFirstName, String newFirstName) { findUserByFirstName(oldFirstName).ifPresent(user -> user.setFirstName(newFirstName)); }

Ini juga sesuai untuk metode statis atau metode utilitas untuk mengembalikan nilai Opsional . Namun, ada banyak situasi di mana kita tidak boleh mengembalikan tipe Opsional .

3. Kapan Tidak Kembali Opsional

Karena Opsional adalah pembungkus dan kelas berbasis nilai, ada beberapa operasi yang tidak dapat dilakukan terhadap objek Opsional . Sering kali, lebih baik mengembalikan tipe sebenarnya daripada tipe Opsional .

Secara umum, untuk pengambil di POJO, lebih cocok untuk mengembalikan tipe sebenarnya, bukan tipe Opsional . Secara khusus, penting bagi Entity Beans, Model Data, dan DTO untuk memiliki getter tradisional.

Kami akan memeriksa beberapa kasus penggunaan penting di bawah.

3.1. Serialisasi

Bayangkan kita memiliki entitas sederhana:

public class Sock implements Serializable { Integer size; Optional pair; // ... getters and setters }

Ini sebenarnya tidak akan berhasil sama sekali. Jika kami mencoba dan membuat serial ini, kami akan mendapatkan NotSerializableException :

new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(new Sock()); 

Dan sungguh, sementara serialisasi Opsional dapat bekerja dengan pustaka lain, itu pasti menambah kerumitan yang mungkin tidak perlu.

Mari kita lihat aplikasi lain dari ketidakcocokan serialisasi yang sama, kali ini dengan JSON.

3.2. JSON

Aplikasi modern mengonversi objek Java ke JSON sepanjang waktu. Jika pengambil mengembalikan tipe Opsional , kemungkinan besar kita akan melihat beberapa struktur data yang tidak terduga di JSON akhir.

Katakanlah kita memiliki kacang dengan properti opsional:

private String firstName; public Optional getFirstName() { return Optional.ofNullable(firstName); } public void setFirstName(String firstName) { this.firstName = firstName; }

Jadi, jika kita menggunakan Jackson untuk membuat serial instance Opsional , kita akan mendapatkan:

{"firstName":{"present":true}} 

Tapi, yang kami inginkan adalah:

{"firstName":"Baeldung"}

Jadi, Opsional adalah masalah untuk kasus penggunaan serialisasi. Selanjutnya, mari kita lihat sepupu serialisasi: menulis data ke database.

3.3. JPA

Di JPA, pengambil, penyetel, dan bidang harus memiliki perjanjian nama serta jenis. Misalnya, bidang firstName berjenis String harus dipasangkan dengan getter bernama getFirstName yang juga mengembalikan String.

Mengikuti konvensi ini membuat beberapa hal lebih sederhana, termasuk penggunaan refleksi oleh perpustakaan seperti Hibernate, untuk memberi kita dukungan pemetaan Object-Relational yang hebat.

Mari kita lihat kasus penggunaan yang sama dari nama depan opsional di POJO.

Namun kali ini, itu akan menjadi entitas JPA:

@Entity public class UserOptionalField implements Serializable { @Id private long userId; private Optional firstName; // ... getters and setters }

Dan mari kita lanjutkan dan coba pertahankan:

UserOptionalField user = new UserOptionalField(); user.setUserId(1l); user.setFirstName(Optional.of("Baeldung")); entityManager.persist(user);

Sayangnya, kami mengalami kesalahan:

Caused by: javax.persistence.PersistenceException: [PersistenceUnit: com.baeldung.optionalReturnType] Unable to build Hibernate SessionFactory at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1015) at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:941) at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56) at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79) at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54) at com.baeldung.optionalReturnType.PersistOptionalTypeExample.(PersistOptionalTypeExample.java:11) Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Optional, at table: UserOptionalField, for columns: [org.hibernate.mapping.Column(firstName)]

Kami bisa mencoba menyimpang dari standar ini. Misalnya, kita dapat mempertahankan properti sebagai String , tetapi mengubah getter:

@Column(nullable = true) private String firstName; public Optional getFirstName() { return Optional.ofNullable(firstName); }

Tampaknya kita bisa memiliki dua cara: memiliki tipe pengembalian Opsional untuk pengambil dan bidang yang dapat dipertahankan firstName .

Namun, sekarang karena kami tidak konsisten dengan pengambil, penyetel, dan bidang kami, akan lebih sulit untuk memanfaatkan default JPA dan alat kode sumber IDE.

Sampai JPA memiliki dukungan elegan tipe Opsional , kita harus tetap berpegang pada kode tradisional. Lebih sederhana dan lebih baik:

private String firstName; // ... traditional getter and setter

Akhirnya mari kita lihat bagaimana hal ini mempengaruhi front end - periksa untuk melihat apakah masalah yang kita hadapi terdengar familiar.

3.4. Bahasa Ekspresi

Mempersiapkan DTO untuk front-end menghadirkan kesulitan serupa.

Sebagai contoh, mari kita bayangkan bahwa kita menggunakan JSP templating untuk membaca firstName UserOptional DTO kita dari permintaan:

Karena ini Opsional , kita tidak akan melihat " Baeldung ". Sebagai gantinya, kita akan melihat representasi String dari tipe Opsional :

Optional[Baeldung] 

Dan ini bukan masalah hanya dengan JSP. Bahasa template apa pun, baik itu Velocity, Freemarker, atau yang lainnya, perlu menambahkan dukungan untuk ini. Sampai saat itu, mari kita terus menjaga DTO kita tetap sederhana.

4. Kesimpulan

Dalam tutorial ini, kita telah belajar bagaimana kita bisa mengembalikan objek Opsional , dan bagaimana menangani nilai pengembalian semacam ini.

Di sisi lain, kita juga telah mempelajari bahwa ada banyak skenario yang lebih baik kita tidak menggunakan tipe pengembalian Opsional untuk pengambil. Meskipun kita dapat menggunakan tipe Opsional sebagai petunjuk bahwa mungkin tidak ada nilai non-null, kita harus berhati-hati untuk tidak terlalu sering menggunakan tipe pengembalian Opsional , terutama dalam pengambil kacang entitas atau DTO.

Kode sumber dari contoh dalam tutorial ini dapat ditemukan di GitHub.