リターンタイプとしてのJavaオプション
1. 序章
オプションタイプはJava8で導入されました。 null を使用せずに、値がない可能性があるというメッセージを明確かつ明示的に伝える方法を提供します。
オプションの戻り型を取得する場合、値が欠落していないかどうかを確認する可能性が高く、アプリケーションのNullPointerExceptionが少なくなります。 ただし、オプションタイプがすべての場所に適しているわけではありません。
適切と思われる場所であればどこでも使用できますが、このチュートリアルでは、オプションをリターンタイプとして使用するためのいくつかのベストプラクティスに焦点を当てます。
2. リターンタイプとしてオプション
オプションタイプは、チュートリアルの後半で説明する一部のシナリオを除いて、ほとんどのメソッドのリターンタイプになります。
ほとんどの場合、オプションを返すことは問題ありません。
public static Optional<User> findUserByName(String name) {
User user = usersByName.get(name);
Optional<User> opt = Optional.ofNullable(user);
return opt;
}
呼び出し元のメソッドでオプションのAPIを使用できるため、これは便利です。
public static void changeUserName(String oldFirstName, String newFirstName) {
findUserByFirstName(oldFirstName).ifPresent(user -> user.setFirstName(newFirstName));
}
静的メソッドまたはユーティリティメソッドがOptional値を返すことも適切です。 ただし、オプションタイプを返さないようにする必要がある状況はたくさんあります。
3. 返却しない場合オプション
オプションはラッパーであり値ベースのクラスであるため、オプションオブジェクトに対して実行できない操作がいくつかあります。 多くの場合、オプションタイプではなく、実際のタイプを返す方が単純に優れています。
一般的に、POJOのゲッターの場合、オプションタイプではなく、実際のタイプを返す方が適しています。 特に、エンティティBean、データモデル、およびDTOが従来のゲッターを持つことが重要です。
以下では、いくつかの重要なユースケースを検討します。
3.1. シリアル化
単純なエンティティがあると想像してみましょう。
public class Sock implements Serializable {
Integer size;
Optional<Sock> pair;
// ... getters and setters
}
これは実際にはまったく機能しません。これをシリアル化しようとすると、 NotSerializableException が発生します:
new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(new Sock());
実際、シリアル化オプションは他のライブラリでも機能する可能性がありますが、それは確かに不必要な複雑さを追加します。
この同じシリアル化の不一致の別のアプリケーションを見てみましょう。今回はJSONを使用します。
3.2. JSON
最新のアプリケーションは、Javaオブジェクトを常にJSONに変換します。 ゲッターがOptionalタイプを返す場合、最終的なJSONに予期しないデータ構造が表示される可能性があります。
オプションのプロパティを持つbeanがあるとしましょう。
private String firstName;
public Optional<String> getFirstName() {
return Optional.ofNullable(firstName);
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
したがって、Jacksonを使用してオプションのインスタンスをシリアル化すると、次のようになります。
{"firstName":{"present":true}}
しかし、私たちが本当に望んでいるのは次のとおりです。
{"firstName":"Baeldung"}
したがって、オプションのは、シリアル化のユースケースにとって苦痛です。 次に、シリアル化のいとこを見てみましょう。データベースへのデータの書き込み。
3.3. JPA
JPAでは、getter、setter、およびfieldには、名前と型の一致が必要です。 たとえば、タイプStringのfirstNameフィールドは、[X104X]String。も返すgetFirstNameというゲッターとペアにする必要があります。
この規則に従うと、Hibernateなどのライブラリによるリフレクションの使用など、いくつかのことが簡単になり、優れたオブジェクトリレーショナルマッピングサポートが提供されます。
同じユースケースを見てみましょう
ただし、今回はJPAエンティティになります。
@Entity
public class UserOptionalField implements Serializable {
@Id
private long userId;
private Optional<String> firstName;
// ... getters and setters
}
そして、先に進んでそれを持続させてみましょう:
UserOptionalField user = new UserOptionalField();
user.setUserId(1l);
user.setFirstName(Optional.of("Baeldung"));
entityManager.persist(user);
悲しいことに、エラーが発生しました。
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.<clinit>(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)]
私たちは試すことができます
@Column(nullable = true)
private String firstName;
public Optional<String> getFirstName() {
return Optional.ofNullable(firstName);
}
ゲッターのOptionalリターンタイプと永続化可能なフィールドfirstNameの両方の方法があるようです。
ただし、getter、setter、およびfieldとの一貫性がなくなったため、JPAのデフォルトとIDEソースコードツールを活用することはさらに困難になります。
JPAがOptionalタイプをエレガントにサポートするまでは、従来のコードに固執する必要があります。 それはよりシンプルでより良いです:
private String firstName;
// ... traditional getter and setter
最後に、これがフロントエンドにどのように影響するかを見てみましょう。発生した問題がおなじみのように聞こえるかどうかを確認してください。
3.4. 表現言語
フロントエンド用にDTOを準備すると、同様の問題が発生します。
たとえば、JSPテンプレートを使用して UserOptionalDTOのfirstNameをリクエストから読み取ると想像してみてください。
<c:out value="${requestScope.user.firstName}" />
オプションであるため、「Baeldung」は表示されません。 代わりに、オプションタイプのString表現が表示されます。
Optional[Baeldung]
そして、これはJSPだけの問題ではありません。 Velocity、Freemarker、またはその他のテンプレート言語は、これに対するサポートを追加する必要があります。 それまでは、DTOをシンプルに保ち続けましょう。
4. 結論
このチュートリアルでは、オプションオブジェクトを返す方法と、この種の戻り値を処理する方法を学習しました。
一方、ゲッターにオプションリターンタイプを使用しない方がよいシナリオがたくさんあることも学びました。 null以外の値がない可能性があることを示すヒントとしてOptionalタイプを使用できますが、特にエンティティのゲッターでは、Optionalリターンタイプを使いすぎないように注意する必要があります。 BeanまたはDTO。
このチュートリアルの例のソースコードは、GitHubにあります。