Java에서 OPTIONAL에 대해 이야기하십시오.

 Java 8에서 도입 된 흥미로운 기능은 Optional 클래스입니다. Optional 클래스가 해결하는 주요 문제는 악명 높은 NullPointerException입니다. 모든 Java 프로그래머가 매우 잘 알고있는 예외입니다.
 기본적으로 이것은 선택적 값을 포함하는 래퍼 클래스입니다. 이는 Optional 클래스가 개체를 포함하거나 비어있을 수 있음을 의미합니다.

 선택 사항은 Java의 함수형 프로그래밍을 향한 강력한 단계이며 패러다임에서이를 달성하는 데 도움이됩니다. 그러나 Optional의 의미는 분명히 그 이상입니다.


간단한 사용 사례부터 시작합니다. Java 8 이전에는 개체 메서드 또는 속성에 액세스하기위한 호출로 인해 NullPointerException이 발생할 수 있습니다.
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

이 작은 예제에서 예외가 트리거되지 않았는지 확인해야하는 경우 액세스하기 전에 각 값을 명시 적으로 확인해야합니다.

if (user != null) {
    
    
    Address address = user.getAddress();
    if (address != null) {
    
    
        Country country = address.getCountry();
        if (country != null) {
    
    
            String isocode = country.getIsocode();
            if (isocode != null) {
    
    
                isocode = isocode.toUpperCase();
            }
        }
    }
}

보시다시피, 이것은 쉽게 장황 해지고 유지하기 어려울 수 있습니다.

이 프로세스를 단순화하기 위해 Optional 클래스를 사용하여 수행하는 방법을 살펴 보겠습니다. 인스턴스 생성 및 유효성 검사에서 다른 메서드 사용, 동일한 유형을 반환하는 다른 메서드와 결합하는 것까지 다음은 Optional의 기적을 목격하는 순간입니다.


선택적 인스턴스 만들기

반복해서 말하면,이 유형의 객체는 값을 포함하거나 비어있을 수 있습니다. 같은 이름의 방법을 사용하여 빈 옵션을 만들 수 있습니다.

@Test(expected = NoSuchElementException.class)
public void whenCreateEmptyOptional_thenNull() {
    
    
    Optional<User> emptyOpt = Optional.empty();
    emptyOpt.get();
}

당연히 emptyOpt 변수의 값에 액세스하려고하면 NoSuchElementException이 발생합니다.

of () 및 ofNullable () 메서드를 사용하여 값이 포함 된 Optional을 만들 수 있습니다. 두 메서드의 차이점은 null 값을 매개 변수로 전달하면 of () 메서드가 NullPointerException을 throw한다는 것입니다.

@Test(expected = NullPointerException.class)
public void whenCreateOfEmptyOptional_thenNullPointerException() {
    
    
    Optional<User> opt = Optional.of(user);
}

우리는 NullPointerException에서 완전히 자유롭지 않습니다. 따라서 개체가 null이 아닌 경우 of ()를 사용해야합니다.

객체가 null이거나 null이 아닌 경우 ofNullable () 메서드를 사용해야합니다.

Optional<User> opt = Optional.ofNullable(user);

Optional 개체의 값에 액세스

Optional 인스턴스에서 실제 값 객체를 검색하는 방법 중 하나는 get () 메서드를 사용하는 것입니다.

@Test
public void whenCreateOfNullableOptional_thenOk() {
    
    
    String name = "John";
    Optional<String> opt = Optional.ofNullable(name);

    assertEquals("John", opt.get());
}

그러나 보시다시피이 메서드는 값이 null 인 경우 예외를 throw합니다. 예외를 방지하려면 먼저 값이 있는지 확인하도록 선택할 수 있습니다.

@Test
public void whenCheckIfPresent_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    Optional<User> opt = Optional.ofNullable(user);
    assertTrue(opt.isPresent());

    assertEquals(user.getEmail(), opt.get().getEmail());
}

값이 있는지 확인하는 또 다른 옵션은 ifPresent () 메서드입니다. 확인을 수행하는 것 외에도이 메서드는 Consumer (consumer) 파라미터도 허용합니다. 객체가 비어 있지 않으면 전달 된 Lambda 표현식을 실행합니다.

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

이 예에서 어설 션은 사용자 사용자가 null이 아닌 경우에만 실행됩니다.

다음으로 null 값을 제공하는 방법을 살펴 보겠습니다.


기본값으로 돌아 가기

Optional 클래스는 객체의 값을 반환하거나 객체가 비어있을 때 기본값을 반환하는 API를 제공합니다.

여기서 사용할 수있는 첫 번째 메서드는 orElse ()입니다. 매우 간단하게 작동합니다. 값이 있으면 해당 값을 반환하고 그렇지 않으면 전달 된 매개 변수의 값을 반환합니다.

@Test
public void whenEmptyValue_thenReturnDefault() {
    
    
    User user = null;
    User user2 = new User("[email protected]", "1234");
    User result = Optional.ofNullable(user).orElse(user2);

    assertEquals(user2.getEmail(), result.getEmail());
}

