Java 8 Различается по свойству


456

В Java 8, как я могу отфильтровать коллекцию, используя StreamAPI, проверив отличимость свойства каждого объекта?

Например, у меня есть список Personобъектов, и я хочу удалить людей с тем же именем,

persons.stream().distinct();

Будет использовать проверку равенства по умолчанию для Personобъекта, поэтому мне нужно что-то вроде,

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

К сожалению, distinct()метод не имеет такой перегрузки. PersonМожно ли сделать это кратко, не изменяя проверку равенства внутри класса?

Ответы:


557

Считайте фильтром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 () для произвольного ключа? )


27
Я думаю, для лучшей совместимости аргумент должен быть Function<? super T, ?>, а не Function<? super T, Object>. Также следует отметить, что для упорядоченного параллельного потока это решение не гарантирует, какой объект будет извлечен (в отличие от обычного distinct()). Также для последовательных потоков есть дополнительные издержки при использовании CHM (чего нет в решении @nosid). Наконец, это решение нарушает контракт filterметода, предикат которого должен быть без состояния, как указано в JavaDoc. Тем не менее проголосовал.
Тагир Валеев

3
@java_newbie Возвращенный экземпляр Predicate distinctByKeyне имеет представления о том, используется ли он в параллельном потоке. Он использует CHM, если он используется параллельно, хотя это добавляет накладные расходы в последовательном случае, как отметил Тагир Валеев выше.
Стюарт Маркс

5
@holandaGo Это не удастся, если вы сохраните и повторно используете экземпляр Predicate, возвращенный distinctByKey. Но это работает, если вы вызываете distinctByKeyкаждый раз, так что каждый раз создается новый экземпляр Predicate.
Стюарт Маркс

3
@ Чинмей нет, не должно. Если вы используете .filter(distinctByKey(...)). Он выполнит метод один раз и вернет предикат. Таким образом, в основном карта уже используется повторно, если вы правильно используете ее в потоке. Если вы сделаете карту статичной, карта будет доступна всем пользователям. Таким образом, если у вас есть два потока, использующих это distinctByKey(), оба будут использовать одну и ту же карту, а это не то, что вам нужно.
g00glen00b

3
Это ооочень умный и совершенно неочевидный. Как правило , это сохраняющее состояние лямбды и лежащая в основе CallSiteбудет связанно с get$Lambdaметодом - это будет возвращать новый экземпляр Predicateвсе время, но эти случаи будут один и те же , mapи function, насколько я понимаю. Очень хорошо!
Евгений,

152

Альтернативой может быть размещение людей на карте с использованием имени в качестве ключа:

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

Обратите внимание, что лицо, которое сохраняется, в случае дублирования имени, будет первым зарегистрированным.


23
@skiwi: как вы думаете, есть способ реализовать это distinct()без накладных расходов? Как любая реализация узнает, видел ли она объект раньше, фактически не запоминая все различные значения, которые она видела? Так что накладные расходыtoMap и, distinctскорее всего, одинаковые.
Хольгер

1
@ Хольгер: Возможно, я ошибался, потому что не думал, что это distinct()создает сама верхняя часть.
Скиви

2
И, очевидно, это портит первоначальный порядок списка
Филипп

10
@ Филипп: можно исправить, изменив на persons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
Хольгер

1
@DanielEarwicker этот вопрос о "различаются по свойству". Потребовалось бы отсортировать поток по одному и тому же свойству , чтобы иметь возможность использовать его в своих интересах. Во-первых, OP никогда не указывал, что поток отсортирован вообще. Во-вторых, потоки не могут определить, отсортированы ли они по определенному свойству . В-третьих, для выполнения того, что вы предлагаете, не существует подлинной потоковой операции «по свойствам». В-четвертых, на практике есть только два способа получить такой отсортированный поток. Сортированный source ( TreeSet), который в любом случае уже различен или sortedв потоке, который также буферизует все элементы.
Хольгер

101

Вы можете обернуть объекты человека в другой класс, который сравнивает только имена людей. После этого вы разворачиваете обернутые объекты, чтобы снова получить поток людей. Операции потока могут выглядеть следующим образом:

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();
    }
}


5
@StuartCaie Не совсем ... нет запоминания, и дело не в производительности, а в адаптации к существующему API.
Марко Топольник

