سؤال

أنا أقوم بالتنفيذ compareTo() طريقة لفئة بسيطة مثل هذه (لتتمكن من استخدام Collections.sort() وغيرها من الأشياء الجيدة التي تقدمها منصة جافا):

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

هذا يؤدي المهمة، لكنني لست سعيدًا تمامًا بهذا الرمز.من المسلم به أنه ليس كذلك جداً معقدة، ولكنها مطولة ومملة للغاية.

السؤال هو، كيف يمكنك جعل هذا أقل مطولا (مع الاحتفاظ بالوظيفة)؟لا تتردد في الرجوع إلى مكتبات Java القياسية أو Apache Commons إذا كانت مفيدة.هل سيكون الخيار الوحيد لجعل هذا الأمر أبسط (قليلًا) هو تنفيذ "NullSafeStringComparator" الخاص بي، وتطبيقه لمقارنة كلا الحقلين؟

التعديلات 1-3:إيدي على حق.تم إصلاح حالة "كلا الاسمين فارغين" أعلاه

حول الإجابة المقبولة

لقد طرحت هذا السؤال في عام 2009، على Java 1.6 بالطبع، وفي ذلك الوقت حل JDK النقي من Eddie كانت إجابتي المقبولة المفضلة.لم أتمكن أبدًا من تغيير ذلك حتى الآن (2017).

هناك أيضا حلول مكتبة الطرف الثالث- مجموعة Apache Commons Collections لعام 2009 وواحدة من Guava لعام 2013، وكلاهما نشرتهما بنفسي - والذي كنت أفضله في وقت ما.

لقد قمت الآن بالتنظيف حل Java 8 بواسطة Lukasz Wiktor الجواب المقبول.يجب بالتأكيد أن يكون ذلك مفضلاً في Java 8، وفي هذه الأيام يجب أن يكون Java 8 متاحًا لجميع المشاريع تقريبًا.

هل كانت مفيدة؟

المحلول

استخدام جافا 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);
}

نصائح أخرى

يمكنك ببساطة استخدام أباتشي كومنز لانج:

result = ObjectUtils.compare(firstComparable, secondComparable)

سأقوم بتنفيذ مقارنة آمنة خالية.قد يكون هناك تنفيذ هناك، ولكن هذا سهل التنفيذ لدرجة أنني كنت دائمًا أطرح تطبيقًا خاصًا بي.

ملحوظة:المقارنة الخاصة بك أعلاه، إذا كلاهما الأسماء فارغة، ولن يتم حتى مقارنة حقول القيمة.لا أعتقد أن هذا هو ما تريد.

سأقوم بتنفيذ ذلك بشيء مثل ما يلي:

// 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 إلى ثابت.

انظر الجزء السفلي من هذه الإجابة للحصول على حل محدث (2013) باستخدام الجوافة.


وهذا ما ذهبت معه في النهاية.اتضح أن لدينا بالفعل طريقة مفيدة لمقارنة السلسلة الآمنة، لذا كان الحل الأبسط هو الاستفادة من ذلك.(إنها قاعدة بيانات كبيرة؛من السهل تفويت هذا النوع من الأشياء :)

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

هذه هي الطريقة التي يتم بها تعريف المساعد (يتم تحميله بشكل زائد بحيث يمكنك أيضًا تحديد ما إذا كانت القيم الخالية تأتي أولاً أم أخيرًا، إذا أردت):

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

لذلك هذا هو في الأساس نفس إجابة إيدي (على الرغم من أنني لن أسمي طريقة المساعدة الثابتة أ المقارنة) و أن أوزين أيضاً.

على أية حال، بشكل عام، كنت سأفضل بشدة الحل باتريك, ، حيث أعتقد أنه من الممارسات الجيدة استخدام المكتبات القائمة كلما أمكن ذلك.(التعرف على المكتبات واستخدامها كما يقول جوش بلوخ.) ولكن في هذه الحالة لم يكن ذلك لينتج الكود الأنظف والأبسط.

تحرير (2009):نسخة مجموعات أباتشي كومنز

في الواقع، إليك طريقة لجعل الحل يعتمد على 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):نسخة الجوافة

وبعد مرور ما يقرب من 5 سنوات، إليك كيفية الإجابة على سؤالي الأصلي.إذا كنت أبرمج بلغة Java، فسأستخدمها (بالطبع). الجوافة.(وبكل تأكيد لا أباتشي كومنز.)

ضع هذا الثابت في مكان ما، على سبيل المثالفي فئة "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 (كلاهما يستخدم JDK's CASE_INSENSITIVE_ORDER)، استخدام nullsLast() كونه الشيء الوحيد الخاص بالجوافة.هذا الإصدار مفضل ببساطة لأن الجوافة مفضلة، كتبعية، لمجموعات كومنز.(مثل الكل موافق.)

