Перейти к основному содержимому

Java Необязательный как возвращаемый тип

· 5 мин. чтения

1. Введение

Необязательный тип был введен в Java 8. Он обеспечивает ясный и явный способ передать сообщение о том, что может не быть значения, без использования null .

При получении возвращаемого типа Optional мы, скорее всего, проверим, отсутствует ли значение, что приведет к меньшему количеству исключений NullPointerException в приложениях. Однако необязательный тип подходит не во всех местах.

Хотя мы можем использовать его везде, где сочтем нужным, в этом руководстве мы сосредоточимся на некоторых передовых методах использования Optional в качестве возвращаемого типа.

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));
}

Также допустимо, чтобы статический метод или служебный метод возвращал необязательное значение. Однако во многих ситуациях мы не должны возвращать необязательный тип.

3. Когда не возвращать Необязательно

Поскольку необязательный класс является оболочкой и основан на значениях , некоторые операции нельзя выполнять с необязательным объектом. Во многих случаях просто лучше вернуть фактический тип, а не необязательный тип.

Вообще говоря, для геттеров в POJO более подходит возврат фактического типа, а не необязательный тип. В частности, для Entity Beans, Data Models и DTO важно иметь традиционные геттеры.

Мы рассмотрим некоторые из важных вариантов использования ниже.

3.1. Сериализация

Давайте представим, что у нас есть простая сущность:

public class Sock implements Serializable {
Integer size;
Optional<Sock> pair;

// ... getters and setters
}

Это на самом деле не будет работать вообще. Если бы мы попытались сериализовать это, мы бы получили NotSerializableException :

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

И действительно, хотя сериализация Optional может работать с другими библиотеками , она, безусловно, добавляет ненужную сложность.

Давайте взглянем на другое применение того же несоответствия сериализации, на этот раз с JSON.

3.2. JSON

Современные приложения постоянно конвертируют объекты Java в JSON. Если геттер возвращает необязательный тип, мы, скорее всего, увидим какую-то неожиданную структуру данных в финальном JSON.

Допустим, у нас есть bean-компонент с необязательным свойством:

private String firstName;

public Optional<String> getFirstName() {
return Optional.ofNullable(firstName);
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

Итак, если мы используем Jackson для сериализации экземпляра Optional , мы получим:

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

Но то, что мы действительно хотим, это:

{"firstName":"ForEach"}

Таким образом, Необязательный является болью для вариантов использования сериализации. Далее, давайте посмотрим на двоюродного брата сериализации: запись данных в базу данных.

3.3. JPA

В JPA геттер, сеттер и поле должны иметь имя, а также соглашение о типе. Например, поле firstName типа String должно быть соединено с получателем getFirstName , который также возвращает String.

Следование этому соглашению упрощает некоторые вещи, в том числе использование отражения такими библиотеками, как Hibernate, что дает нам отличную поддержку объектно-реляционного сопоставления.

Давайте взглянем на тот же вариант использования необязательного имени в POJO.

Однако на этот раз это будет сущность 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("ForEach"));
entityManager.persist(user);

К сожалению, мы сталкиваемся с ошибкой:

Caused by: javax.persistence.PersistenceException: [PersistenceUnit: com.foreach.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.foreach.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)]

Мы могли бы попробовать отклониться от этого стандарта. Например, мы могли бы сохранить свойство как String , но изменить геттер:

@Column(nullable = true) 
private String firstName;

public Optional<String> getFirstName() {
return Optional.ofNullable(firstName);
}

Похоже, у нас могут быть оба пути: иметь необязательный возвращаемый тип для геттера и сохраняемое поле firstName .

Однако теперь, когда мы несовместимы с нашим геттером, сеттером и полем, будет сложнее использовать значения по умолчанию JPA и инструменты исходного кода IDE.

Пока в JPA не появится элегантная поддержка типа Optional , мы должны придерживаться традиционного кода. Это проще и лучше:

private String firstName;

// ... traditional getter and setter

Давайте, наконец, посмотрим, как это влияет на внешний интерфейс — проверьте, не кажется ли знакомой проблема, с которой мы столкнулись.

3.4. Языки выражений

Подготовка DTO для внешнего интерфейса сопряжена с аналогичными трудностями.

Например, давайте представим, что мы используем шаблоны JSP для чтения нашего UserOptional DTO firstName из запроса:

<c:out value="${requestScope.user.firstName}" />

Поскольку это необязательно , мы не увидим « ForEach ». Вместо этого мы увидим строковое представление необязательного типа:

Optional[ForEach]

И это проблема не только JSP. Любой язык шаблонов, будь то Velocity, Freemarker или что-то еще, должен будет добавить поддержку этого. А пока давайте продолжим упрощать наши DTO.

4. Вывод

В этом руководстве мы узнали, как вернуть необязательный объект и что делать с возвращаемым значением такого типа.

С другой стороны, мы также узнали, что есть много сценариев, в которых нам было бы лучше не использовать необязательный возвращаемый тип для геттера. Хотя мы можем использовать необязательный тип как подсказку о том, что не может быть ненулевого значения, мы должны быть осторожны, чтобы не злоупотреблять необязательным возвращаемым типом, особенно в геттере объектного компонента или DTO.

Исходный код примеров из этого руководства можно найти на GitHub .