Java 8 แตกต่างกันตามคุณสมบัติ


456

ใน Java 8 ฉันจะกรองคอลเล็กชันโดยใช้StreamAPI ได้อย่างไรโดยตรวจสอบความแตกต่างของคุณสมบัติของแต่ละวัตถุ

ตัวอย่างเช่นฉันมีรายการPersonวัตถุและฉันต้องการลบคนที่มีชื่อเดียวกัน

persons.stream().distinct();

จะใช้การตรวจสอบความเท่าเทียมกันเริ่มต้นสำหรับPersonวัตถุดังนั้นฉันต้องการบางสิ่งเช่น

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

น่าเสียดายที่distinct()วิธีการนี้ไม่มีการโอเวอร์โหลดดังกล่าว หากไม่มีการแก้ไขการตรวจสอบความเท่าเทียมกันในPersonชั้นเรียนเป็นไปได้ไหมที่จะทำอย่างรัดกุม?

คำตอบ:


557

พิจารณาdistinctที่จะเป็นตัวกรอง stateful นี่คือฟังก์ชั่นที่คืนค่าเพรดิเคตที่รักษาสถานะเกี่ยวกับสิ่งที่เห็นก่อนหน้านี้และคืนค่าว่าองค์ประกอบที่กำหนดนั้นถูกมองเห็นเป็นครั้งแรกหรือไม่:

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 () บนคีย์ arbitrary? )


27
ผมคิดว่าเข้ากันได้ดีกว่าข้อโต้แย้งที่ควรจะเป็นไม่ได้Function<? super T, ?> Function<? super T, Object>นอกจากนี้ควรสังเกตว่าสำหรับการสตรีมแบบขนานที่มีคำสั่งวิธีนี้จะไม่รับประกันว่าวัตถุใดที่จะถูกแยกออก (ต่างจากปกติdistinct()) นอกจากนี้สำหรับสตรีมต่อเนื่องจะมีค่าใช้จ่ายเพิ่มเติมสำหรับการใช้ CHM (ซึ่งไม่มีอยู่ใน @nosid solution) ในที่สุดการแก้ปัญหานี้ละเมิดสัญญาของfilterวิธีการที่จะต้องมีสถานะไร้สัญชาติตามที่ระบุไว้ใน JavaDoc upvoted อย่างไรก็ตาม
Tagir Valeev

3
@java_newbie อินสแตนซ์ Predicate ที่ส่งคืนมาโดยdistinctByKeyไม่รู้ว่ามันถูกใช้ภายในสตรีมแบบขนานหรือไม่ มันใช้ CHM ในกรณีที่มีการใช้งานแบบขนานแม้ว่าจะเพิ่มค่าใช้จ่ายในกรณีตามลำดับเป็น Tagir Valeev ระบุไว้ข้างต้น
Stuart Marks

5
@holandaGo distinctByKeyมันจะล้มเหลวถ้าคุณบันทึกและนำมาใช้เช่นคำกริยากลับโดย แต่มันจะทำงานถ้าคุณโทรหาdistinctByKeyแต่ละครั้งเพื่อที่จะสร้างอินสแตนซ์เพรดิเคตใหม่ทุกครั้ง
Stuart Marks

3
@Chinmay ไม่มันไม่ควร .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น่าจะเหมือนกันมาก
Holger

1
@ Holger ฉันอาจจะผิดที่นั่นเพราะฉันไม่ได้คิดว่าdistinct()ตัวเองเป็นค่าใช้จ่าย
skiwi

2
และเห็นได้ชัดว่ามันทำให้คำสั่งดั้งเดิมของรายการ
ยุ่งเหยิง

10
@Phippipp: สามารถแก้ไขได้โดยเปลี่ยนเป็นpersons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
Holger

1
@DanielEarwicker คำถามนี้เกี่ยวกับ "คุณสมบัติแตกต่างกัน" มันจะต้องมีกระแสข้อมูลที่จะเรียงลำดับตามคุณสมบัติเดียวกันเพื่อให้สามารถใช้ประโยชน์จากมัน ประการแรก OP ไม่ได้ระบุว่าสตรีมจะถูกจัดเรียงเลย ประการที่สองสตรีมไม่สามารถตรวจพบว่ามีการเรียงลำดับตามคุณสมบัติบางอย่างหรือไม่ ประการที่สามไม่มีการดำเนินการสตรีม "ที่แตกต่างกันตามคุณสมบัติ" ของแท้เพื่อดำเนินการตามที่คุณแนะนำ ในทางปฏิบัติมีเพียงสองวิธีในการรับสตรีมที่เรียงลำดับ แหล่งที่จัดเรียง ( TreeSet) ซึ่งมีความแตกต่างอยู่แล้วหรือsortedบนสตรีมที่ยังบัฟเฟอร์องค์ประกอบทั้งหมด
Holger

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