إذا كنت تتساءل عن Ordering, ، لاحظ أنه ينفذ Comparator.إنه مفيد جدًا خاصة لاحتياجات الفرز الأكثر تعقيدًا، مما يسمح لك على سبيل المثال بتسلسل العديد من الطلبات باستخدام compound().يقرأ شرح الطلب للمزيد من!

أوصي دائمًا باستخدام Apache commons لأنه سيكون على الأرجح أفضل من تلك التي يمكنك كتابتها بنفسك.بالإضافة إلى أنه يمكنك بعد ذلك القيام بعمل "حقيقي" بدلاً من إعادة الابتكار.

الفئة التي تهتم بها هي المقارنة فارغة.انها تسمح لك لجعل القيم الخالية عالية أو منخفضة.يمكنك أيضًا إعطاؤه المقارنة الخاصة بك لاستخدامها عندما لا تكون القيمتان فارغتين.

في حالتك، يمكن أن يكون لديك متغير عضو ثابت يقوم بإجراء المقارنة ومن ثم 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);
}

}

حتى لو قررت إنشاء فئة خاصة بك، ضع هذا الفصل في الاعتبار لأنه مفيد جدًا عند طلب قوائم تحتوي على عناصر فارغة.

أعلم أنه قد لا يكون إجابة مباشرة على سؤالك، لأنك قلت أنه يجب دعم القيم الخالية.

لكني أريد فقط أن أشير إلى أن دعم القيم الخالية في CompareTo لا يتماشى مع عقد CompareTo الموصوف رسميًا javadocs للمقارنة:

لاحظ أن NULL ليس مثيلًا لأي فئة ، ويجب أن يرمي E.COMPARETO (NULL) nullpointerxception على الرغم من أن E.equals (NULL) يعيد خطأ.

لذلك إما أن أرمي NullPointerException بشكل صريح أو أتركه يتم طرحه لأول مرة عندما يتم إلغاء الإشارة إلى الوسيطة الفارغة.

يمكنك استخراج الطريقة:

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

}

يمكنك تصميم فصلك ليكون غير قابل للتغيير (Effective Java 2nd Ed.يحتوي على قسم كبير حول هذا، البند 15:قلل من قابلية التغيير) وتأكد عند الإنشاء من عدم وجود أي قيم خالية (واستخدم نمط كائن فارغ إذا لزم الأمر).ثم يمكنك تخطي كل عمليات التحقق هذه والافتراض بأمان أن القيم ليست فارغة.

كنت أبحث عن شيء مماثل وبدا هذا معقدًا بعض الشيء لذا فعلت هذا.أعتقد أنه من الأسهل قليلاً أن نفهم.يمكنك استخدامه كمقارن أو كبطانة واحدة.بالنسبة لهذا السؤال، يمكنك تغييره إلى CompareToIgnoreCase().كما هو الحال، تطفو القيم الخالية.يمكنك قلب 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);
    }
}

يمكننا استخدام Java 8 لإجراء مقارنة خالية من الأشياء بين الكائنات.من المفترض أن يكون لدي فصل دراسي يحتوي على حقلين:اسم السلسلة والعمر الصحيح وأريد أولاً مقارنة الأسماء ثم الأعمار إذا كان كلاهما متساويًا.

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

في حالة استخدام أي شخص لـ 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

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]

إحدى الطرق البسيطة باستخدام المقارنة NullSafe هو استخدام تطبيق Spring له، وفيما يلي أحد الأمثلة البسيطة للإشارة إلى:

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

مثال آخر لـ 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());
}

هذا هو تطبيقي الذي أستخدمه لفرز ArrayList الخاص بي.يتم فرز الفئات الفارغة إلى الأخيرة.

بالنسبة لحالتي، يقوم EntityPhone بتوسيع EntityAbstract والحاوية الخاصة بي هي List <EntityAbstract>.

يتم استخدام الأسلوب "compareIfNull()" للفرز الآمن الخالي.الطرق الأخرى مخصصة للاكتمال، وتوضح كيف يمكن استخدام CompareIfNull.

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

بالنسبة للحالة المحددة التي تعلم فيها أن البيانات لن تحتوي على قيم خالية (فكرة جيدة دائمًا للسلاسل) وتكون البيانات كبيرة جدًا، فأنت لا تزال تجري ثلاث مقارنات قبل مقارنة القيم فعليًا، إذا كنت تعرف على وجه اليقين أن هذه هي حالتك, ، يمكنك تحسين قليلا.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);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top