วิธีการทำให้การติดตั้ง comparTo ปลอดภัยเป็นโมฆะง่ายขึ้น?


156

ฉันใช้compareTo()วิธีการสำหรับชั้นเรียนอย่างง่ายเช่นนี้ (เพื่อให้สามารถใช้Collections.sort()และสินค้าอื่น ๆ ที่เสนอโดยแพลตฟอร์ม Java):

public class Metadata implements Comparable<Metadata> {
    private String name;
    private String value;

// Imagine basic constructor and accessors here
// Irrelevant parts omitted
}

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

วิธีแก้ปัญหาที่ควรคำนึงถึงนั้นเป็นไปตามลำดับต่อไปนี้ (ฉันใช้ "คำสั่งป้องกัน" ที่นี่ในขณะที่คนอื่น ๆ อาจต้องการจุดส่งคืนเดียว แต่นั่นอยู่ด้านข้าง)

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(Metadata other) {
    if (this.name == null && other.name != null){
        return -1;
    }
    else if (this.name != null && other.name == null){
        return 1;
    }
    else if (this.name != null && other.name != null) {
        int result = this.name.compareToIgnoreCase(other.name);
        if (result != 0){
            return result;
        }
    }

    if (this.value == null) {
        return other.value == null ? 0 : -1;
    }
    if (other.value == null){
        return 1;
    }

    return this.value.compareToIgnoreCase(other.value);
}

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

คำถามคือคุณจะทำให้ verbose นี้น้อยลงได้อย่างไร (ในขณะที่รักษาการทำงาน) โปรดอ้างถึงไลบรารีมาตรฐาน Java หรือ Apache Commons หากพวกเขาช่วย ตัวเลือกเดียวที่ทำให้สิ่งนี้ง่ายขึ้นคือการใช้ "NullSafeStringComparator" ของฉันเองและใช้เพื่อเปรียบเทียบทั้งสองฟิลด์

แก้ไข 1-3 : Eddie's right; แก้ไขตัวอักษร "ทั้งสองชื่อเป็นโมฆะ" ด้านบน

เกี่ยวกับคำตอบที่ยอมรับ

ฉันถามคำถามนี้ย้อนกลับไปในปี 2009 บน Java 1.6 แน่นอนและในเวลานั้นโซลูชัน JDK บริสุทธิ์โดย Eddieเป็นคำตอบที่ฉันต้องการ ฉันไม่เคยเปลี่ยนเลยจนกระทั่งตอนนี้ (2017)

นอกจากนี้ยังมีโซลูชันห้องสมุดของบุคคลที่สาม - 2009 Apache Commons Collections one และ Guava 2013 ทั้งสองโพสต์โดยฉัน - ฉันชอบในบางช่วงเวลา

ตอนนี้ฉันสร้างโซลูชัน Java 8 ที่สะอาดโดย Lukasz Wiktorเป็นคำตอบที่ยอมรับได้ ที่ควรจะเป็นที่ต้องการอย่างแน่นอนถ้าใน Java 8 และวันนี้ Java 8 ควรจะสามารถใช้ได้กับโครงการเกือบทั้งหมด


คำตอบ:


173

ใช้Java 8 :

private static Comparator<String> nullSafeStringComparator = Comparator
        .nullsFirst(String::compareToIgnoreCase); 

private static Comparator<Metadata> metadataComparator = Comparator
        .comparing(Metadata::getName, nullSafeStringComparator)
        .thenComparing(Metadata::getValue, nullSafeStringComparator);

public int compareTo(Metadata that) {
    return metadataComparator.compare(this, that);
}

7
ฉันสนับสนุนการใช้ Java 8 ในตัวซึ่งสนับสนุน Apache Commons Lang แต่รหัส Java 8 นั้นค่อนข้างน่าเกลียดและยังคง verbose ฉันจะติดกับ org.apache.commons.lang3.builder.CompareToBuilder สักครู่
jschreiner

1
สิ่งนี้ใช้ไม่ได้กับ Collections.sort (Arrays.asList (null, val1, null, val2, null)) เนื่องจากจะพยายามเรียก comparTo () บนวัตถุ null ดูเหมือนว่าปัญหากับกรอบการทำงานของคอลเลกชันที่จะซื่อสัตย์พยายามที่จะหาวิธีการนี้
Pedro Borges