6
Также могут помочь com.google.common.base.Equivalence.wrap (S) и com.google.common.base.Equivalence.Wrapper.get ().
bjmi

Вы можете сделать класс-обертку универсальным и параметризованным функцией извлечения ключей.
Лии

equalsМетод может быть упрощен доreturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Holger

55

Другое решение, использующее Set. Может быть не идеальное решение, но оно работает

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

Или, если вы можете изменить исходный список, вы можете использовать метод removeIf

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

2
Это лучший ответ, если вы не используете сторонние библиотеки!
Манодж Шреста

5
используя гениальную идею, что Set.add возвращает true, если этот набор еще не содержит указанный элемент. +1
Люви

Я считаю, что этот метод не работает для обработки параллельных потоков, поскольку он не является потокобезопасным.
LoBo

@LoBo Наверное, нет. Это просто идея, которая подойдет для простых случаев. Пользователи могут расширить его для обеспечения безопасности потоков / параллелизма.
Сантош

Интересный подход, но выглядит как анти-шаблон для изменения внешней коллекции (набора) при фильтрации потока в другой коллекции (персоны) ...
Джастин Роу

31

Существует более простой подход с использованием TreeSet с пользовательским компаратором.

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

4
Я думаю, что ваш ответ помогает к порядку, а не к уникальности. Однако это помогло мне подумать, как это сделать. Проверьте здесь: stackoverflow.com/questions/1019854/…
janagn

Имейте в виду, что вы будете платить за сортировку элементов здесь, и нам не нужно сортировать, чтобы найти дубликаты или даже удалить дубликаты.
писарук

12
Comparator.comparing (Person :: getName)
Жан-Франсуа Савар

24

Мы также можем использовать RxJava (очень мощная библиотека реактивных расширений )

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

или

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

Rx потрясающий, но это плохой ответ. Observableна основе толчка, тогда как на основе Streamтяги. stackoverflow.com/questions/30216979/…
sdgfsdh

4
вопрос задайте для решения java8, не обязательно использующего поток. Мой ответ показывает, что java8 stream api менее мощен, чем rx api
frhack

1
Использование реактора , это будетFlux.fromIterable(persons).distinct(p -> p.getName())
Ritesh

Вопрос буквально гласит «используя StreamAPI», а не «не обязательно используя stream». Тем не менее, это отличное решение проблемы XY фильтрации потока по различным значениям.
М. Джастин

12

Вы можете использовать groupingByколлектор:

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

Если вы хотите иметь другой поток, вы можете использовать это:

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

11

Вы можете использовать distinct(HashingStrategy)метод в Eclipse Collections .

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

Если вы можете personsвыполнить рефакторинг для реализации интерфейса Коллекции Eclipse, вы можете вызвать метод непосредственно в списке.

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 Collections.


Метод DifferentBy был добавлен в Eclipse Collections 9.0, что может еще больше упростить это решение. medium.com/@donraab/...
Дональд Рааб

10

Я рекомендую использовать Vavr , если можете. С помощью этой библиотеки вы можете сделать следующее:

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

Ранее известный как "javaslang" библиотека.
user11153

9

Вы можете использовать библиотеку StreamEx :

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

К сожалению, этот метод отличной библиотеки StreamEx плохо спроектирован - он сравнивает равенство объектов вместо использования equals. Это может работать Stringблагодаря интернированию строк, но может и не работать .
Крутящий момент

7

Расширяя ответ Стюарта Маркса, это можно сделать более коротким способом и без одновременной карты (если вам не нужны параллельные потоки):

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());

2
Этот не принимает во внимание, что поток может быть параллельным.
brunnsbe

Спасибо за комментарий, я обновил свой ответ. Если вам не нужен параллельный поток, отсутствие одновременных карт дает вам гораздо лучшую производительность.
Войцех Гурски

Ваш код, вероятно, будет работать для параллельных коллекций, если вы создадите Collections.synchronizedSet(new HashSet<>())вместо него. Но это, вероятно, будет медленнее, чем с ConcurrentHashMap.
Лии

7

Подобный подход, который использовал Саид Заринфам, но больше стиля Java 8 :)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());

1
Я бы заменил линию карты на flatMap(plans -> plans.stream().findFirst().stream())нее, чтобы избежать использования get on Optional
Эндрю Снек,

Может быть , это тоже нормально: flatMap (. Планы -> plans.stream () предел (1))
Rrr

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())