여기서 사용자 개체는 비어 있으므로 기본값 인 user2가 반환됩니다.

객체의 초기 값이 null이 아니면 기본값이 무시됩니다.

@Test
public void whenValueNotNull_thenIgnoreDefault() {
    
    
    User user = new User("[email protected]","1234");
    User user2 = new User("[email protected]", "1234");
    User result = Optional.ofNullable(user).orElse(user2);

    assertEquals("[email protected]", result.getEmail());
}

동일한 유형의 두 번째 API는 orElseGet ()입니다. 동작이 약간 다릅니다. 이 메소드는 값이 있으면 값을 반환합니다. 값이 없으면 매개 변수로 전달 된 Supplier 기능 인터페이스를 실행하고 실행 결과를 반환합니다.

User result = Optional.ofNullable(user).orElseGet( () -> user2);

orElse ()와 orElseGet ()의 차이점

언뜻보기에이 두 가지 방법은 동일한 효과가있는 것 같습니다. 그러나 그렇지 않습니다. 행동의 유사점과 차이점을 강조하기 위해 몇 가지 예를 만듭니다.

객체가 비어있을 때의 동작을 살펴 보겠습니다.

@Test
public void givenEmptyValue_whenCompare_thenOk() {
    
    
    User user = null
    logger.debug("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.debug("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

private User createNewUser() {
    
    
    logger.debug("Creating New User");
    return new User("[email protected]", "1234");
}

위 코드에서 두 메서드는 모두 메시지를 기록하고 User 개체를 반환하는 createNewUser () 메서드를 호출합니다.

코드 출력은 다음과 같습니다.

Using orElse
Creating New User
Using orElseGet
Creating New User

이것은 객체가 비어 있고 기본 객체가 반환 될 때 동작에 차이가 없음을 보여줍니다.


다음으로 유사한 예를 살펴 보겠습니다. 여기서 Optional은 비어 있지 않습니다.
@Test
public void givenPresentValue_whenCompare_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    logger.info("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.info("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

이번 출력 :

Using orElse
Creating New User
Using orElseGet

이 예에서 두 Optional 개체는 모두 null이 아닌 값을 포함하고 두 메서드 모두 해당하는 null이 아닌 값을 반환합니다. 그러나 orElse () 메서드는 여전히 User 객체를 만듭니다. 반대로 orElseGet () 메서드는 User 객체를 생성하지 않습니다.

웹 서비스 또는 데이터 쿼리 호출과 같이 더 집약적 인 호출을 수행 할 때 이러한 차이는 성능에 상당한 영향을 미칠 수 있습니다.


반품 예외

orElse () 및 orElseGet () 메서드 외에도 Optional은 orElseThrow () API를 정의합니다. 대체 값을 반환하는 대신 객체가 비어 있으면 예외가 발생합니다.

@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
    
    
    User result = Optional.ofNullable(user)
      .orElseThrow( () -> new IllegalArgumentException());
}

여기서 사용자 값이 null이면 IllegalArgumentException이 발생합니다.

이 메서드를 사용하면 더 풍부한 의미를 가질 수 있으며 항상 NullPointerException을 throw하는 대신 throw 할 예외 유형을 결정할 수 있습니다.

이제 Optional을 사용하는 방법을 잘 이해 했으므로 Optional 값을 변환하고 필터링 할 수있는 다른 메서드를 살펴 보겠습니다.


전환 가치

Optional의 값을 변환하는 방법에는 여러 가지가 있습니다. map () 및 flatMap () 메서드부터 시작합니다.

먼저 map () API를 사용하는 예를 살펴 보겠습니다.

@Test
public void whenMap_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("[email protected]");

    assertEquals(email, user.getEmail());
}

map ()은 함수를 매개 변수로 값에 적용 (호출) 한 다음 반환 된 값을 Optional에 래핑합니다. 이를 통해 반환 값에 대해 체인 테스트 호출을 수행 할 수 있습니다. 여기에서 다음 루프는 orElse ()입니다.

대조적으로 flatMap ()은 매개 변수로 함수를 필요로하고 값에 대해이 함수를 호출하고 결과를 직접 반환합니다.

다음 작업에서 옵션을 반환하는 메서드를 User 클래스에 추가했습니다.

public class User {
    
        
    private String position;

    public Optional<String> getPosition() {
    
    
        return Optional.ofNullable(position);
    }

    //...
}

getter 메서드는 String 값의 Optional을 반환하므로 User의 Optional 객체에서 flatMap ()을 호출 할 때 매개 변수로 사용할 수 있습니다. 반환 된 값은 압축을 푼 문자열 값입니다.

@Test
public void whenFlatMap_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    user.setPosition("Developer");
    String position = Optional.ofNullable(user)
      .flatMap(u -> u.getPosition()).orElse("default");

    assertEquals(position, user.getPosition().get());
}

