web-dev-qa-db-ja.com

Java 8プロパティ別

Java 8では、各オブジェクトのプロパティの明確さをチェックすることによってStream APIを使用してコレクションをフィルターすることができますか?

たとえば、Personオブジェクトのリストがあり、同じ名前の人を削除したいとします。

persons.stream().distinct();

Personオブジェクトに対してデフォルトの等価性チェックを使用するので、以下のようなものが必要です。

persons.stream().distinct(p -> p.getName());

残念ながら、distinct()メソッドにはそのようなオーバーロードはありません。 Personクラス内の等価性チェックを修正しなくても、これを簡潔に行うことは可能ですか?

340
RichK

distinct ステートフルフィルタ であると考えてください。これは、以前に見たものに関する状態を維持し、与えられた要素が初めて見られたかどうかを返す述語を返す関数です。

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

それからあなたは書くことができます:

persons.stream().filter(distinctByKey(Person::getName))

ストリームが順序付けられて並列に実行される場合、これはdistinct()が行うように、最初のものの代わりに、重複の中から 任意の 要素を保存することに注意してください。

(これは、この質問に対する 私の答え と本質的に同じです: 任意のキーに対するJava Lambda Stream Distinct()?

410
Stuart Marks

別の方法としては、名前をキーとして使用して人を地図に配置します。

persons.collect(toMap(Person::getName, p -> p, (p, q) -> p)).values();

名前が重複している場合は、保持されているPersonが最初に入力されます。

104
wha'eve'

人物オブジェクトを別のクラスにラップして、人物の名前だけを比較することができます。その後、ラップされたオブジェクトのラップを解除して、人物ストリームを再び取得します。ストリーム操作は次のようになります。

persons.stream()
    .map(Wrapper::new)
    .distinct()
    .map(Wrapper::unwrap)
    ...;

クラスWrapperは次のようになります。

class Wrapper {
    private final Person person;
    public Wrapper(Person person) {
        this.person = person;
    }
    public Person unwrap() {
        return person;
    }
    public boolean equals(Object other) {
        if (other instanceof Wrapper) {
            return ((Wrapper) other).person.getName().equals(person.getName());
        } else {
            return false;
        }
    }
    public int hashCode() {
        return person.getName().hashCode();
    }
}
78
nosid

カスタムコンパレータを使用してTreeSetを使用するより簡単な方法があります。

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
));
23
josketres

Setを使ったもう一つの解決策。理想的な解決策ではないかもしれませんが、うまくいきます。

Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

または元のリストを変更できる場合は、 removeIf methodを使用できます。

persons.removeIf(p -> !set.add(p.getName()));
23
Santhosh

RxJava (非常に強力な 反応的拡張 library)を使うこともできます。

Observable.from(persons).distinct(Person::getName)

または

Observable.from(persons).distinct(p -> p.getName())
23
frhack

distinct(HashingStrategy)メソッドは Eclipse Collections で使用できます。

List<Person> persons = ...;
MutableList<Person> distinct =
    ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));

Eclipse Collectionsインターフェースを実装するためにpersonsをリ​​ファクタリングできる場合は、リスト上で直接メソッドを呼び出すことができます。

MutableList<Person> persons = ...;
MutableList<Person> distinct =
    persons.distinct(HashingStrategies.fromFunction(Person::getName));

HashingStrategy は、equalsとhashcodeのカスタム実装を定義することを可能にする単なる戦略インタフェースです。

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

注:私はEclipseコレクションのコミッターです。

10
Craig P. Motlin

StreamEx libraryを使用できます。

StreamEx.of(persons)
        .distinct(Person::getName)
        .toList()
8
Sllouyssgort

できれば Vavr の使用をお勧めします。このライブラリを使用すると、次のことができます。

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection
8

groupingByコレクターを使うことができます:

persons.collect(groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));

別のストリームが欲しいなら、これを使うことができます:

persons.collect(groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));
6
Saeed Zarinfam

Stuart Marksの答えを拡張すると、これはより簡単な方法で並行マップなしで実行できます(並列ストリームが不要な場合)。

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    final Set<Object> seen = new HashSet<>();
    return t -> seen.add(keyExtractor.apply(t));
}

それから電話してください。

persons.stream().filter(distinctByKey(p -> p.getName());
6

私は一般的なバージョンを作りました:

private <T, R> Collector<T, ?, Stream<T>> distinctByKey(Function<T, R> keyExtractor) {
    return Collectors.collectingAndThen(
            toMap(
                    keyExtractor,
                    t -> t,
                    (t1, t2) -> t1
            ),
            (Map<R, T> map) -> map.values().stream()
    );
}

例:

Stream.of(new Person("Jean"), 
          new Person("Jean"),
          new Person("Paul")
)
    .filter(...)
    .collect(distinctByKey(Person::getName)) // return a stream of Person with 2 elements, jean and Paul
    .map(...)
    .collect(toList())
6

Saeed Zarinfamが使用したのと同様のアプローチですが、よりJava 8スタイル:)

