[java] 선택적인 용도

6 개월 이상 동안 Java 8을 사용하고있는 새로운 API 변경에 매우 만족합니다. 내가 아직도 확신하지 못하는 영역은 언제 사용해야하는지 Optional입니다. 나는 어디에서나 사용할 수있는 곳 어디에서나 사용하려고하는 것 같습니다 null.

사용할 수있는 상황이 많이있는 것 같으며 이점 (가독성 / null 안전성)을 추가하거나 추가 오버 헤드를 유발하는지 확실하지 않습니다.

그래서 몇 가지 예가 있으며, Optional유익한 지에 대한 커뮤니티의 생각에 관심 이 있습니다.

1 – 공공 방법의 반환 형식으로 방법은 반환 할 때 null:

public Optional<Foo> findFoo(String id);

2-param이 다음 null과 같은 경우 메소드 매개 변수로 :

public Foo doSomething(String id, Optional<Bar> barOptional);

3-Bean의 선택적 멤버로서 :

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4-안으로 Collections:

일반적으로 나는 생각하지 않습니다 :

List<Optional<Foo>>

값 등 filter()을 제거 하는 데 사용할 수 있기 때문에 무엇이든 추가 null하지만 Optional컬렉션에 잘 사용 됩니까?

내가 놓친 사건이 있습니까?



답변

요점은 Optional반환 값이 없음을 나타내는 값을 반환하는 함수를위한 수단을 제공하는 것입니다. 이 토론을 참조하십시오 . 이를 통해 호출자는 일련의 유창한 메소드 호출을 계속할 수 있습니다.

이것은 OP 질문에서 유스 케이스 1 과 가장 일치합니다 . 비록 값의 부재는 보다 더 정확한 배합이다 널 (null) 과 같은 이후 IntStream.findFirst는 null을 반환하지 않을 수는.

유스 케이스 # 2의 경우 , 선택적 인수를 메소드에 전달하면 이것이 작동하도록 만들 수 있지만 다소 어색합니다. 문자열 다음에 선택적 두 번째 문자열을 취하는 메소드가 있다고 가정하십시오. Optional두 번째 인수로 a를 허용하면 다음 과 같은 코드가 생성됩니다.

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

null을 수락하는 것이 더 좋습니다.

foo("bar", "baz");
foo("bar", null);

아마도 가장 좋은 것은 단일 문자열 인수를 허용하고 두 번째에 대한 기본값을 제공하는 오버로드 된 메소드를 갖는 것입니다.

foo("bar", "baz");
foo("bar");

이것에는 한계가 있지만 위의 것보다 훨씬 좋습니다.

사용 예 # 3# 4 를 갖는 Optional데이터 구조 클래스 필드에 또는은 API의 오용 여겨진다. 첫째, Optional상단에 명시된 기본 설계 목표에 위배 됩니다. 둘째, 가치를 더하지 않습니다.

Optional대체 값을 제공하거나, 대체 값을 제공하기위한 함수를 호출하거나, 예외를 발생시키는 방법에는 값이없는 것을 처리하는 세 가지 방법이 있습니다 . 필드에 저장하는 경우 초기화 또는 할당 시간에이 작업을 수행합니다. OP가 언급 한 것처럼 목록에 값을 추가하는 경우 단순히 값을 추가하지 않고 추가 된 값을 “평평하게”선택할 수 있습니다.

누군가가 실제로 Optional필드 또는 컬렉션에 저장하려고하는 몇 가지 고안된 사례를 생각해 낼 수 있다고 확신 하지만 일반적 으로이 작업을 피하는 것이 가장 좋습니다.


답변

나는 게임에 늦었지만 가치가있는 것을 위해 2 센트를 추가하고 싶습니다. Stuart Marks의 답변으로 잘 요약 된 의 디자인 목표에Optional 반대 하지만 여전히 타당성을 확신합니다.

어디에서나 옵션 사용

일반적으로

사용에 대해Optional 전체 블로그 게시물을 작성 했지만 기본적으로 다음과 같습니다.

  • 가능한 한 선택을 피하기 위해 수업을 디자인하십시오
  • 나머지 모든 경우에 기본값은 Optional대신 사용하는 것이 좋습니다null
  • 다음에 대한 예외를 만들 수 있습니다.
    • 지역 변수
    • 개인 메소드에 값과 인수를 리턴
    • 성능이 중요한 코드 블록 (예상치 않고 프로파일 러 사용)

