[java] Java 스트림을 1 및 1 요소로 필터링

Java 8을 사용 Stream하여의 요소를 찾으려고합니다 LinkedList. 그러나 필터 기준과 일치하는 항목이 하나만 있음을 보증하고 싶습니다.

이 코드를 보자 :

public static void main(String[] args) {

    LinkedList<User> users = new LinkedList<>();
    users.add(new User(1, "User1"));
    users.add(new User(2, "User2"));
    users.add(new User(3, "User3"));

    User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
    System.out.println(match.toString());
}

static class User {

    @Override
    public String toString() {
        return id + " - " + username;
    }

    int id;
    String username;

    public User() {
    }

    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public int getId() {
        return id;
    }
}

이 코드는 UserID를 기반으로 찾습니다 . 그러나 User필터와 일치 하는 수를 보장 할 수는 없습니다 .

필터 라인을 다음으로 변경 :

User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();

던질 것입니다 NoSuchElementException(좋은!)

그래도 여러 개의 일치 항목이 있으면 오류가 발생하도록하고 싶습니다. 이 방법이 있습니까?



답변

맞춤 만들기 Collector

public static <T> Collector<T, ?, T> toSingleton() {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                if (list.size() != 1) {
                    throw new IllegalStateException();
                }
                return list.get(0);
            }
    );
}

우리가 사용하는 Collectors.collectingAndThen우리의 원하는 구성하기 Collector로를

  1. 수집기를 List사용하여 객체를 Collectors.toList()수집합니다.
  2. 끝에 추가 피니셔를 적용하면 단일 요소가 반환되거나 IllegalStateExceptionif 가 발생 list.size != 1합니다.

로 사용 :

User resultUser = users.stream()
        .filter(user -> user.getId() > 0)
        .collect(toSingleton());

그런 다음 원하는대로 이것을 사용자 정의 할 수 있습니다. Collector예를 들어, 예외를 생성자에서 인수로 지정하고 두 값을 허용하도록 조정하십시오.

대체로 우아하지 않은 대안 :

당신은 관련된 ‘해결’사용 peek()과를 AtomicInteger하지만, 정말 당신은을 사용해서는 안됩니다.

당신이 할 수있는 일은 다음 List과 같이 그것을 수집하는 것입니다 .

LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
List<User> resultUserList = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.toList());
if (resultUserList.size() != 1) {
    throw new IllegalStateException();
}
User resultUser = resultUserList.get(0);


답변

완전성을 위해 다음은 @prunge의 탁월한 답변에 해당하는 ‘한 줄짜리’입니다.

User user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .reduce((a, b) -> {
            throw new IllegalStateException("Multiple elements: " + a + ", " + b);
        })
        .get();

이것은 스트림에서 유일한 일치하는 요소를 가져 와서

  • NoSuchElementException 스트림이 비어있는 경우
  • IllegalStateException 스트림에 둘 이상의 일치하는 요소가 포함 된 경우

이 접근법의 변형은 예외를 조기에 던지는 것을 피하고 대신 Optional단독 요소를 포함하거나 0 또는 다중 요소가있는 경우 아무것도 없음 (빈)으로 결과를 나타냅니다 .

Optional<User> user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.reducing((a, b) -> null));


답변

사용자 정의 작성과 관련된 다른 대답 Collector은 아마도 더 효율적일 것입니다 (예 : Louis Wasserman ‘s , +1). 간결성을 원한다면 다음을 제안합니다.

List<User> result = users.stream()
    .filter(user -> user.getId() == 1)
    .limit(2)
    .collect(Collectors.toList());

그런 다음 결과 목록의 크기를 확인하십시오.

if (result.size() != 1) {
  throw new IllegalStateException("Expected exactly one user but got " + result);
User user = result.get(0);
}


답변

구아바MoreCollectors.onlyElement()여기서 옳은 일을합니다. 그러나 직접 해야하는 경우이 작업을 수행 할 수 있습니다 Collector.

<E> Collector<E, ?, Optional<E>> getOnly() {
  return Collector.of(
    AtomicReference::new,
    (ref, e) -> {
      if (!ref.compareAndSet(null, e)) {
         throw new IllegalArgumentException("Multiple values");
      }
    },
    (ref1, ref2) -> {
      if (ref1.get() == null) {
        return ref2;
      } else if (ref2.get() != null) {
        throw new IllegalArgumentException("Multiple values");
      } else {
        return ref1;
      }
    },
    ref -> Optional.ofNullable(ref.get()),
    Collector.Characteristics.UNORDERED);
}

… 또는 Holder대신 자신의 유형을 사용합니다 AtomicReference. 원하는 Collector만큼 재사용 할 수 있습니다 .


답변

구아바 MoreCollectors.onlyElement()( JavaDoc )를 사용하십시오 .

IllegalArgumentException스트림이 둘 이상의 요소로 구성되어 NoSuchElementException있고 스트림이 비어 있으면 원하는 것을 수행하고 throw합니다 .

용법:

import static com.google.common.collect.MoreCollectors.onlyElement;

User match =
    users.stream().filter((user) -> user.getId() < 0).collect(onlyElement());


답변

스트림에서 지원하지 않는 이상한 작업을 수행 할 수있는 “이스케이프 해치”작업은 다음을 요청하는 것입니다 Iterator.

Iterator<T> it = users.stream().filter((user) -> user.getId() < 0).iterator();
if (!it.hasNext())
    throw new NoSuchElementException();
else {
    result = it.next();
    if (it.hasNext())
        throw new TooManyElementsException();
}

구아바는 Iterator유일한 요소 를 가져 와서 유일한 요소 를 가져 와서 0 개 또는 여러 개의 요소가있는 경우 던지는데 여기에서 맨 아래 n-1 줄을 바꿀 수 있습니다.


답변

최신 정보

@Holger의 의견에 대한 좋은 제안 :

Optional<User> match = users.stream()
              .filter((user) -> user.getId() > 1)
              .reduce((u, v) -> { throw new IllegalStateException("More than one ID found") });

원래 답변

예외는에 의해 발생 Optional#get하지만 도움이되지 않는 요소가 두 개 이상인 경우. 하나의 항목 만 허용하는 컬렉션의 사용자를 수집 할 수 있습니다 (예 :

User match = users.stream().filter((user) -> user.getId() > 1)
                  .collect(toCollection(() -> new ArrayBlockingQueue<User>(1)))
                  .poll();

을 던지지 java.lang.IllegalStateException: Queue full만 너무 해키 느낌.

또는 옵션과 함께 축소를 사용할 수 있습니다.

User match = Optional.ofNullable(users.stream().filter((user) -> user.getId() > 1)
                .reduce(null, (u, v) -> {
                    if (u != null && v != null)
                        throw new IllegalStateException("More than one ID found");
                    else return u == null ? v : u;
                })).get();

감소는 본질적으로 다음을 반환합니다.

  • 사용자가 없으면 null
  • 하나만 발견되면 사용자
  • 둘 이상이 발견되면 예외가 발생합니다.

그런 다음 결과는 선택 사항으로 래핑됩니다.

그러나 가장 간단한 해결책은 아마도 컬렉션으로 수집하고 크기가 1인지 확인하고 유일한 요소를 얻는 것입니다.