persons.collect(groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());
5
Alex
Set<YourPropertyType> set = new HashSet<>();
list
        .stream()
        .filter(it -> set.add(it.getYourProperty()))
        .forEach(it -> ...);
4

これをサポートする別のライブラリは jOOλ とその Seq.distinct(Function<T,U>) メソッドです。

Seq.seq(persons).distinct(Person::getName).toList();

フードの下で 、それは 受け入れられた答えと実質的に同じことをします 、しかし。

4

これを実装する最も簡単な方法は、要素のプロパティを使用して作成できるオプションのComparatorをすでに提供しているので、並べ替え機能にジャンプすることです。それから、ソートされたストリームに対してすべての等しい要素が隣接しているという事実を使用するステートフルPredicateを使用して行うことができる重複を除外する必要があります。

Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
    Person previous;
    public boolean test(Person p) {
      if(previous!=null && c.compare(previous, p)==0)
        return false;
      previous=p;
      return true;
    }
})./* more stream operations here */;

もちろん、ステートフルなPredicateはスレッドセーフではありませんが、それが必要な場合は、このロジックをCollectorに移動して、Collectorを使用するときにストリームにスレッドセーフを任せることができます。これはあなたがあなたの質問で私達に言わなかった異なった要素の流れをどうしたいかによって異なります。

2
Holger

@ josketresの答えに基づいて、私は一般的な効用方法を作成しました:

Collector を作成することで、これをよりJava 8に対応させることができます。

public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
    return input.stream()
            .collect(toCollection(() -> new TreeSet<>(comparer)));
}


@Test
public void removeDuplicatesWithDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(7), new C(42), new C(42));
    Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
    assertEquals(2, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 7));
    assertTrue(result.stream().anyMatch(c -> c.value == 42));
}

@Test
public void removeDuplicatesWithoutDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(1), new C(2), new C(3));
    Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
    assertEquals(3, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 1));
    assertTrue(result.stream().anyMatch(c -> c.value == 2));
    assertTrue(result.stream().anyMatch(c -> c.value == 3));
}

private class C {
    public final int value;

    private C(int value) {
        this.value = value;
    }
}
1
Garrett Smith

異なるオブジェクトのリストは、次のものを使って見つけることができます -

 List distnictPersons = persons.stream()
                    .collect(Collectors.collectingAndThen(
                            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
                            ArrayList::new));
1
Naveen Dhalaria

これに対する私のアプローチは、同じプロパティを持つすべてのオブジェクトをまとめてグループ化し、次にグループを1のサイズに切り詰めて、最後にそれらをListとして収集することです。

  List<YourPersonClass> listWithDistinctPersons =   persons.stream()
            //operators to remove duplicates based on person name
            .collect(Collectors.groupingBy(p -> p.getName()))
            .values()
            .stream()
            //cut short the groups to size of 1
            .flatMap(group -> group.stream().limit(1))
            //collect distinct users as list
            .collect(Collectors.toList());
0
uneq95

たぶん誰かに役立つでしょう。もう1つ要件がありました。サードパーティからのオブジェクトAのリストを持つことは、同じA.bに対して同じA.idフィールドを持つものをすべて削除します(リスト内の同じA.idを持つ複数のAオブジェクト)。 ストリームパーティション answer by Tagir ValeevMap<A.id, List<A>>を返すカスタムCollectorを使うよう促しました。単純なflatMapが残りをします。

 public static <T, K, K2> Collector<T, ?, Map<K, List<T>>> groupingDistinctBy(Function<T, K> keyFunction, Function<T, K2> distinctFunction) {
    return groupingBy(keyFunction, Collector.of((Supplier<Map<K2, T>>) HashMap::new,
            (map, error) -> map.putIfAbsent(distinctFunction.apply(error), error),
            (left, right) -> {
                left.putAll(right);
                return left;
            }, map -> new ArrayList<>(map.values()),
            Collector.Characteristics.UNORDERED)); }
0
Aliaksei Yatsau

私の場合、前の要素を制御する必要がありました。次に、前の要素が現在の要素と異なる場合に制御するstateful Predicateを作成しました。その場合は保持しました。

public List<Log> fetchLogById(Long id) {
    return this.findLogById(id)
        .stream().filter(new LogPredicate())
        .collect(Collectors.toList());
}

public class LogPredicate implements Predicate<Log> {

private Log previous;

public boolean test(Log atual) {
    boolean isDifferent = previouws == null || verifyIfDifferentLog(current, previous);

    if (isDifferent) {
        previous = current;
    }
    return isDifferent;
}

private boolean verifyIfDifferentLog(Log current,
                                               Log previous) {
    return !current.getId().equals(previous.getId());
}

}

0
Flavio Oliva