5

Мой подход к этому состоит в том, чтобы сгруппировать все объекты с одинаковым свойством вместе, затем обрезать группы до размера 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());

3

Список отдельных объектов можно найти с помощью:

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

2

Самый простой способ реализовать это - перейти к функции сортировки, поскольку она уже предоставляет опцию, Comparatorкоторая может быть создана с использованием свойства элемента. Затем вы должны отфильтровать дубликаты, что можно сделать с помощью statefull, 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 */;

Конечно, statefull Predicateне является потокобезопасным, однако, если вам это нужно, вы можете переместить эту логику в a Collectorи позволить потоку позаботиться о безопасности потоков при использовании вашего Collector. Это зависит от того, что вы хотите сделать с потоком различных элементов, которые вы не сказали нам в своем вопросе.


1

Основываясь на ответе @ josketres, я создал универсальный вспомогательный метод:

Вы можете сделать это более дружественным к Java 8, создав Collector .

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

Может быть кому-нибудь пригодится. У меня было немного другое требование. Имея список объектов Aот сторонних производителей, удалите все объекты, имеющие одинаковое A.bполе для одного и того же A.id(несколько Aобъектов с одинаковыми A.idв списке). Ответ потока от Тагира Валеева вдохновил меня на использование обычая, Collectorкоторый возвращает Map<A.id, List<A>>. Простой 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)); }

1

У меня была ситуация, когда я должен был получить отдельные элементы из списка на основе 2 ключей. Если вы хотите различить на основе двух ключей или может составной ключ, попробуйте это

class Person{
    int rollno;
    String name;
}
List<Person> personList;


Function<Person, List<Object>> compositeKey = personList->
        Arrays.<Object>asList(personList.getName(), personList.getRollno());

Map<Object, List<Person>> map = personList.stream().collect(Collectors.groupingBy(compositeKey, Collectors.toList()));

List<Object> duplicateEntrys = map.entrySet().stream()`enter code here`
        .filter(settingMap ->
                settingMap.getValue().size() > 1)
        .collect(Collectors.toList());

0

В моем случае мне нужно было контролировать то, что было предыдущим элементом. Затем я создал предикат с отслеживанием состояния, в котором я контролировал, отличался ли предыдущий элемент от текущего элемента, в этом случае я сохранял его.

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

Мое решение в этом списке:

List<HolderEntry> result ....

List<HolderEntry> dto3s = new ArrayList<>(result.stream().collect(toMap(
            HolderEntry::getId,
            holder -> holder,  //or Function.identity() if you want
            (holder1, holder2) -> holder1 
    )).values());

В моей ситуации я хочу найти отличные значения и поместить их в список.


0

Хотя ответ с наибольшим количеством голосов является абсолютно лучшим по сравнению с Java 8, в то же время он является абсолютно худшим с точки зрения производительности. Если вы действительно хотите плохое приложение с низкой производительностью, тогда используйте его. Простое требование извлечения уникального набора личных имен должно быть достигнуто просто «для каждого» и «набором». Ситуация становится еще хуже, если список превышает 10.

Предположим, у вас есть коллекция из 20 объектов, например:

