2015-05-26 18 views
6

Sto riscontrando questo problema da tempo, ho cercato molte domande StackOverflow ma non ho potuto risolvere il mio problema.Perché il mio confronto mostra a volte IllegalArgumentException?

Ho anche chiesto una domanda simile prima e ci hanno dato il suggerimento di utilizzare,

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); 

Essa non ha risolto il mio problema. Non ho mai ottenuto questa eccezione su nessuno dei miei dispositivi di prova, ma alcuni dei miei utenti lo hanno segnalato regolarmente. Sono davvero all'oscuro di come risolverlo.

L'eccezione

questa è l'eccezione che sto ottenendo,

java.lang.IllegalArgumentException: Comparison method violates its general contract! 
at java.util.TimSort.mergeLo(TimSort.java:743) 
at java.util.TimSort.mergeAt(TimSort.java:479) 
at java.util.TimSort.mergeCollapse(TimSort.java:404) 
at java.util.TimSort.sort(TimSort.java:210) 
at java.util.TimSort.sort(TimSort.java:169) 
at java.util.Arrays.sort(Arrays.java:2023) 
at java.util.Collections.sort(Collections.java:1883) 

o, talvolta, questo,

java.lang.IllegalArgumentException: Comparison method violates its general contract! 
at java.util.TimSort.mergeHi(TimSort.java:864) 
at java.util.TimSort.mergeAt(TimSort.java:481) 
at java.util.TimSort.mergeCollapse(TimSort.java:406) 
at java.util.TimSort.sort(TimSort.java:210) 
at java.util.TimSort.sort(TimSort.java:169) 
at java.util.Arrays.sort(Arrays.java:2010) 
at java.util.Collections.sort(Collections.java:1883) 

quello che ho fatto

enum FileItemComparator implements Comparator<FileItem> { 

    //Using ENUM 
    NAME_SORT { 
     public int compare(FileItem o1, FileItem o2) { 

      int result = 0; 
      if (o1 != null && o2 != null) { 

       String n1 = o1.getFileName(); 
       String n2 = o2.getFileName(); 

       if (n1 != null && n2 != null) 
        result = n1.compareTo(n2); 
      } 

      return result; 
     } 
    }, 
    DATE_SORT { 
     public int compare(FileItem o1, FileItem o2) { 

      int result = 0; 
      if (o1 != null && o2 != null) { 

       String d1 = o1.getFileDate(); 
       String d2 = o2.getFileDate(); 

       if (d1 != null && d2 != null) { 

        Long l1 = Long.valueOf(d1); 
        Long l2 = Long.valueOf(d2); 

        if (l1 != null && l2 != null) { 
         result = l1.compareTo(l2); 
        } 
       } 

      } 

      return result; 
     } 
    }, 
    SIZE_SORT { 
     public int compare(FileItem o1, FileItem o2) { 

      int result = 0; 
      if (o1 != null && o2 != null) { 

       File f1 = o1.getItem(); 
       File f2 = o2.getItem(); 

       if (f1 != null && f2 != null) { 

        result = Long.valueOf(f1.length()).compareTo(Long.valueOf(f2.length())); 
       } 
      } 

      return result; 
     } 
    }; 

    public static Comparator<FileItem> descending(final Comparator<FileItem> other) { 

     return new Comparator<FileItem>() { 
      public int compare(FileItem o1, FileItem o2) { 
       return -1 * other.compare(o1, o2); 
      } 
     }; 
    } 

    public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) { 
     return new Comparator<FileItem>() { 
      public int compare(FileItem o1, FileItem o2) { 
       for (FileItemComparator option : multipleOptions) { 
        int result = option.compare(o1, o2); 
        if (result != 0) { 
         return result; 
        } 
       } 
       return 0; 
      } 
     }; 
    } 
} 

Questo è come mi sto ordinamento,

Collections.sort(dirs, FileItemComparator.getComparator(FileItemComparator.NAME_SORT)); 

Il problema

