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;
}
}
이 코드는 User
ID를 기반으로 찾습니다 . 그러나 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
로를
- 수집기를
List
사용하여 객체를Collectors.toList()
수집합니다. - 끝에 추가 피니셔를 적용하면 단일 요소가 반환되거나
IllegalStateException
if 가 발생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인지 확인하고 유일한 요소를 얻는 것입니다.
대안은 축소를 사용하는 것입니다 (이 예에서는 문자열을 사용하지만을 포함하여 모든 객체 유형에 쉽게 적용 할 수 있음 User
).
List<String> list = ImmutableList.of("one", "two", "three", "four", "five", "two");
String match = list.stream().filter("two"::equals).reduce(thereCanBeOnlyOne()).get();
//throws NoSuchElementException if there are no matching elements - "zero"
//throws RuntimeException if duplicates are found - "two"
//otherwise returns the match - "one"
...
//Reduction operator that throws RuntimeException if there are duplicates
private static <T> BinaryOperator<T> thereCanBeOnlyOne()
{
return (a, b) -> {throw new RuntimeException("Duplicate elements found: " + a + " and " + b);};
}
따라서 User
당신 과 함께하는 경우 :
User match = users.stream().filter((user) -> user.getId() < 0).reduce(thereCanBeOnlyOne()).get();
사용하여 Collector
:
public static <T> Collector<T, ?, Optional<T>> toSingleton() {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty()
);
}
용법:
Optional<User> result = users.stream()
.filter((user) -> user.getId() < 0)
.collect(toSingleton());
우리 Optional
는 일반적으로 Collection
정확히 하나의 요소를 포함 한다고 가정 할 수 없으므로를 반환합니다 . 이 경우에 대해 이미 알고 있다면 다음으로 전화하십시오.
User user = result.orElseThrow();
이로 인해 발신자에게 오류를 처리해야하는 부담이 발생합니다.
구아바는 있다 Collector
라고 이것을 MoreCollectors.onlyElement()
.
감소 사용
이것은 내가 찾은 더 간단하고 유연한 방법입니다 (@prunge 답변 기반)
Optional<User> user = users.stream()
.filter(user -> user.getId() == 1)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple elements: " + a + ", " + b);
})
이렇게하면 얻을 수 있습니다 :
- 옵션-항상 개체와 함께 또는
Optional.empty()
없는 경우 - 하나 이상의 요소가있는 경우 예외 (결국 사용자 정의 유형 / 메시지 포함)
RxJava (매우 강력한 반응성 확장 라이브러리)를 사용할 수 있습니다
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User userFound = Observable.from(users)
.filter((user) -> user.getId() == 1)
.single().toBlocking().first();
단일 작업자는 어떠한 사용자 또는이 두 개 이상의 사용자가 발견되지 않는 경우 예외를 던진다.
으로 Collectors.toMap(keyMapper, valueMapper)
동일한 키를 가진 여러 항목을 처리하는 데 사용하는 던지는 합병은 간단합니다 :
List<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
int id = 1;
User match = Optional.ofNullable(users.stream()
.filter(user -> user.getId() == id)
.collect(Collectors.toMap(User::getId, Function.identity()))
.get(id)).get();
당신은 얻을 것이다 IllegalStateException
중복 키 위해. 그러나 결국 코드를 사용하여 코드를 더 읽을 수 있는지 확실하지 않습니다 if
.
I am using those two collectors:
public static <T> Collector<T, ?, Optional<T>> zeroOrOne() {
return Collectors.reducing((a, b) -> {
throw new IllegalStateException("More than one value was returned");
});
}
public static <T> Collector<T, ?, T> onlyOne() {
return Collectors.collectingAndThen(zeroOrOne(), Optional::get);
}
If you don't mind using a 3rd party library, SequenceM
from cyclops-streams (and LazyFutureStream
from simple-react) both a have single & singleOptional operators.
singleOptional()
throws an exception if there are 0
or more than 1
elements in the Stream
, otherwise it returns the single value.
String result = SequenceM.of("x")
.single();
SequenceM.of().single(); // NoSuchElementException
SequenceM.of(1, 2, 3).single(); // NoSuchElementException
String result = LazyFutureStream.fromStream(Stream.of("x"))
.single();
singleOptional()
returns Optional.empty()
if there are no values or more than one value in the Stream
.
Optional<String> result = SequenceM.fromStream(Stream.of("x"))
.singleOptional();
//Optional["x"]
Optional<String> result = SequenceM.of().singleOptional();
// Optional.empty
Optional<String> result = SequenceM.of(1, 2, 3).singleOptional();
// Optional.empty
Disclosure - I am the author of both libraries.
I think this way is more simple:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.findFirst().get();
I went with the direct-approach and just implemented the thing:
public class CollectSingle<T> implements Collector<T, T, T>, BiConsumer<T, T>, Function<T, T>, Supplier<T> {
T value;
@Override
public Supplier<T> supplier() {
return this;
}
@Override
public BiConsumer<T, T> accumulator() {
return this;
}
@Override
public BinaryOperator<T> combiner() {
return null;
}
@Override
public Function<T, T> finisher() {
return this;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
@Override //accumulator
public void accept(T ignore, T nvalue) {
if (value != null) {
throw new UnsupportedOperationException("Collect single only supports single element, "
+ value + " and " + nvalue + " found.");
}
value = nvalue;
}
@Override //supplier
public T get() {
value = null; //reset for reuse
return value;
}
@Override //finisher
public T apply(T t) {
return value;
}
}
with the JUnit test:
public class CollectSingleTest {
@Test
public void collectOne( ) {
List<Integer> lst = new ArrayList<>();
lst.add(7);
Integer o = lst.stream().collect( new CollectSingle<>());
System.out.println(o);
}
@Test(expected = UnsupportedOperationException.class)
public void failOnTwo( ) {
List<Integer> lst = new ArrayList<>();
lst.add(7);
lst.add(8);
Integer o = lst.stream().collect( new CollectSingle<>());
}
}
This implementation not threadsafe.
Have you tried this
long c = users.stream().filter((user) -> user.getId() == 1).count();
if(c > 1){
throw new IllegalStateException();
}
long count()
Returns the count of elements in this stream. This is a special case of a reduction and is equivalent to:
return mapToLong(e -> 1L).sum();
This is a terminal operation.
Source: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
참고URL : https://stackoverflow.com/questions/22694884/filter-java-stream-to-1-and-only-1-element
'Programming' 카테고리의 다른 글
helper와 helper_method는 무엇을합니까? (0) | 2020.05.10 |
---|---|
간단한 파이썬 루프를 어떻게 병렬화합니까? (0) | 2020.05.10 |
Mac에 R 설치-경고 메시지 :“C”를 사용하여 LC_CTYPE 설정에 실패했습니다 (0) | 2020.05.09 |
이중 중괄호와 AngularJS-Twig 충돌 (0) | 2020.05.09 |
세트를 변환하는 가장 간결한 방법 (0) | 2020.05.09 |