3
@PedroBorges ผู้เขียนถามเกี่ยวกับการเรียงลำดับวัตถุคอนเทนเนอร์ที่มีเขตข้อมูลที่เรียงลำดับได้ (ซึ่งเขตข้อมูลเหล่านั้นอาจเป็นโมฆะ) ไม่เกี่ยวกับการเรียงลำดับการอ้างอิงคอนเทนเนอร์ null ดังนั้นในขณะที่ความคิดเห็นของคุณถูกต้องซึ่งCollections.sort(List)จะไม่ทำงานเมื่อรายการมีโมฆะความคิดเห็นนั้นไม่เกี่ยวข้องกับคำถาม
Scrubbie

211

คุณสามารถใช้Apache Commons Lang :

result = ObjectUtils.compare(firstComparable, secondComparable)

4
(@Kong: สิ่งนี้จะดูแลความปลอดภัยเป็นโมฆะ แต่ไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ซึ่งเป็นอีกแง่มุมหนึ่งของคำถามเดิมดังนั้นจึงไม่เปลี่ยนคำตอบที่ยอมรับ)
Jonik

3
นอกจากนี้ในใจของฉันApache Commonsไม่ควรจะเป็นคำตอบที่ได้รับการยอมรับในปี 2013 (แม้ว่าบางโครงการย่อยมีการบำรุงรักษาที่ดีขึ้นกว่าคนอื่น ๆ .) ฝรั่งสามารถนำมาใช้เพื่อให้บรรลุในสิ่งเดียวกัน ; เห็น/nullsFirst() nullsLast()
Jonik

8
@ Jonik ทำไมคุณคิดว่า Apache Commons ไม่ควรเป็นคำตอบที่ได้รับการยอมรับในปี 2013
จริงๆ

1
Apache Commons ส่วนใหญ่เป็นสิ่งที่สืบทอดมา / ดูแลไม่ดี / คุณภาพต่ำ มีทางเลือกที่ดีกว่าสำหรับทุกสิ่งที่มีให้ตัวอย่างเช่นในGuavaซึ่งเป็น lib คุณภาพสูงมากตลอดจนเพิ่มขึ้นใน JDK ประมาณปี 2005 ใช่แล้ว Apache Commons เป็นสิ่งที่น่ารังเกียจ แต่ทุกวันนี้ไม่จำเป็นต้องมีในโครงการส่วนใหญ่ (แน่นอนว่ามีข้อยกเว้นเช่นถ้าฉันต้องการไคลเอนต์ FTP ด้วยเหตุผลบางอย่างฉันอาจใช้ไคลเอนต์ใน Apache Commons Net และอื่น ๆ )
Jonik

6
@ Jonik คุณจะตอบคำถามโดยใช้ Guava อย่างไร การยืนยันของคุณว่า Apache Commons Lang (แพคเกจorg.apache.commons.lang3) คือ "แบบดั้งเดิม / การบำรุงรักษาต่ำ / คุณภาพต่ำ" เป็นเท็จหรือไม่มีมูลที่ดีที่สุด คอมมอนส์ Lang3 เข้าใจง่ายและใช้งานง่ายและบำรุงรักษาอย่างแข็งขัน มันอาจเป็นห้องสมุดที่ใช้บ่อยที่สุดของฉัน (นอกเหนือจาก Spring Framework และ Spring Security) - คลาสStringUtilsด้วยวิธีการที่ปลอดภัยต่อค่า null ทำให้การปรับอินพุตเป็นเรื่องปกติเป็นเรื่องเล็กน้อย
พอล

93

ฉันจะใช้เครื่องมือเปรียบเทียบที่ปลอดภัย อาจมีการนำไปใช้งานที่นั่น แต่สิ่งนี้ตรงไปตรงมามากที่จะนำไปปฏิบัติซึ่งฉันจะทำเองเสมอ

หมายเหตุ: ตัวเปรียบเทียบของคุณด้านบนหากทั้งสองชื่อเป็นโมฆะจะไม่ได้เปรียบเทียบฟิลด์ค่า ฉันไม่คิดว่านี่คือสิ่งที่คุณต้องการ

ฉันจะใช้สิ่งนี้ด้วยสิ่งต่อไปนี้:

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(final Metadata other) {

    if (other == null) {
        throw new NullPointerException();
    }

    int result = nullSafeStringComparator(this.name, other.name);
    if (result != 0) {
        return result;
    }

    return nullSafeStringComparator(this.value, other.value);
}