Sono sicuro che c'è qualcosa di sbagliato nel metodo confrontare con dipendenze transitive. Ho provato molto e non riesco a risolverlo. In realtà, non ho mai avuto questo problema in nessuno dei miei dispositivi di test, ma i miei utenti lo segnalano costantemente.

Spero che qualcuno qui sia in grado di cogliere il problema e aiutarmi a risolverlo una volta per tutte.

Aggiornato Codice (Grazie a @Eran)

ho pensato che sarebbe stato meglio per aiutare gli altri, pubblicando il codice aggiornato completo. Aiuterà molte persone che affrontano lo stesso problema.

enum FileItemComparator implements Comparator<FileItem> { 

    //Using ENUM 
    NAME_SORT { 
     public int compare(FileItem o1, FileItem o2) { 

      if (o1 == null) { 
       if (o2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null in the end 
       } 
      } else if (o2 == null) { 
       return -1; 
      } 

      String n1 = o1.getFileName(); 
      String n2 = o2.getFileName(); 

      if (n1 == null) { 
       if (n2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null names after non null names 
       } 
      } else if (n2 == null) { 
       return -1; 
      } 
      return n1.compareTo(n2); 
     } 
    }, 
    DATE_SORT { 
     public int compare(FileItem o1, FileItem o2) { 

      if (o1 == null) { 
       if (o2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null in the end 
       } 
      } else if (o2 == null) { 
       return -1; 
      } 

      String d1 = o1.getFileDate(); 
      String d2 = o2.getFileDate(); 

      if (d1 == null) { 
       if (d2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null names after non null names 
       } 
      } else if (d2 == null) { 
       return -1; 
      } 

      Long l1 = Long.valueOf(d1); 
      Long l2 = Long.valueOf(d2); 

      if (l1 == null) { 
       if (l2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null names after non null names 
       } 
      } else if (l2 == null) { 
       return -1; 
      } 

      return l1.compareTo(l2); 
     } 
    }, 
    SIZE_SORT { 
     public int compare(FileItem o1, FileItem o2) { 

      if (o1 == null) { 
       if (o2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null in the end 
       } 
      } else if (o2 == null) { 
       return -1; 
      } 

      File f1 = o1.getItem(); 
      File f2 = o2.getItem(); 

      if (f1 == null) { 
       if (f2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null in the end 
       } 
      } else if (f2 == null) { 
       return -1; 
      } 

      Long l1 = Long.valueOf(f1.length()); 
      Long l2 = Long.valueOf(f2.length()); 

      if (l1 == null) { 
       if (l2 == null) { 
        return 0; 
       } else { 
        return 1; // this will put null names after non null names 
       } 
      } else if (l2 == null) { 
       return -1; 
      } 

      return l1.compareTo(l2); 
     } 
    }; 

    public static Comparator<FileItem> descending(final Comparator<FileItem> other) { 

     return new Comparator<FileItem>() { 
      public int compare(FileItem o1, FileItem o2) { 
       return -1 * other.compare(o1, o2); 
      } 
     }; 
    } 

    public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) { 
     return new Comparator<FileItem>() { 
      public int compare(FileItem o1, FileItem o2) { 
       for (FileItemComparator option : multipleOptions) { 
        int result = option.compare(o1, o2); 
        if (result != 0) { 
         return result; 
        } 
       } 
       return 0; 
      } 
     }; 
    } 
} 
+0

Potrebbe essere correlato con uno degli argomenti "nullo" e non l'altro, tuttavia restituire "0"? – Mena

+0

@Mena Sì, questo è possibile. Buona scoperta Potete per favore aiutarmi con un codice funzionante? –

+0

