새로운 Java 8 스트림 프레임 워크와 친구는 매우 간결한 Java 코드를 만들지 만 간결하게하기가 까다로워 보이는 단순한 상황을 발견했습니다.
a List<Thing> things
와 method를 고려하십시오 Optional<Other> resolve(Thing thing)
. 나는 매핑 할 Thing
에의 Optional<Other>
의를 첫 번째를 얻을 Other
. 확실한 해결책은을 사용하는 things.stream().flatMap(this::resolve).findFirst()
것이지만 flatMap
스트림을 반환 Optional
해야하며 stream()
메소드 가 없습니다 (또는 스트림 Collection
으로 변환하거나 볼 수있는 메소드를 제공하거나 제공 하지 않아야 함 Collection
).
내가 생각해 낼 수있는 최선은 다음과 같습니다.
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
그러나 그것은 매우 일반적인 경우처럼 보일 정도로 끔찍한 것처럼 보입니다. 더 좋은 아이디어가 있습니까?
답변
자바 9
Optional.stream
JDK 9에 추가되었습니다.이를 통해 헬퍼 메소드가 없어도 다음을 수행 할 수 있습니다.
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(Optional::stream)
.findFirst();
자바 8
다소을 설정하는 불편 점에서 예,이는 API에 작은 구멍이었다 Optional<T>
제로 또는-하나의 길이로 Stream<T>
. 당신은 이것을 할 수 있습니다 :
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.findFirst();
내부에 삼항 연산자를 갖는 flatMap
것은 약간 번거롭기 때문에 이것을 수행하는 작은 도우미 함수를 작성하는 것이 좋습니다.
/**
* Turns an Optional<T> into a Stream<T> of length zero or one depending upon
* whether a value is present.
*/
static <T> Stream<T> streamopt(Optional<T> opt) {
if (opt.isPresent())
return Stream.of(opt.get());
else
return Stream.empty();
}
Optional<Other> result =
things.stream()
.flatMap(t -> streamopt(resolve(t)))
.findFirst();
여기서는 resolve()
별도의 map()
작업 대신 호출을 인라인 했지만 맛의 문제입니다.
답변
srborlongan 사용자가 제안한 편집 내용을 기반으로 두 번째 답변 을 다른 답변에 추가하고 있습니다. 제안 된 기술이 흥미 롭다고 생각하지만 내 대답을 편집하는 데 실제로 적합하지 않았습니다. 다른 사람들은 동의했고 제안 된 수정안은 표결되었다. (나는 유권자 중 한 사람이 아니었다.) 그러나이 기술에는 장점이있다. srborlongan이 자신의 답변을 게시 한 것이 가장 좋을 것입니다. 이것은 아직 일어나지 않았으며 StackOverflow가 편집 기록을 거부했을 때 기술이 손실되는 것을 원하지 않기 때문에 별도의 답변으로 표시하기로 결정했습니다.
기본적으로이 기법은 Optional
삼항 연산자 ( ? :
) 또는 if / else 문 을 사용하지 않아도되도록 일부 방법을 영리하게 사용하는 것입니다.
내 인라인 예제는 다음과 같이 다시 작성됩니다.
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
도우미 메서드를 사용하는 내 예제는 다음과 같이 다시 작성됩니다.
/**
* Turns an Optional<T> into a Stream<T> of length zero or one depending upon
* whether a value is present.
*/
static <T> Stream<T> streamopt(Optional<T> opt) {
return opt.map(Stream::of)
.orElseGet(Stream::empty);
}
Optional<Other> result =
things.stream()
.flatMap(t -> streamopt(resolve(t)))
.findFirst();
해설
원래 버전과 수정 된 버전을 직접 비교해 보겠습니다.
// original
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
// modified
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
노동자와 같은 접근 방식이라면 원본은 간단합니다 Optional<Other>
. 값이 있으면 해당 값이 포함 된 스트림을 반환하고 값이 없으면 빈 스트림을 반환합니다. 매우 간단하고 설명하기 쉽습니다.
수정은 영리하며 조건을 피할 수 있다는 이점이 있습니다. (일부 사람들은 삼항 연산자를 싫어한다는 것을 알고 있습니다. 잘못 사용하면 실제로 코드를 이해하기 어려울 수 있습니다.) 그러나 때로는 상황이 너무 영리 할 수 있습니다. 수정 된 코드는로 시작합니다 Optional<Other>
. 그런 Optional.map
다음 다음과 같이 정의 된 호출 합니다.
값이 있으면 제공된 맵핑 함수를 적용하고 결과가 널이 아닌 경우 결과를 설명하는 선택 사항을 리턴하십시오. 그렇지 않으면 빈 옵션을 반환하십시오.
map(Stream::of)
호출은을 반환합니다 Optional<Stream<Other>>
. 선택 입력에 값이 있으면 반환 된 선택에 단일 기타 결과가 포함 된 스트림이 포함됩니다. 그러나 값이 없으면 결과는 비어 있습니다.
다음에 대한 호출 orElseGet(Stream::empty)
은 유형의 값 을 반환합니다 Stream<Other>
. 입력 값이 있으면 단일 요소 인 값을 가져옵니다 Stream<Other>
. 그렇지 않으면 (입력 값이없는 경우) 빈을 반환합니다 Stream<Other>
. 따라서 원래 조건부 코드와 동일한 결과가 정확합니다.
거부 된 편집과 관련하여 내 답변에 대해 언급 한 의견에서이 기술을 “보다 간결하지만 모호한”것으로 설명했습니다. 나는 이것에 의해 서있다. 그것이 무엇을하고 있는지 알아내는 데 시간이 걸렸고, 그것이 무엇을하고 있는지에 대한 위의 설명을 쓰는 데 시간이 걸렸습니다. 키 미묘의 변형이다 Optional<Other>
행 Optional<Stream<Other>>
. 일단 당신이 이것을 이해하는 것은 의미가 있지만, 나에게는 분명하지 않았습니다.
하지만 처음에는 불분명 한 것이 시간이지나면서 관용적이 될 수 있음을 인정합니다. 이 기술은 실제로 Optional.stream
추가 될 때까지 ( 최소한 경우) 실제로 가장 좋은 방법 일 수 있습니다 .
업데이트 : Optional.stream
JDK 9에 추가되었습니다.
답변
이미하고있는 것처럼 더 간결하게 할 수는 없습니다.
당신은 당신이 원하지 않는 것을 주장 .filter(Optional::isPresent)
하고 .map(Optional::get)
.
그러나 이것은 결과적으로 당신이 지금에 매핑 설명 메소드 @StuartMarks에 의해 해결 된 Optional<T>
그래서 지금 당신이 사용해야 .flatMap(this::streamopt)
하고, get()
결국.
따라서 여전히 두 개의 문으로 구성되며 이제 새로운 방법으로 예외를 얻을 수 있습니다! 모든 옵션이 비어 있으면 어떻게됩니까? 그런 다음 findFirst()
빈 옵션을 반환하고 get()
실패합니다!
그래서 당신이 가진 것 :
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
입니다 당신이 원하는 것을 달성하는 가장 좋은 방법은 실제로, 그것은 당신이 같은 결과를 저장할 것입니다 T
아닌 같은 Optional<T>
.
나는 CustomOptional<T>
포장하고 Optional<T>
추가 방법을 제공하는 클래스를 만들 자유를 얻었습니다 flatStream()
. 다음을 확장 할 수 없습니다 Optional<T>
.
class CustomOptional<T> {
private final Optional<T> optional;
private CustomOptional() {
this.optional = Optional.empty();
}
private CustomOptional(final T value) {
this.optional = Optional.of(value);
}
private CustomOptional(final Optional<T> optional) {
this.optional = optional;
}
public Optional<T> getOptional() {
return optional;
}
public static <T> CustomOptional<T> empty() {
return new CustomOptional<>();
}
public static <T> CustomOptional<T> of(final T value) {
return new CustomOptional<>(value);
}
public static <T> CustomOptional<T> ofNullable(final T value) {
return (value == null) ? empty() : of(value);
}
public T get() {
return optional.get();
}
public boolean isPresent() {
return optional.isPresent();
}
public void ifPresent(final Consumer<? super T> consumer) {
optional.ifPresent(consumer);
}
public CustomOptional<T> filter(final Predicate<? super T> predicate) {
return new CustomOptional<>(optional.filter(predicate));
}
public <U> CustomOptional<U> map(final Function<? super T, ? extends U> mapper) {
return new CustomOptional<>(optional.map(mapper));
}
public <U> CustomOptional<U> flatMap(final Function<? super T, ? extends CustomOptional<U>> mapper) {
return new CustomOptional<>(optional.flatMap(mapper.andThen(cu -> cu.getOptional())));
}
public T orElse(final T other) {
return optional.orElse(other);
}
public T orElseGet(final Supplier<? extends T> other) {
return optional.orElseGet(other);
}
public <X extends Throwable> T orElseThrow(final Supplier<? extends X> exceptionSuppier) throws X {
return optional.orElseThrow(exceptionSuppier);
}
public Stream<T> flatStream() {
if (!optional.isPresent()) {
return Stream.empty();
}
return Stream.of(get());
}
public T getTOrNull() {
if (!optional.isPresent()) {
return null;
}
return get();
}
@Override
public boolean equals(final Object obj) {
return optional.equals(obj);
}
@Override
public int hashCode() {
return optional.hashCode();
}
@Override
public String toString() {
return optional.toString();
}
}
다음 flatStream()
과 같이 내가 추가 한 것을 볼 수 있습니다.
public Stream<T> flatStream() {
if (!optional.isPresent()) {
return Stream.empty();
}
return Stream.of(get());
}
로 사용 :
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.flatMap(CustomOptional::flatStream)
.findFirst()
.get();
당신은 여전히 를 반환해야합니다 Stream<T>
당신은 반환 할 수 없으므로, 여기에 T
있기 때문에 경우, !optional.isPresent()
다음, T == null
당신이 같은 선언하지만, 다음 경우 .flatMap(CustomOptional::flatStream)
추가하려고 할 null
스트림에 해당이 불가능합니다.
예를 들어 :
public T getTOrNull() {
if (!optional.isPresent()) {
return null;
}
return get();
}
로 사용 :
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.map(CustomOptional::getTOrNull)
.findFirst()
.get();
이제 NullPointerException
스트림 작업 내부를 던질 것 입니다.
결론
사용한 방법이 실제로 가장 좋은 방법입니다.
답변
다음을 사용하는 약간 짧은 버전 reduce
:
things.stream()
.map(this::resolve)
.reduce(Optional.empty(), (a, b) -> a.isPresent() ? a : b );
reduce 함수를 정적 유틸리티 메소드로 이동하면 다음과 같이됩니다.
.reduce(Optional.empty(), Util::firstPresent );
답변
내 이전 답변 이별로 인기가없는 것처럼 보였으므로 다시 한 번 알려 드리겠습니다.
짧은 대답 :
당신은 대부분 올바른 길을 가고 있습니다. 원하는 결과를 얻을 수있는 가장 짧은 코드는 다음과 같습니다.
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst()
.flatMap( Function.identity() );
이것은 모든 요구 사항에 적합합니다.
- 비어 있지 않은 첫 번째 응답을 찾습니다.
Optional<Result>
this::resolve
필요에 따라 게으르게 호출this::resolve
비어 있지 않은 첫 번째 결과 후에 호출되지 않습니다- 돌아올 것이다
Optional<Result>
더 긴 답변
OP 초기 버전과 비교할 때 유일한 수정은 .map(Optional::get)
호출하기 전에 제거 하고 체인의 마지막 호출로 .findFirst()
추가 .flatMap(o -> o)
한 것입니다.
스트림이 실제 결과를 찾을 때마다 double-Optional을 제거하는 좋은 효과가 있습니다.
Java에서는 이보다 더 짧게 갈 수 없습니다.
보다 일반적인 for
루프 기술을 사용하는 대체 코드 스 니펫은 거의 같은 수의 코드 줄이며 수행해야 할 순서와 수는 거의 동일합니다.
- 호출
this.resolve
, - 에 따라 필터링
Optional.isPresent
- 결과를 반환하고
- 부정적인 결과를 다루는 방법 (아무 것도 발견되지 않은 경우)
내 솔루션이 광고 된대로 작동한다는 것을 증명하기 위해 작은 테스트 프로그램을 작성했습니다.
public class StackOverflow {
public static void main( String... args ) {
try {
final int integer = Stream.of( args )
.peek( s -> System.out.println( "Looking at " + s ) )
.map( StackOverflow::resolve )
.filter( Optional::isPresent )
.findFirst()
.flatMap( o -> o )
.orElseThrow( NoSuchElementException::new )
.intValue();
System.out.println( "First integer found is " + integer );
}
catch ( NoSuchElementException e ) {
System.out.println( "No integers provided!" );
}
}
private static Optional<Integer> resolve( String string ) {
try {
return Optional.of( Integer.valueOf( string ) );
}
catch ( NumberFormatException e )
{
System.out.println( '"' + string + '"' + " is not an integer");
return Optional.empty();
}
}
}
(필요에 따라 해결하기 위해 많은 호출 만 디버깅하고 확인하는 추가 줄이 거의 없습니다 …)
이것을 커맨드 라인에서 실행하면 다음과 같은 결과가 나타납니다.
$ java StackOferflow a b 3 c 4
Looking at a
"a" is not an integer
Looking at b
"b" is not an integer
Looking at 3
First integer found is 3
답변
타사 라이브러리를 사용하지 않으려면 Javaslang을 사용할 수 있습니다 . 스칼라와 비슷하지만 Java로 구현됩니다.
그것은 Scala에서 알려진 것과 매우 유사한 완전한 불변 콜렉션 라이브러리와 함께 제공됩니다. 이 콜렉션은 Java 콜렉션 및 Java 8 스트림을 대체합니다. 또한 자체 옵션 구현이 있습니다.
import javaslang.collection.Stream;
import javaslang.control.Option;
Stream<Option<String>> options = Stream.of(Option.some("foo"), Option.none(), Option.some("bar"));
// = Stream("foo", "bar")
Stream<String> strings = options.flatMap(o -> o);
초기 질문의 예에 대한 해결책은 다음과 같습니다.
import javaslang.collection.Stream;
import javaslang.control.Option;
public class Test {
void run() {
// = Stream(Thing(1), Thing(2), Thing(3))
Stream<Thing> things = Stream.of(new Thing(1), new Thing(2), new Thing(3));
// = Some(Other(2))
Option<Other> others = things.flatMap(this::resolve).headOption();
}
Option<Other> resolve(Thing thing) {
Other other = (thing.i % 2 == 0) ? new Other(i + "") : null;
return Option.of(other);
}
}
class Thing {
final int i;
Thing(int i) { this.i = i; }
public String toString() { return "Thing(" + i + ")"; }
}
class Other {
final String s;
Other(String s) { this.s = s; }
public String toString() { return "Other(" + s + ")"; }
}
면책 조항 : 저는 Javaslang의 제작자입니다.
답변
파티에 늦었지만
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst().get();
옵션을 스트림으로 수동으로 변환하는 util 메소드를 작성하면 마지막 get ()을 제거 할 수 있습니다.
things.stream()
.map(this::resolve)
.flatMap(Util::optionalToStream)
.findFirst();
resolve 함수에서 즉시 스트림을 반환하면 한 줄을 더 저장합니다.