public static int nullSafeStringComparator(final String one, final String two) {
    if (one == null ^ two == null) {
        return (one == null) ? -1 : 1;
    }

    if (one == null && two == null) {
        return 0;
    }

    return one.compareToIgnoreCase(two);
}

แก้ไข: แก้ไขความผิดพลาดในตัวอย่างโค้ด นั่นคือสิ่งที่ฉันไม่ได้ทำการทดสอบก่อน!

แก้ไข: เลื่อนขั้น nullSafeStringComparator เป็นสแตติก


2
เกี่ยวกับซ้อน "ถ้า" ... ฉันพบว่าซ้อนกันว่าจะอ่านได้น้อยลงสำหรับกรณีนี้ดังนั้นฉันจึงหลีกเลี่ยง ใช่บางครั้งจะมีการเปรียบเทียบที่ไม่จำเป็นเนื่องจากสิ่งนี้ ขั้นสุดท้ายสำหรับพารามิเตอร์ไม่จำเป็น แต่เป็นความคิดที่ดี
Eddie

31
การใช้ XOR อย่างหวาน ๆ
James McMahon

9
@phihag - ฉันรู้ว่ามันนานกว่า 3 ปี แต่ ... finalคำหลักไม่จำเป็นจริง ๆ (โค้ด Java เป็น verbose ตามที่เป็นอยู่แล้ว) อย่างไรก็ตามมันป้องกันการใช้พารามิเตอร์ซ้ำเป็น vars ท้องถิ่น (เป็นวิธีการเข้ารหัสที่แย่มาก) ความเข้าใจโดยรวมเกี่ยวกับซอฟต์แวร์ของเราดีขึ้นเมื่อเวลาผ่านไปเรารู้ว่าสิ่งต่าง ๆ ควรเป็นครั้งสุดท้าย / const / ไม่เปลี่ยนแปลงโดยค่าเริ่มต้น ดังนั้นฉันชอบความฟุ่มเฟื่อยเพิ่มเติมเล็กน้อยในการใช้finalในการประกาศพารามิเตอร์ (แต่ฟังก์ชั่นเล็กน้อยอาจจะ) ที่จะได้รับinmutability-by-quasi-default) ค่าใช้จ่ายในการทำความเข้าใจ / การบำรุงรักษาของมันเป็นเล็กน้อยในรูปแบบที่ยิ่งใหญ่ของสิ่งต่าง ๆ
luis.espinal

24
@ James McMahon ฉันไม่เห็นด้วย Xor (^) สามารถถูกแทนที่ด้วยไม่เท่ากัน (! =) มันยังรวบรวมรหัสไบต์เดียวกัน การใช้! = vs ^ เป็นเพียงเรื่องของรสนิยมและความสามารถในการอ่าน ดังนั้นการตัดสินจากความจริงที่ว่าคุณประหลาดใจฉันจะบอกว่ามันไม่ได้อยู่ที่นี่ ใช้ xor เมื่อคุณพยายามคำนวณการตรวจสอบ ในกรณีอื่น ๆ (เช่นนี้) ลองทำตาม!
bvdb

5
มันง่ายที่จะขยายคำตอบนี้โดยแทนที่ String ด้วย T, T ซึ่งประกาศว่า <T ขยาย Comparable <T>> ... จากนั้นเราสามารถเปรียบเทียบวัตถุที่เปรียบเทียบได้ที่ไม่มีค่าใด ๆ ได้อย่างปลอดภัย
Thierry

20

ดูที่ด้านล่างของคำตอบนี้สำหรับการแก้ปัญหาที่ปรับปรุง (2013) โดยใช้ Guava


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

public int compareTo(Metadata other) {
    int result = StringUtils.compare(this.getName(), other.getName(), true);
    if (result != 0) {
        return result;
    }
    return StringUtils.compare(this.getValue(), other.getValue(), true);
}

นี่คือวิธีการกำหนดตัวช่วย (มันทำงานหนักเกินไปเพื่อให้คุณสามารถกำหนดได้ว่าค่า null จะมาก่อนหรือไม่ถ้าคุณต้องการ):

public static int compare(String s1, String s2, boolean ignoreCase) { ... }