처음 두 예외는에서 래핑 및 래핑 해제 참조의인지 된 오버 헤드를 줄일 수 있습니다 Optional. 이들은 null이 법적으로 한 인스턴스에서 다른 인스턴스로 경계를 전달할 수 없도록 선택됩니다.

이것은 거의 Optionals만큼 나쁜 컬렉션에서 s를 거의 허용하지 않습니다 null. 하지 마십시오. 😉

당신의 질문에 대하여

  1. 예.
  2. 과부하가 옵션이 아닌 경우 가능합니다.
  3. 다른 접근 방식 (하위 클래스 링, 꾸미기 등)이 옵션이 없다면 그렇습니다.
  4. 안돼!

장점

이렇게하면 null코드 기반에서 s 의 존재가 줄어 듭니다 . 그러나 그것은 요점조차 아닙니다. 다른 중요한 장점이 있습니다.

의도를 명확하게한다

사용 Optional하면 변수가 선택 사항임을 명확하게 나타냅니다. 코드를 읽거나 API를 사용하는 소비자라면 아무 것도 없을 수 있으며 값에 액세스하기 전에 확인이 필요하다는 사실을 염두에 두어야합니다.

불확실성을 제거

없이 Optionala의 의미를 null발생 불분명하다. 상태를 합법적으로 표현 Map.get하거나 (참조 ) 초기화 누락 또는 실패와 같은 구현 오류 일 수 있습니다.

을 지속적으로 사용함에 따라 이것은 극적으로 변 Optional합니다. 여기서 이미 발생은 null버그가 있음을 나타냅니다. (값을 잃어 Optional버릴 수 있기 때문에를 사용했을 것입니다.) 이것은 이것의 의미에 대한 질문 null이 이미 답변 되었기 때문에 널 포인터 예외 디버깅을 훨씬 쉽게 만듭니다 .

더 많은 Null 검사

이제는 null더 이상 아무것도 할 수 없으므로 모든 곳에서 시행 할 수 있습니다. 주석, 어설 션 또는 일반 검사를 사용하더라도이 인수 또는 반환 유형이 null 일 수 있는지 여부를 생각할 필요가 없습니다. 할 수 없습니다!

단점

물론, 총알은 없습니다 …

공연

추가 인스턴스에 값 (특히 프리미티브)을 래핑하면 성능이 저하 될 수 있습니다. 꽉 조이는 루프에서는 눈에 띄거나 더 나빠질 수 있습니다.

컴파일러는 수명이 짧은 수명 동안 추가 참조를 우회 할 수 있습니다 Optional. Java 10에서 값 유형 은 추가로 패널티를 줄이거 나 제거 할 수 있습니다.

직렬화

Optional직렬화 할 수 없지만 해결 방법 이 지나치게 복잡하지 않습니다.

불변

Java에서 제네릭 형식의 차이로 인해 실제 값 형식이 제네릭 형식 인수로 푸시되면 특정 작업이 번거로워집니다. 여기에 예가 있습니다 ( “파라 메트릭 다형성”참조) .


답변

개인적으로 필자는 IntelliJ의 코드 검사 도구 를 사용 @NotNull하여 @Nullable컴파일 시간이 길기 때문에 사용 하고 확인 하는 것을 선호합니다 (일부 런타임 확인 가능) 코드 가독성 및 런타임 성능 측면에서 오버 헤드가 적습니다. Optional을 사용하는 것만 큼 엄격하지는 않지만 이러한 엄격한 부족은 적절한 단위 테스트를 통해 뒷받침되어야합니다.

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

이것은 Java 5에서 작동하며 값을 줄 바꿈하거나 줄 바꿈 할 필요가 없습니다. (또는 랩퍼 오브젝트 작성)


답변

구아바 옵션과 위키 페이지에 잘 나와 있다고 생각합니다.

