술어로 첫 번째 요소 찾기


504

방금 Java 8 람다로 연주하기 시작했으며 기능적 언어로 익숙한 것들 중 일부를 구현하려고합니다.

예를 들어, 대부분의 기능적 언어에는 시퀀스에서 작동하는 일종의 찾기 함수 또는 술어가 첫 번째 요소를 리턴하는 목록이 true있습니다. Java 8에서 이것을 달성하는 유일한 방법은 다음과 같습니다.

lst.stream()
    .filter(x -> x > 5)
    .findFirst()

그러나 필터가 적어도 내 이해 (잘못 될 수 있음)까지 전체 목록을 스캔하므로 비효율적 인 것 같습니다. 더 좋은 방법이 있습니까?


53
비효율적이지 않고 Java 8 스트림 구현은 지연 평가되므로 필터는 터미널 작업에만 적용됩니다. 여기에 같은 질문 : stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
마렉 그레고르

1
멋있는. 그것이 내가 바라는 바입니다. 그렇지 않으면 주요 디자인 플롭이되었을 것입니다.
siki

2
목록에 그러한 요소가 포함되어 있는지 여부를 실제로 확인하려는 경우 (아마도 여러 요소 중 첫 번째 요소는 제외) .findAny ()는 이론적으로 병렬 설정에서 더 효율적일 수 있으며 물론 그 의도를보다 명확하게 전달할 수 있습니다.
Joachim Lous

간단한 forEach주기와 비교할 때 힙에 수많은 오브젝트와 수십 개의 동적 메소드 호출이 작성됩니다. 이것이 성능 테스트의 결론에 항상 영향을 미치지는 않지만 핫스팟에서는 스트림 및 유사한 헤비급 구조의 사소한 사용을 피하는 것이 차이를 만듭니다.
Agoston Horvath

답변:


720

아니요, 필터는 전체 스트림을 스캔하지 않습니다. 지연 연산을 반환하는 중간 연산입니다 (실제로 모든 중간 연산이 지연 스트림을 반환 함). 설득하기 위해 다음 테스트를 수행하면됩니다.

List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("will filter " + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

어떤 출력 :

will filter 1
will filter 10
10

스트림의 첫 두 요소 만 실제로 처리된다는 것을 알 수 있습니다.

그래서 당신은 당신의 접근 방식으로 완벽하게 갈 수 있습니다.


37
참고로, get();스트림 파이프 라인에 어떤 값을 공급하는지 알고 결과가있을 것이기 때문에 여기에서 사용 했습니다. 실제로, 당신은 사용하지 말아야 get();하지만, orElse()/ orElseGet()/ orElseThrow()작업이 요소가 발생합니다 스트림 파이프 라인에 적용하면 당신이 알고하지 않을 수 있습니다로 (대신 NSEE의보다 의미있는 오류).
Alexis C.

31
.findFirst().orElse(null);예를 들어
Gondy

20
orElse null을 사용하지 마십시오. 반 패턴이어야합니다. 옵션에 모두 포함되어 있으므로 왜 NPE를 위험에 처해야합니까? Optional을 다루는 것이 더 좋은 방법이라고 생각합니다. 옵션을 사용하기 전에 isPresent ()로 선택적을 테스트하십시오.
BeJay

@BeJay 이해가되지 않습니다. 대신에 무엇을 사용해야 orElse합니까?
존 헨켈

3
@JohnHenckel 나는 BeJay가 의미하는 바는 Optional타입 으로 남겨 두어야한다는 것 .findFirst입니다. Optional을 사용하는 것 중 하나는 개발자 가 check null대신 seg 를 다루지 않아도되도록 도와주는 것입니다 . Optional 인터페이스의 다른 부분을 myObject != null확인 myOptional.isPresent()하거나 사용할 수 있습니다 . 그것이 더 명확 해졌습니까?
AMTerp

102

그러나 필터가 전체 목록을 스캔하므로 비효율적 인 것 같습니다.

아닙니다. 술어를 만족시키는 첫 번째 요소가 발견되는 즉시 "중단"합니다. 스트림 패키지 javadoc 에서 게으름에 대해 더 자세히 읽을 수 있습니다 . 특히 (강조 광산) :

필터링, 매핑 또는 중복 제거와 같은 많은 스트림 작업을 느리게 구현하여 최적화 기회를 제공 할 수 있습니다. 예를 들어, "세 개의 연속 모음으로 첫 번째 문자열 찾기"는 모든 입력 문자열을 검사 할 필요는 없습니다. 스트림 작업은 중간 (스트림 생성) 작업과 터미널 (값 또는 부작용 발생) 작업으로 구분됩니다. 중간 작업은 항상 게으르다.


5
이 답변은 나에게 더 유익하고 방법뿐만 아니라 그 이유를 설명합니다. 나는 새로운 중간 작업이 항상 게으르지 않습니다. Java 스트림은 계속해서 나를 놀라게합니다.
kevinarpe

30
return dataSource.getParkingLots()
                 .stream()
                 .filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
                 .findFirst()
                 .orElse(null);

객체 목록에서 하나의 객체 만 필터링해야했습니다. 그래서 나는 이것을 사용했는데 도움이되기를 바랍니다.


BETTER : 부울 리턴 값을 찾기 때문에 널 검사를 추가하여 더 잘 수행 할 수 있습니다. return dataSource.getParkingLots (). stream (). filter (parkingLot-> Objects.equals (parkingLot.getId (), id)) .findFirst (). orElse (null)! = null;
shreedhar bhat

1
@shreedharbhat하지 않아도됩니다 .orElse(null) != null. 대신 선택적 API ( .isPresent예 :)를 사용하십시오 .findFirst().isPresent().
AMTerp

@shreedharbhat은 먼저 모든 부울 반환 값을 찾지 못했습니다. 두 번째로, 그들이 글을 쓰는 것이 더 깨끗했을 것입니다.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
AjaxLeung

13

Alexis C 의 답변 외에도 검색하려는 요소가 있는지 확실하지 않은 배열 목록으로 작업하는 경우이를 사용하십시오.

Integer a = list.stream()
                .peek(num -> System.out.println("will filter " + num))
                .filter(x -> x > 5)
                .findFirst()
                .orElse(null);

그럼 당신은 단순히 여부를 확인할 수 있습니다 A가 있습니다 null.


1
예를 고쳐야합니다. 일반 int에 null을 할당 할 수 없습니다. stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
RubioRic

게시물을 수정했습니다. 정수 목록에서 검색 할 때 0이 유효한 결과 일 수 있습니다. 변수 유형을 정수로 바꾸고 기본값을 null로 바꿨습니다.
RubioRic

0

import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

// Stream is ~30 times slower for same operation...
public class StreamPerfTest {

    int iterations = 100;
    List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);


    // 55 ms
    @Test
    public void stream() {

        for (int i = 0; i < iterations; i++) {
            Optional<Integer> result = list.stream()
                    .filter(x -> x > 5)
                    .findFirst();

            System.out.println(result.orElse(null));
        }
    }

    // 2 ms
    @Test
    public void loop() {

        for (int i = 0; i < iterations; i++) {
            Integer result = null;
            for (Integer walk : list) {
                if (walk > 5) {
                    result = walk;
                    break;
                }
            }
            System.out.println(result);
        }
    }
}

0

개선 된 One-Liner 답변 : 부울 반환 값을 찾고 있다면 isPresent를 추가하여 더 잘 수행 할 수 있습니다.

return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();

부울 리턴 값을 원하면 anyMatch
AjaxLeung
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.