ดังนั้นนี่จึงเป็นคำตอบเดียวกับคำตอบของ Eddie (แม้ว่าฉันจะไม่เรียกวิธีเปรียบเทียบแบบคงที่ผู้เปรียบเทียบ ) และของ uzhinด้วย

โดยทั่วไปแล้วฉันจะชอบวิธีการแก้ปัญหาของ Patrickอย่างมากเพราะฉันคิดว่ามันเป็นแนวปฏิบัติที่ดีในการใช้ห้องสมุดที่จัดตั้งขึ้นทุกครั้งที่ทำได้ ( รู้และใช้ห้องสมุดอย่างที่ Josh Bloch กล่าว) แต่ในกรณีนี้จะไม่ทำให้เกิดโค้ดที่สะอาดและง่ายที่สุด

แก้ไข (2009): รุ่น Apache Commons Collections

ที่จริงนี่เป็นวิธีที่จะทำให้การแก้ปัญหาตาม Apache Commons NullComparatorง่ายขึ้น รวมเข้ากับตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ที่Comparatorจัดให้ในStringคลาส:

public static final Comparator<String> NULL_SAFE_COMPARATOR 
    = new NullComparator(String.CASE_INSENSITIVE_ORDER);

@Override
public int compareTo(Metadata other) {
    int result = NULL_SAFE_COMPARATOR.compare(this.name, other.name);
    if (result != 0) {
        return result;
    }
    return NULL_SAFE_COMPARATOR.compare(this.value, other.value);
}

ฉันคิดว่าตอนนี้สวยหรูแล้ว (ยังมีปัญหาเล็ก ๆ เพียงปัญหาเดียว: คอมมอนส์NullComparatorไม่รองรับข้อมูลทั่วไปดังนั้นจึงมีการมอบหมายที่ไม่ได้ตรวจสอบ)

อัปเดต (2013): รุ่น Guava

เกือบ 5 ปีต่อมานี่คือวิธีที่ฉันจะตอบคำถามเดิมของฉัน หากการเข้ารหัสใน Java ฉันจะ (แน่นอน) จะใช้ฝรั่ง (และไม่ใช่ Apache Commons อย่างแน่นอน)

ใส่ค่าคงที่นี้ที่ไหนสักแห่งเช่นในคลาส "StringUtils":

public static final Ordering<String> CASE_INSENSITIVE_NULL_SAFE_ORDER =
    Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast(); // or nullsFirst()

จากนั้นในpublic class Metadata implements Comparable<Metadata>:

@Override
public int compareTo(Metadata other) {
    int result = CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.name, other.name);
    if (result != 0) {
        return result;
    }
    return CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.value, other.value);
}    

แน่นอนว่านี่เกือบจะเหมือนกับ Apache Commons (ทั้งคู่ใช้CASE_INSENSITIVE_ORDERของ JDK ) ซึ่งเป็นการใช้nullsLast()สิ่งที่เฉพาะเจาะจงของฝรั่งเท่านั้น รุ่นนี้เป็นที่นิยมมากกว่าเพราะ Guava เป็นที่นิยมมากกว่าในการพึ่งพาคอมมอนส์ (ตามที่ทุกคนตกลง )

หากคุณสงสัยเกี่ยวกับOrderingมันโปรดทราบว่ามันใช้งานComparatorได้ มันมีประโยชน์สวยโดยเฉพาะอย่างยิ่งสำหรับความต้องการของการเรียงลำดับที่ซับซ้อนมากขึ้นช่วยให้คุณเช่นการห่วงโซ่หลาย orderings compound()ใช้ อ่านคำสั่งการอธิบายเพิ่มเติม!


2
String.CASE_INSENSITIVE_ORDER ทำให้โซลูชันสะอาดขึ้นจริง ๆ เป็นการปรับปรุงที่ดี
Patrick

2
หากคุณใช้ Apache Commons อยู่ดีComparatorChainคุณก็ไม่จำเป็นต้องมีcompareToวิธีการของตัวเอง
amoebe

13

ฉันมักจะแนะนำให้ใช้ Apache Commons เพราะมันจะดีกว่าที่คุณเขียนเอง นอกจากนี้คุณยังสามารถทำงาน 'ของจริง' แทนการสร้างใหม่