13
สิ่งนี้เรียกว่าการแปลงชวา
ร์เซี่ยน

5
@StuartCaie ไม่จริง ... ไม่มีการบันทึกและจุดไม่ได้ประสิทธิภาพ แต่ปรับให้เข้ากับ API ที่มีอยู่
Marko Topolnik

6
com.google.common.base.Equivalence.wrap (S) และ com.google.common.base.Equivalence.Wrapper.get () สามารถช่วยได้เช่นกัน
bjmi

คุณสามารถสร้างคลาส wrapper generic และ parametrized โดยฟังก์ชั่นการแยกคีย์
Lii

equalsวิธีการได้ง่ายไปreturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
โฮล

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
นี่คือคำตอบที่ดีที่สุดหากคุณไม่ได้ใช้ห้องสมุดบุคคลที่สาม!
Manoj Shrestha

5
การใช้แนวคิดที่มีความคิดสร้างสรรค์ซึ่ง Set.add ให้ผลตอบแทนจริงถ้าชุดนี้ยังไม่มีองค์ประกอบที่ระบุ +1
Luvie

ฉันเชื่อว่าวิธีนี้ใช้ไม่ได้กับการประมวลผลสตรีมแบบขนานเนื่องจากไม่ปลอดภัยกับเธรด
LoBo

@LoBo อาจจะไม่ นี่เป็นเพียงความคิดที่จะใช้สำหรับกรณีง่าย ๆ ผู้ใช้สามารถขยายเพื่อความปลอดภัยด้าย / ขนาน
Santhosh

วิธีการที่น่าสนใจ แต่ดูเหมือนจะเป็นแบบต่อต้านการแก้ไขคอลเลกชันภายนอก (ชุด) ในขณะที่กรองกระแสในคอลเลกชันอื่น (คน) ...
Justin Rowe

31

มีวิธีที่ง่ายกว่าในการใช้ TreeSet พร้อมตัวเปรียบเทียบที่กำหนดเอง

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

4
ฉันคิดว่าคำตอบของคุณช่วยในการสั่งซื้อและไม่ให้มีลักษณะเฉพาะ อย่างไรก็ตามมันช่วยให้ฉันตั้งความคิดเกี่ยวกับวิธีการทำ ตรวจสอบที่นี่: stackoverflow.com/questions/1019854/…
janagn

โปรดทราบว่าคุณจะต้องจ่ายราคาสำหรับการเรียงลำดับองค์ประกอบที่นี่และเราไม่จำเป็นต้องเรียงลำดับเพื่อค้นหารายการที่ซ้ำกันหรือแม้กระทั่งลบรายการที่ซ้ำกัน
pisaruk

12
Comparator.comparing (บุคคล :: getName)
Jean-François Savard

24

เรายังสามารถใช้RxJava ( ไลบรารีส่วนขยายรีแอคทีฟที่มีประสิทธิภาพมาก)

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

หรือ

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

Rx นั้นยอดเยี่ยม แต่นี่เป็นคำตอบที่ไม่ดี Observableเป็นแบบ push-based ในขณะที่Streamแบบ pull-based stackoverflow.com/questions/30216979/…
sdgfsdh

4
คำถามถามหาทางออก java8 ไม่จำเป็นต้องใช้กระแส คำตอบของฉันแสดงให้เห็นว่า java8 stream api มี powefull น้อยกว่า rx api
frhack

1
การใช้เครื่องปฏิกรณ์จะเป็นFlux.fromIterable(persons).distinct(p -> p.getName())
Ritesh

คำถามที่ว่า "ใช้StreamAPI" ไม่ใช่ "ไม่จำเป็นต้องใช้สตรีม" ที่กล่าวมานี้เป็นทางออกที่ดีสำหรับปัญหา XY ของการกรองสตรีมไปยังค่าที่แตกต่าง
M. Justin

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 คอลเลกชัน

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

หากคุณสามารถ refactor personsเพื่อใช้อินเตอร์เฟส Eclipse Collections คุณสามารถเรียกเมธอดโดยตรงบนรายการ

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/…
Donald Raab

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 ที่ยอดเยี่ยมนั้นได้รับการออกแบบมาไม่ดี - มันเปรียบเทียบความเท่าเทียมกันของวัตถุแทนที่จะใช้เท่ากับ สิ่งนี้อาจใช้งานStringได้เพราะต้องขอบคุณการฝึกงานแบบสตริง แต่ก็อาจไม่ได้เช่นกัน
แรงบิด