필터 값

값을 변환하는 것 외에도 Optional 클래스는 조건에 따라 값을 "필터링"하는 방법도 제공합니다.

filter ()는 Predicate 매개 변수를 받아들이고 테스트 결과의 값을 true로 반환합니다. 테스트 결과가 거짓이면 빈 옵션이 반환됩니다.

기본 이메일 확인을 기반으로 사용자를 수락할지 거부 할지를 결정하는 예를 살펴 보겠습니다.

@Test
public void whenFilter_thenOk() {
    
    
    User user = new User("[email protected]", "1234");
    Optional<User> result = Optional.ofNullable(user)
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

    assertTrue(result.isPresent());
}

필터 테스트가 통과되면 결과 개체에 null이 아닌 값이 포함됩니다.


Optional 클래스의 Chain 방식

Optional을 더 완벽하게 사용하려면 모두 동일하고 유사한 객체를 반환하므로 대부분의 메서드를 연결하고 결합 할 수 있습니다.

우리는 Optional을 사용하여 소개 된 첫 번째 예제를 다시 작성합니다.

먼저 getter 메서드가 Optional 참조를 반환하도록 클래스를 리팩터링합니다.

public class User {
    
    
    private Address address;

    public Optional<Address> getAddress() {
    
    
        return Optional.ofNullable(address);
    }

    // ...
}
public class Address {
    
    
    private Country country;

    public Optional<Country> getCountry() {
    
    
        return Optional.ofNullable(country);
    }

    // ...
}

위의 중첩 된 구조는 다음 그림으로 나타낼 수 있습니다.
여기에 사진 설명 삽입
이제 null 검사를 삭제하고이를 Optional 메서드로 바꿀 수 있습니다.

@Test
public void whenChaining_thenOk() {
    
    
    User user = new User("[email protected]", "1234");

    String result = Optional.ofNullable(user)
      .flatMap(u -> u.getAddress())
      .flatMap(a -> a.getCountry())
      .map(c -> c.getIsocode())
      .orElse("default");

    assertEquals(result, "default");
}

위의 코드는 메서드 참조로 더 줄일 수 있습니다.

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

결과적으로 코드는 이전에 조건부 분기를 사용했던 긴 코드보다 훨씬 깔끔해 보입니다.


옵션은 어떻게 사용되어야합니까?

Optional을 사용하여 언제 어떻게 사용하는지 결정할 때 고려해야 할 몇 가지 사항이 있습니다.

중요한 점은 Optional이 Serializable이 아니라는 것입니다. 따라서 클래스의 필드로 사용해서는 안됩니다.

직렬화해야하는 객체에 Optional 값이 포함되어있는 경우 Jackson 라이브러리는 Optional을 일반 객체로 처리하는 것을 지원합니다. 즉, Jackson은 빈 개체를 null로 처리하고 값이있는 개체는 해당 값을 해당 도메인의 값으로 처리합니다. 이 기능은 jackson-modules-java8 프로젝트에 있습니다.


또한 유형이 메소드 또는 구성 메소드 매개 변수로 사용되는 다른 경우에는 그다지 유용하지 않습니다. 그렇게하면 코드가 복잡해지고 완전히 불필요합니다.
User user = new User("[email protected]", "1234", Optional.empty());

불필요한 매개 변수를 처리하기 위해 오버로드 된 메서드를 사용하는 것이 훨씬 쉽습니다.

선택 사항은 주로 반환 유형으로 사용됩니다. 이 유형의 인스턴스를 가져온 후 값이 있으면이 값을 가져올 수 있고 그렇지 않으면 몇 가지 대체 작업을 수행 할 수 있습니다.

Optional 클래스에는 유창한 API를 빌드하기 위해 Optional을 반환하는 스트림 또는 기타 메서드와 결합하는 매우 유용한 사용 사례가 있습니다.

Stream의 findFirst () 메서드를 사용하여 Optional 객체를 반환하는 예제를 살펴 보겠습니다.

@Test
public void whenEmptyStream_thenReturnDefaultOptional() {
    
    
    List<User> users = new ArrayList<>();
    User user = users.stream().findFirst().orElse(new User("default", "1234"));

    assertEquals(user.getEmail(), "default");
}

요약하자면

선택 사항은 Java 언어에 유용한 추가 기능입니다. 이러한 예외를 완전히 제거 할 수는 없지만 코드에서 NullPointerExceptions를 줄이는 것을 목표로합니다.

또한 잘 설계되었으며 Java 8 기능이 지원하는 기능을 자연스럽게 통합합니다.

일반적으로이 간단하고 강력한 클래스는 해당 프로그램보다 더 간단하고 읽기 쉽고 오류가 적은 프로그램을 만드는 데 도움이됩니다.

출처 : https://www.oschina.net/translate/understanding-accepting-and-leveraging-optional-in?lang=chs&page=2#

추천

출처blog.csdn.net/woaichihanbao/article/details/108071437