ระดับที่คุณกำลังสนใจในการเป็นNull เปรียบเทียบ อนุญาตให้คุณสร้างค่า Null ให้สูงหรือต่ำ คุณให้ตัวเปรียบเทียบของคุณเองเพื่อใช้เมื่อค่าทั้งสองไม่เป็นโมฆะ

ในกรณีของคุณคุณสามารถมีตัวแปรสมาชิกแบบคงที่ทำการเปรียบเทียบแล้วcompareToวิธีการของคุณอ้างอิงนั้น

บางสิ่งบางอย่างเช่น

class Metadata implements Comparable<Metadata> {
private String name;
private String value;

static NullComparator nullAndCaseInsensitveComparator = new NullComparator(
        new Comparator<String>() {

            @Override
            public int compare(String o1, String o2) {
                // inputs can't be null
                return o1.compareToIgnoreCase(o2);
            }

        });

@Override
public int compareTo(Metadata other) {
    if (other == null) {
        return 1;
    }
    int res = nullAndCaseInsensitveComparator.compare(name, other.name);
    if (res != 0)
        return res;

    return nullAndCaseInsensitveComparator.compare(value, other.value);
}

}

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


ขอบคุณฉันหวังว่าจะมีอะไรแบบนี้ในคอมมอนส์! ในกรณีนี้ แต่ฉันไม่ได้ใช้มัน: stackoverflow.com/questions/481813/…
Jonik

เพิ่งรู้ว่าวิธีการของคุณสามารถลดความซับซ้อนโดยใช้ StringCASE_INSENSITIVE_ORDER ดูคำตอบติดตามที่แก้ไขของฉัน
Jonik

นี่เป็นสิ่งที่ดี แต่การตรวจสอบ "if (other == null) {" ไม่ควรมี Javadoc for Comparable บอกว่า ComparTo ควรโยน NullPointerException ถ้าอันอื่นเป็นโมฆะ
Daniel Alexiuc

7

ฉันรู้ว่าอาจไม่สามารถตอบคำถามของคุณได้โดยตรงเพราะคุณบอกว่าต้องมีค่าว่าง

แต่ฉันแค่ต้องการที่จะทราบว่าการสนับสนุนโมฆะในการเปรียบเทียบไม่เป็นไปตามสัญญาการเปรียบเทียบกับที่อธิบายไว้ในjavadocsอย่างเป็นทางการสำหรับการเปรียบเทียบ :

โปรดทราบว่า null ไม่ใช่อินสแตนซ์ของคลาสใด ๆ และ e.compareTo (null) ควรใช้ NullPointerException แม้ว่า e.equals (null) จะส่งกลับค่า false

ดังนั้นฉันจะโยน NullPointerException อย่างชัดเจนหรือปล่อยให้มันถูกโยนครั้งแรกเมื่ออาร์กิวเมนต์ null ถูกยกเลิกการลงทะเบียน


4

คุณสามารถแยกวิธี:

public int cmp(String txt, String otherTxt)
{
    if ( txt == null )
        return otjerTxt == null ? 0 : 1;

    if ( otherTxt == null )
          return 1;

    return txt.compareToIgnoreCase(otherTxt);
}

public int compareTo(Metadata other) {
   int result = cmp( name, other.name); 
   if ( result != 0 )  return result;
   return cmp( value, other.value); 

}


2
"0: 1" ควรไม่ใช่ "0: -1" หรือไม่
Rolf Kristensen

3

คุณสามารถออกแบบระดับของคุณจะไม่เปลี่ยนรูป (. มีผลบังคับใช้ Java 2 เอ็ดมีส่วนที่ดีเกี่ยวกับเรื่องนี้รายการ 15: ลดความไม่แน่นอน) และให้แน่ใจว่าเมื่อการก่อสร้างว่าไม่มี nulls เป็นไปได้ (และใช้รูปแบบวัตถุ nullถ้าจำเป็น) จากนั้นคุณสามารถข้ามการตรวจสอบทั้งหมดเหล่านั้นและถือว่าค่าไม่เป็นโมฆะ


ใช่ว่าโดยทั่วไปเป็นทางออกที่ดีและช่วยลดความยุ่งยากหลายสิ่งหลายอย่าง - แต่ที่นี่ผมมีความสนใจมากขึ้นในกรณีที่ค่า null จะได้รับอนุญาตสำหรับหนึ่งหรือเหตุผลอื่นและจะต้องนำเข้าบัญชี :)
Jonik

2