7

การขยายคำตอบของ 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());

2
อันนี้ไม่คำนึงถึงว่ากระแสอาจขนาน
brunnsbe

ขอบคุณสำหรับความคิดเห็นฉันได้อัปเดตคำตอบของฉันแล้ว หากคุณไม่ต้องการกระแสข้อมูลแบบขนานการไม่ใช้แผนที่พร้อมกันจะให้ประสิทธิภาพที่ดีขึ้น
Wojciech Górski

รหัสของคุณอาจใช้กับคอลเล็กชันแบบขนานได้หากคุณสร้างขึ้นCollections.synchronizedSet(new HashSet<>())ใหม่ ConcurrentHashMapแต่มันอาจจะช้ากว่าด้วย
Lii

7

วิธีการที่คล้ายกันซึ่ง Saeed Zarinfam ใช้ แต่มีสไตล์ 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())เพื่อหลีกเลี่ยงการใช้งานบนทางเลือก
Andrew Sneck

อาจเป็นเช่นนี้ก็ได้: flatMap (plans -> plans.stream (). limit (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ไม่ด้ายปลอดภัย แต่ถ้าว่าเป็นความต้องการของคุณคุณสามารถย้ายตรรกะนี้เป็นและให้ดูแลกระแสของหัวข้อความปลอดภัยเมื่อใช้ของคุณCollector Collectorขึ้นอยู่กับสิ่งที่คุณต้องการจะทำกับกระแสขององค์ประกอบที่แตกต่างที่คุณไม่ได้บอกเราในคำถามของคุณ


1

จากคำตอบของ @ josketres ฉันได้สร้างวิธีการใช้งานทั่วไป:

คุณอาจจะทำเรื่องนี้ให้มากขึ้น 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

อาจจะมีประโยชน์สำหรับใครบางคน ฉันมีข้อกำหนดอื่นอีกเล็กน้อย มีรายชื่อวัตถุAจากบุคคลที่สามลบทั้งหมดที่มีA.bเขตข้อมูลเดียวกันสำหรับเดียวกันA.id( AวัตถุหลายA.idรายการในรายการเดียวกัน) พาร์ทิชันกระแสตอบโดยTagir Valeevแรงบันดาลใจให้ผมใช้กำหนดเองซึ่งผลตอบแทน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

ในขณะที่คำตอบ upvoted ที่สูงที่สุดคือคำตอบที่ดีที่สุดอย่างแน่นอน wrt 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รหัสเช่นนี้ (โปรดทราบ im ใช้เดียวกันdistinctByKeyกริยาที่กล่าวถึงในคำตอบที่ได้รับการยอมรับ):

@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                                  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

และอย่างที่คุณเห็นการเรียบง่ายสำหรับแต่ละรายการจะดีกว่า 3 เท่าในปริมาณงานและคะแนนข้อผิดพลาดน้อยกว่าเมื่อเปรียบเทียบกับ Java 8 Stream

ที่สูงขึ้นผ่านที่ดีกว่าผลการดำเนินงาน


1
ขอขอบคุณ แต่คำถามนี้มีความเฉพาะเจาะจงมากในบริบทของ Stream API
RichK

ใช่ฉันเห็นด้วยฉันได้พูดไปแล้วว่า "ในขณะที่คำตอบ upvoted ที่สูงที่สุดคือคำตอบที่ดีที่สุดอย่างแน่นอน wrt Java 8" ปัญหาสามารถแก้ไขได้หลายวิธีและฉันพยายามเน้นที่นี่ว่าปัญหาในมือสามารถแก้ไขได้ง่ายแทนที่จะเป็นอันตรายกับ Java 8 Streams ที่อันตรายกำลังเสื่อมประสิทธิภาพ :)
Abhinav Ganguly

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

นอกจากนี้หากคุณต้องการที่จะหาที่แตกต่างกันหรือไม่ซ้ำกันรายชื่อ , ไม่ได้เป็นคนที่คุณสามารถทำได้โดยใช้วิธีการต่อไปนี้ทั้งสองได้เป็นอย่างดี

วิธีที่ 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
ที่จะได้รับรายชื่อที่แตกต่างกัน แต่ไม่ใช่บุคคลที่มีชื่อ
RichK
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.