sembra [Eran] ​​(http://stackoverflow.com/users/1221571/eran) ha appena fatto :) – Mena

risposta

9

Diamo un'occhiata al vostro primo metodo di confronto:

public int compare(FileItem o1, FileItem o2) { 

     int result = 0; 
     if (o1 != null && o2 != null) { 

      String n1 = o1.getFileName(); 
      String n2 = o2.getFileName(); 

      if (n1 != null && n2 != null) 
       result = n1.compareTo(n2); 
     } 

     return result; 
    } 

Supponiamo si confrontano due FileItems (chiamiamoli O1 e O2), uno con un nome di file e l'altro senza (file nulla vale a dire nome). Il tuo metodo restituirà 0.

Ora se confronti O2 con un altro FileItem (o3) per il quale il nome del file non è nullo, si ritorna a 0.

Ma se si confronta da o1 a o3, poiché entrambi hanno un nome file non nullo, il confronto restituisce -1 o 1 (assumendo che i nomi dei file siano diversi).

Pertanto il confronto è incoerente poiché non è transitivo.

Se un elemento manca di una proprietà richiesta per il confronto e l'altro no, non si dovrebbe restituire 0.È necessario decidere se restituire 1 o -1 (a seconda che, ad esempio, gli oggetti File con nome null debbano essere ordinati prima o dopo i FileItem con nomi non nulli).

Ad esempio:

public int compare(FileItem o1, FileItem o2) 
{ 
    if (o1 == null) { 
     if (o2 == null) { 
      return 0; 
     } else { 
      return 1; // this will put null in the end 
     } 
    } else if (o2 == null) { 
     return -1; 
    } 
    String n1 = o1.getFileName(); 
    String n2 = o2.getFileName(); 
    if (n1 == null) { 
     if (n2 == null) { 
      return 0; 
     } else { 
      return 1; // this will put null names after non null names 
     } 
    } else if (n2 == null) { 
     return -1; 
    } 
    return n1.compareTo(n2); 
} 
+0

Questa è una spiegazione fantastica. Puoi per favore pubblicare un codice funzionante per il metodo di confronto? Non voglio ripetere ancora nessun errore. –

+0

@Aritra vedi modifica. Non l'ho provato, ma sembra giusto. – Eran

+0

Grazie mille per questo. Fammi provare, per favore. Btw, nell'ultima modifica hai appena invertito la logica, per mettere null alla fine stai tornando 1. E 'questo il comportamento corretto? Una spiegazione come prima sarebbe davvero utile. –

3

Questo è un errore comune con comparatori - non si sta gestendo null coerente. Il modello di consueto sarebbe simile a questa:

public int compare(FileItem o1, FileItem o2) { 
    // null == null 
    if (o1 == null && o2 == null) { 
     return 0; 
    } 
    // null < not null 
    if (o1 == null || o2 == null) { 
     return -1; 
    } 
    // Neither can be null now so this is safe. 
    String n1 = o1.getFileName(); 
    String n2 = o2.getFileName(); 
    // Same logic again. 
    if (n1 == null && n2 == null) { 
     return 0; 
    } 
    if (n1 == null || n2 == null) { 
     return -1; 
    } 
    return n1.compareTo(n2); 
} 

Aggiunto

Si noti che questa implementazione anche un errore comune come sto permettendo compare(null,not_null) per eguagliare compare(not_null,null) che viola anche il contratto - si prega di utilizzare @Eran's solution o qualcosa come questo.

public int compare(FileItem o1, FileItem o2) { 
    // null == null 
    if (o1 == null && o2 == null) { 
     return 0; 
    } 
    // null != not null 
    if (o1 == null || o2 == null) { 
     // Swap these around if you want 'null' at the other end. 
     return o1 == null ? -1: 1; 
    } 
    // Neither can be null now so this is safe. 
    String n1 = o1.getFileName(); 
    String n2 = o2.getFileName(); 
    // Same logic again. 
    if (n1 == null && n2 == null) { 
     return 0; 
    } 
    if (n1 == null || n2 == null) { 
     // Swap these around if you want 'null' at the other end. 
     return n1 == null ? -1: 1; 
    } 
    return n1.compareTo(n2); 
} 
+2

Non credo che anche questo confronto sia coerente. compare (null, not null) e compare (non null, null) restituirà -1 con questo codice. – Eran

+0

@Eran - Questo è un buon punto - oggi non sto vivendo una buona giornata. :) – OldCurmudgeon

Problemi correlati