널 (null)에 이름을 부여함으로써 가독성이 향상되는 것 외에도, Optional의 가장 큰 장점은 바보 방지입니다. 선택 사항을 적극적으로 풀고 해당 사례를 해결해야하기 때문에 프로그램을 전혀 컴파일하지 않으려면 부재 사례에 대해 적극적으로 생각해야합니다. 널 (Null)은 단순히 물건을 잊어 버릴 수있게 해주 며 FindBugs가 도움이되지만 문제를 거의 해결하지 못한다고 생각합니다.

이것은 “존재할 수도 있고 없을 수도있는”값을 반환 할 때 특히 관련이 있습니다. other.method (a, b)가 other.method를 구현할 때 a가 null 일 수 있다는 것을 잊어 버릴 것보다 other.method (a, b)가 null 값을 반환 할 수 있다는 것을 잊어 버릴 가능성이 훨씬 높습니다. Optional을 반환하면 호출자가 코드를 컴파일하기 위해 객체 자체를 언랩해야하므로이 경우를 잊을 수 없습니다. -(출처 : Guava Wiki-null 사용 및 회피-요점은 무엇입니까? )

Optional약간의 오버 헤드가 추가되지만
객체의 부재를 명시 하고 프로그래머가 상황을 처리하도록 강요하는 것이 확실한 이점이라고 생각 합니다. 누군가가 사랑하는 != null수표를 잊어 버리는 것을 방지합니다 .

2 의 예를 들어 작성하면 훨씬 더 명확한 코드라고 생각합니다.

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

…보다

if(soundcard != null){
  System.out.println(soundcard);
}

Optional에게는 사운드 카드가 없다는 사실을 더 잘 포착합니다.

당신의 요점에 대한 나의 2 ¢ :

  1. public Optional<Foo> findFoo(String id);-확실하지 않습니다. 어쩌면 나는 반환 Result<Foo>될 수있는 빈을 하거나를 포함 Foo. 비슷한 개념이지만 실제로는 아닙니다 Optional.
  2. public Foo doSomething(String id, Optional<Bar> barOptional);-Peter Lawrey의 답변 에서와 같이 @Nullable 및 findbugs check를 선호합니다 . 이 토론 도 참조하십시오 .
  3. 귀하의 도서 예-선택 사항을 내부적으로 사용할지 확실하지 않습니다. 복잡성에 따라 달라질 수 있습니다. 책의 “API”의 경우 책을 사용 Optional<Index> getIndex()하여 책에 색인이 없을 수 있음을 명시 적으로 나타냅니다.
  4. 컬렉션에서 사용하지 않고 컬렉션에서 null 값을 허용하지 않습니다.

일반적으로 nulls의 통과를 최소화하려고합니다 . (한 번 태워 …) 나는 적절한 추상화를 찾아서 동료 프로그래머에게 특정 반환 값이 실제로 나타내는 것을 나타낼 가치가 있다고 생각합니다.


답변

에서 오라클 튜토리얼 :

Optional의 목적은 코드베이스의 모든 단일 null 참조를 대체하는 것이 아니라 메소드의 서명을 읽음으로써 사용자가 선택적 값을 기대할 수 있는지를 알려주는 더 나은 API를 디자인하는 데 도움이되는 것입니다. 또한 Optional은 값의 부재를 처리하기 위해 Optional을 능동적으로 래핑 해제하도록합니다. 결과적으로 의도하지 않은 널 포인터 예외로부터 코드를 보호합니다.


답변

1-메소드가 널을 리턴 할 수있는 공용 메소드 리턴 유형

여기입니다 좋은 기사 쇼 유스 케이스 # 1의 유용성 그게. 이 코드가

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

이것으로 변환됩니다

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

선택적을 각 getter 메소드 의 리턴 값으로 사용 합니다.


답변

다음은 흥미로운 사용법입니다.

내 프로젝트 중 하나를 심하게 테스트하려고하므로 어설 션을 작성합니다. 내가 확인해야 할 사항과 내가 확인하지 않은 사항 만 있습니다.

따라서 다음과 같이 주장하는 것을 빌드하고 주장을 사용하여 검증합니다.

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

NodeAssert 클래스에서 다음을 수행합니다.

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

즉, 레이블을 확인하려는 경우 어설 션이 실제로 트리거됩니다.