public static final List<SimpleEvent> testList = Arrays.asList(
            new SimpleEvent("Tom"), new SimpleEvent("Dick"),new SimpleEvent("Harry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Huckle"),new SimpleEvent("Berry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("Cherry"),
            new SimpleEvent("Roses"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("gotya"),
            new SimpleEvent("Gotye"),new SimpleEvent("Nibble"),new SimpleEvent("Berry"),new SimpleEvent("Jibble"));

Где ваш объект SimpleEventвыглядит так:

public class SimpleEvent {

private String name;
private String type;

public SimpleEvent(String name) {
    this.name = name;
    this.type = "type_"+name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getType() {
    return type;
}

public void setType(String type) {
    this.type = type;
}
}

И тест, у вас есть JMH подобного кода (Пожалуйста , обратите внимание, им , используя тот же distinctByKey Predicate упоминается в общепринятом ответа):

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aStreamBasedUniqueSet(Blackhole blackhole) throws Exception{

    Set<String> uniqueNames = testList
            .stream()
            .filter(distinctByKey(SimpleEvent::getName))
            .map(SimpleEvent::getName)
            .collect(Collectors.toSet());
    blackhole.consume(uniqueNames);
}

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aForEachBasedUniqueSet(Blackhole blackhole) throws Exception{
    Set<String> uniqueNames = new HashSet<>();

    for (SimpleEvent event : testList) {
        uniqueNames.add(event.getName());
    }
    blackhole.consume(uniqueNames);
}

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(MyBenchmark.class.getSimpleName())
            .forks(1)
            .mode(Mode.Throughput)
            .warmupBatchSize(3)
            .warmupIterations(3)
            .measurementIterations(3)
            .build();

    new Runner(opt).run();
}

Тогда вы получите результаты Benchmark :

Benchmark                                  Mode  Samples        Score  Score error  Units
c.s.MyBenchmark.aForEachBasedUniqueSet    thrpt        3  2635199.952  1663320.718  ops/s
c.s.MyBenchmark.aStreamBasedUniqueSet     thrpt        3   729134.695   895825.697  ops/s

И, как вы можете видеть, простой For-Each в 3 раза лучше по пропускной способности и меньше по количеству ошибок по сравнению с Java 8 Stream.

Чем выше пропускная способность, тем выше производительность


1
Спасибо, но вопрос был очень конкретным в контексте Stream API
RichK

Да, я согласен, я уже упоминал: «Хотя ответ с наибольшим количеством голосов является абсолютно лучшим ответом по сравнению с Java 8». Проблему можно решить по-разному, и я пытаюсь подчеркнуть, что эта проблема может быть решена просто, а не опасно с потоками Java 8, где опасность заключается в снижении производительности. :)
Абхинав Гангули

0
Here is the example
public class PayRoll {

    private int payRollId;
    private int id;
    private String name;
    private String dept;
    private int salary;


    public PayRoll(int payRollId, int id, String name, String dept, int salary) {
        super();
        this.payRollId = payRollId;
        this.id = id;
        this.name = name;
        this.dept = dept;
        this.salary = salary;
    }
} 

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class Prac {
    public static void main(String[] args) {

        int salary=70000;
        PayRoll payRoll=new PayRoll(1311, 1, "A", "HR", salary);
        PayRoll payRoll2=new PayRoll(1411, 2    , "B", "Technical", salary);
        PayRoll payRoll3=new PayRoll(1511, 1, "C", "HR", salary);
        PayRoll payRoll4=new PayRoll(1611, 1, "D", "Technical", salary);
        PayRoll payRoll5=new PayRoll(711, 3,"E", "Technical", salary);
        PayRoll payRoll6=new PayRoll(1811, 3, "F", "Technical", salary);
        List<PayRoll>list=new ArrayList<PayRoll>();
        list.add(payRoll);
        list.add(payRoll2);
        list.add(payRoll3);
        list.add(payRoll4);
        list.add(payRoll5);
        list.add(payRoll6);


        Map<Object, Optional<PayRoll>> k = list.stream().collect(Collectors.groupingBy(p->p.getId()+"|"+p.getDept(),Collectors.maxBy(Comparator.comparingInt(PayRoll::getPayRollId))));


        k.entrySet().forEach(p->
        {
            if(p.getValue().isPresent())
            {
                System.out.println(p.getValue().get());
            }
        });



    }
}

Output:

PayRoll [payRollId=1611, id=1, name=D, dept=Technical, salary=70000]
PayRoll [payRollId=1811, id=3, name=F, dept=Technical, salary=70000]
PayRoll [payRollId=1411, id=2, name=B, dept=Technical, salary=70000]
PayRoll [payRollId=1511, id=1, name=C, dept=HR, salary=70000]

-2

Если вы хотите, чтобы список лиц, следующий будет простой способ

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

Кроме того, если вы хотите найти отдельный или уникальный список имен , а не Person , вы также можете использовать следующие два метода.

Способ 1: использование distinct

persons.stream().map(x->x.getName()).distinct.collect(Collectors.toList());

Способ 2: использование HashSet

Set<E> set = new HashSet<>();
set.addAll(person.stream().map(x->x.getName()).collect(Collectors.toList()));

2
Это создает список имен, а не Persons.
Халк

1
Это именно то, что я искал. Мне был нужен однострочный метод для устранения дубликатов при преобразовании коллекции в другую. Спасибо.
Радж

-3

Самый простой код, который вы можете написать:

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());

12
Это
даст
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.