ฉันกำลังมองหาบางสิ่งที่คล้ายกันและดูเหมือนซับซ้อนเล็กน้อยดังนั้นฉันจึงทำสิ่งนี้ ฉันคิดว่ามันง่ายกว่าที่จะเข้าใจ คุณสามารถใช้มันเป็น Comparator หรือเป็นหนึ่งซับ สำหรับคำถามนี้คุณจะเปลี่ยนเป็น comparToIgnoreCase () เช่นเดียวกับ nulls ลอยขึ้น คุณสามารถพลิก 1, -1 หากคุณต้องการให้จม

StringUtil.NULL_SAFE_COMPARATOR.compare(getName(), o.getName());

.

public class StringUtil {
    public static final Comparator<String> NULL_SAFE_COMPARATOR = new Comparator<String>() {

        @Override
        public int compare(final String s1, final String s2) {
            if (s1 == s2) {
                //Nulls or exact equality
                return 0;
            } else if (s1 == null) {
                //s1 null and s2 not null, so s1 less
                return -1;
            } else if (s2 == null) {
                //s2 null and s1 not null, so s1 greater
                return 1;
            } else {
                return s1.compareTo(s2);
            }
        }
    }; 

    public static void main(String args[]) {
        final ArrayList<String> list = new ArrayList<String>(Arrays.asList(new String[]{"qad", "bad", "sad", null, "had"}));
        Collections.sort(list, NULL_SAFE_COMPARATOR);

        System.out.println(list);
    }
}

2

เราสามารถใช้ java 8 เพื่อทำการเปรียบเทียบที่เป็นมิตรระหว่างวัตถุ ฉันควรจะมีคลาส Boy พร้อม 2 ฟิลด์: ชื่อสตริงและอายุจำนวนเต็มและฉันต้องการเปรียบเทียบชื่อและอายุก่อนถ้าทั้งสองเท่ากัน

static void test2() {
    List<Boy> list = new ArrayList<>();
    list.add(new Boy("Peter", null));
    list.add(new Boy("Tom", 24));
    list.add(new Boy("Peter", 20));
    list.add(new Boy("Peter", 23));
    list.add(new Boy("Peter", 18));
    list.add(new Boy(null, 19));
    list.add(new Boy(null, 12));
    list.add(new Boy(null, 24));
    list.add(new Boy("Peter", null));
    list.add(new Boy(null, 21));
    list.add(new Boy("John", 30));

    List<Boy> list2 = list.stream()
            .sorted(comparing(Boy::getName, 
                        nullsLast(naturalOrder()))
                   .thenComparing(Boy::getAge, 
                        nullsLast(naturalOrder())))
            .collect(toList());
    list2.stream().forEach(System.out::println);

}

private static class Boy {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Boy(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "name: " + name + " age: " + age;
    }
}

และผลลัพธ์:

    name: John age: 30
    name: Peter age: 18
    name: Peter age: 20
    name: Peter age: 23
    name: Peter age: null
    name: Peter age: null
    name: Tom age: 24
    name: null age: 12
    name: null age: 19
    name: null age: 21
    name: null age: 24

1

ในกรณีที่ใครก็ตามที่ใช้ Spring มีคลาส org.springframework.util.comparator.NullSafeComparator ที่ทำเช่นนี้ให้คุณเช่นกัน เพียงแค่ตกแต่งของคุณเองเทียบเคียงกับมันเช่นนี้

new NullSafeComparator<YourObject>(new YourComparable(), true)

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html


1

สำหรับกรณีเฉพาะที่คุณรู้ว่าข้อมูลจะไม่ได้มี nulls (เสมอความคิดที่ดีสำหรับสตริง) และข้อมูลที่มีขนาดใหญ่จริงๆคุณจะยังคงทำสามรถก่อนที่จริงการเปรียบเทียบค่าที่ถ้าคุณทราบว่าเป็นกรณีนี้ , คุณสามารถเพิ่มประสิทธิภาพบิตตาด YMMV เนื่องจากรหัสที่อ่านได้จะสำคัญกว่าการปรับแต่งเล็กน้อย

        if(o1.name != null && o2.name != null){
            return o1.name.compareToIgnoreCase(o2.name);
        }
        // at least one is null
        return (o1.name == o2.name) ? 0 : (o1.name != null ? 1 : -1);

1
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Comparator;

