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


92

การทำงานใน Java 8 ฉันได้TreeSetกำหนดไว้ดังนี้:

private TreeSet<PositionReport> positionReports = 
        new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp));

PositionReport เป็นคลาสที่ค่อนข้างง่ายที่กำหนดไว้เช่นนี้:

public static final class PositionReport implements Cloneable {
    private final long timestamp;
    private final Position position;

    public static PositionReport create(long timestamp, Position position) {
        return new PositionReport(timestamp, position);
    }

    private PositionReport(long timestamp, Position position) {
        this.timestamp = timestamp;
        this.position = position;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public Position getPosition() {
        return position;
    }
}

ใช้งานได้ดี

ตอนนี้ฉันต้องการลบรายการออกจากTreeSet positionReportsที่timestampเก่ากว่าค่าบางค่า แต่ฉันไม่สามารถหาไวยากรณ์ Java 8 ที่ถูกต้องเพื่อแสดงสิ่งนี้ได้

ความพยายามนี้รวบรวมได้จริง แต่ให้ตัวTreeSetเปรียบเทียบที่ไม่ได้กำหนดใหม่แก่ฉัน:

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(Collectors.toCollection(TreeSet::new))

ฉันจะแสดงออกได้อย่างไรว่าฉันต้องการรวบรวมเข้าTreeSetด้วยตัวเปรียบเทียบเช่นComparator.comparingLong(PositionReport::getTimestamp)?

ฉันจะคิดอะไรบางอย่างเช่น

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(
                TreeSet::TreeSet(Comparator.comparingLong(PositionReport::getTimestamp))
            )
        );

แต่สิ่งนี้ไม่ได้รวบรวม / ดูเหมือนว่าจะเป็นไวยากรณ์ที่ถูกต้องสำหรับการอ้างอิงวิธีการ

คำตอบ:


118

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

TreeSet<Report> toTreeSet(Collection<Report> reports, long timestamp) {
    return reports.stream().filter(report -> report.timestamp() >= timestamp).collect(
        Collectors.toCollection(
            () -> new TreeSet<>(Comparator.comparingLong(Report::timestamp))
        )
    );
}

4
สิ่งหนึ่งที่ควรทราบก็คือคุณไม่จำเป็นต้องใช้ตัวเปรียบเทียบหากประเภทของ TreeSet ของคุณ (ในกรณีนี้คือ PositionReport) ที่เทียบเคียงได้
xtrakBandit

35
ติดตามได้ที่ @xtrakBandit - อีกครั้งหากคุณไม่จำเป็นต้องระบุตัวเปรียบเทียบ (การเรียงลำดับตามธรรมชาติ) - คุณสามารถทำให้กระชับได้:.collect(Collectors.toCollection(TreeSet::new));
Joshua Goldberg

ฉันได้รับข้อผิดพลาดนี้:toCollection in class Collectors cannot be applied to given types
Bahadir Tasdemir

@BahadirTasdemir รหัสใช้งานได้ ตรวจสอบให้แน่ใจว่าคุณได้เพียงผ่านอาร์กิวเมนต์หนึ่งไปยังCollectors::toCollectionกที่ส่งกลับSupplier เป็นประเภทที่มีวิธีนามธรรมเพียงวิธีเดียวซึ่งหมายความว่าสามารถเป็นเป้าหมายของนิพจน์แลมบ์ดาเช่นเดียวกับในคำตอบนี้ นิพจน์แลมบ์ดาจะต้องไม่มีอาร์กิวเมนต์ (ดังนั้นรายการอาร์กิวเมนต์ว่างเปล่า) และส่งคืนคอลเล็กชันที่มีประเภทองค์ประกอบที่ตรงกับประเภทขององค์ประกอบในสตรีมที่คุณกำลังรวบรวม (ในกรณีนี้คือก) CollectionSupplier()TreeSet<PositionReport>
gdejohn

15

นี่เป็นเรื่องง่ายเพียงแค่ใช้รหัสถัดไป:

    positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(()->new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp)
)));

9

คุณสามารถแปลงเป็น SortedSet ได้ในตอนท้าย (โดยที่คุณไม่ต้องสนใจสำเนาเพิ่มเติม)

positionReports = positionReports
                .stream()
                .filter(p -> p.getTimeStamp() >= oldestKept)
                .collect(Collectors.toSet());

return new TreeSet(positionReports);

7
คุณต้องระมัดระวังขณะทำสิ่งนี้ คุณอาจสูญเสียองค์ประกอบขณะทำสิ่งนี้ เช่นเดียวกับในคำถามที่ถามข้างต้นตัวเปรียบเทียบตามธรรมชาติขององค์ประกอบนั้นแตกต่างจากที่ OP ต้องการใช้ ดังนั้นคุณในการแปลงเริ่มต้นเนื่องจากเป็นชุดจึงอาจสูญเสียองค์ประกอบบางอย่างซึ่งตัวเปรียบเทียบอื่นอาจไม่มี (กล่าวคือตัวเปรียบเทียบตัวแรกอาจคืนค่าcompareTo() เป็น 0 ในขณะที่อีกตัวอาจไม่มีสำหรับการเปรียบเทียบบางอย่างทั้งหมดที่compareTo()เป็น 0 จะหายไปเพราะเป็นชุด)
looneyGod

6

default boolean removeIf(Predicate<? super E> filter)มีวิธีการในการเก็บสำหรับการนี้โดยไม่ต้องใช้กระแสเป็น: ดูJavadoc

ดังนั้นรหัสของคุณอาจมีลักษณะดังนี้:

positionReports.removeIf(p -> p.timestamp < oldestKept);

1

ปัญหาของ TreeSet คือตัวเปรียบเทียบที่เราต้องการสำหรับการเรียงลำดับรายการจะถูกใช้เพื่อตรวจหารายการที่ซ้ำกันเมื่อแทรกรายการลงในชุด ดังนั้นหากฟังก์ชันตัวเปรียบเทียบเป็น 0 สำหรับสองรายการฟังก์ชันนี้จะทิ้งอย่างไม่ถูกต้องโดยพิจารณาว่าซ้ำกัน

การตรวจหารายการที่ซ้ำกันควรทำโดยวิธี hashCode ที่ถูกต้องแยกต่างหากของรายการ ฉันชอบใช้ HashSet แบบธรรมดาเพื่อป้องกันการซ้ำกันด้วย hashCode โดยพิจารณาจากคุณสมบัติทั้งหมด (id และชื่อในตัวอย่าง) และส่งคืนรายการที่เรียงลำดับอย่างง่ายเมื่อได้รับรายการ (จัดเรียงตามชื่อในตัวอย่างเท่านั้น):

public class ProductAvailableFiltersDTO {

    private Set<FilterItem> category_ids = new HashSet<>();

    public List<FilterItem> getCategory_ids() {
        return category_ids.stream()
            .sorted(Comparator.comparing(FilterItem::getName))
            .collect(Collectors.toList());
    }

    public void setCategory_ids(List<FilterItem> category_ids) {
        this.category_ids.clear();
        if (CollectionUtils.isNotEmpty(category_ids)) {
            this.category_ids.addAll(category_ids);
        }
    }
}


public class FilterItem {
    private String id;
    private String name;

    public FilterItem(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FilterItem)) return false;
        FilterItem that = (FilterItem) o;
        return Objects.equals(getId(), that.getId()) &&
                Objects.equals(getName(), that.getName());
    }

    @Override
    public int hashCode() {

        return Objects.hash(getId(), getName());
    }
}

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