public class TestClass {

    public static void main(String[] args) {

        Student s1 = new Student("1","Nikhil");
        Student s2 = new Student("1","*");
        Student s3 = new Student("1",null);
        Student s11 = new Student("2","Nikhil");
        Student s12 = new Student("2","*");
        Student s13 = new Student("2",null);
        List<Student> list = new ArrayList<Student>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        list.add(s11);
        list.add(s12);
        list.add(s13);

        list.sort(Comparator.comparing(Student::getName,Comparator.nullsLast(Comparator.naturalOrder())));

        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
            Student student = (Student) iterator.next();
            System.out.println(student);
        }


    }

}

ผลลัพธ์คือ

Student [name=*, id=1]
Student [name=*, id=2]
Student [name=Nikhil, id=1]
Student [name=Nikhil, id=2]
Student [name=null, id=1]
Student [name=null, id=2]

1

หนึ่งในวิธีที่ง่ายที่สุดในการใช้ NullSafe Comparatorคือการใช้การติดตั้งแบบสปริงของมันด้านล่างเป็นตัวอย่างง่าย ๆ ในการอ้างอิง:

public int compare(Object o1, Object o2) {
        ValidationMessage m1 = (ValidationMessage) o1;
        ValidationMessage m2 = (ValidationMessage) o2;
        int c;
        if (m1.getTimestamp() == m2.getTimestamp()) {
            c = NullSafeComparator.NULLS_HIGH.compare(m1.getProperty(), m2.getProperty());
            if (c == 0) {
                c = m1.getSeverity().compareTo(m2.getSeverity());
                if (c == 0) {
                    c = m1.getMessage().compareTo(m2.getMessage());
                }
            }
        }
        else {
            c = (m1.getTimestamp() > m2.getTimestamp()) ? -1 : 1;
        }
        return c;
    }

0

Apache ObjectUtils อีกตัวอย่างหนึ่ง สามารถจัดเรียงวัตถุประเภทอื่นได้

@Override
public int compare(Object o1, Object o2) {
    String s1 = ObjectUtils.toString(o1);
    String s2 = ObjectUtils.toString(o2);
    return s1.toLowerCase().compareTo(s2.toLowerCase());
}

0

นี่คือการใช้งานของฉันที่ฉันใช้เพื่อเรียงลำดับ ArrayList ของฉัน คลาส Null จะถูกจัดเรียงเป็นลำดับสุดท้าย

สำหรับกรณีของฉัน EntityPhone ขยาย EntityAbstract และคอนเทนเนอร์ของฉันคือรายการ <EntityAbstract>

เมธอด "comparIfNull ()" ใช้สำหรับการจัดเรียงที่ปลอดภัยแบบ null วิธีอื่นมีไว้เพื่อความสมบูรณ์แสดงว่ามีการเปรียบเทียบ IfNull ได้อย่างไร

@Nullable
private static Integer compareIfNull(EntityPhone ep1, EntityPhone ep2) {

    if (ep1 == null || ep2 == null) {
        if (ep1 == ep2) {
            return 0;
        }
        return ep1 == null ? -1 : 1;
    }
    return null;
}

private static final Comparator<EntityAbstract> AbsComparatorByName = = new Comparator<EntityAbstract>() {
    @Override
    public int compare(EntityAbstract ea1, EntityAbstract ea2) {

    //sort type Phone first.
    EntityPhone ep1 = getEntityPhone(ea1);
    EntityPhone ep2 = getEntityPhone(ea2);

    //null compare
    Integer x = compareIfNull(ep1, ep2);
    if (x != null) return x;

    String name1 = ep1.getName().toUpperCase();
    String name2 = ep2.getName().toUpperCase();

    return name1.compareTo(name2);
}
}


private static EntityPhone getEntityPhone(EntityAbstract ea) { 
    return (ea != null && ea.getClass() == EntityPhone.class) ?
            (EntityPhone) ea : null;
}

0

หากคุณต้องการแฮ็คง่ายๆ

arrlist.sort((o1, o2) -> {
    if (o1.getName() == null) o1.setName("");
    if (o2.getName() == null) o2.setName("");

    return o1.getName().compareTo(o2.getName());
})

หากคุณต้องการให้ null เป็นจุดสิ้นสุดของรายการเพียงแค่เปลี่ยนค่านี้ใน metod ด้านบน

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