2016-07-09 41 views
7

Il seguente codice è una versione modificata di Dave Koelle's AlphanumComparator. La modifica contiene il codice che ordina le stringhe vuote fino alla fine dell'elenco o in fondo allo JTable nel mio caso. Il problema è un java.lang.IllegalArgumentException: Comparison method violates its general contract! si verifica.Contrattuale violazione contratto generale

Per risolvere il mio problema, ho esaminato il problema e ho trovato motivi come il comparatore non ha uno return 0; nella posizione corretta. Ho anche trovato un commento nel Java bug database che diceva

l'algoritmo di ordinamento utilizzato da java.util.Arrays.sort e (indirettamente) da java.util.Collections.sort è stato sostituito. La nuova implementazione sort può lanciare un IllegalArgumentException se rileva un Comparable che viola il contratto Comparable. L'implementazione precedente ignorò silenziosamente una situazione del genere. Se il comportamento precedente si desidera, è possibile utilizzare la nuova proprietà di sistema, java.util.Arrays.useLegacyMergeSort, per ripristinare la precedente comportamento Mergesort

import java.util.Comparator; 
import javax.swing.JTable; 
import javax.swing.SortOrder; 

public class AlphanumComparator implements Comparator<String> { 
    JTable table; 

    public AlphanumComparator(JTable table) { 
     this.table = table; 
    } 

    private final boolean isDigit(char ch) { 
     return ch >= 48 && ch <= 57; 
    } 

    private final String getChunk(String s, int slength, int marker) { 
     StringBuilder chunk = new StringBuilder(); 
     char c = s.charAt(marker); 
     chunk.append(c); 
     marker++; 
     if (isDigit(c)) { 
      while (marker < slength) { 
       c = s.charAt(marker); 
       if (!isDigit(c)) 
        break; 
       chunk.append(c); 
       marker++; 
      } 
     } else { 
      while (marker < slength) { 
       c = s.charAt(marker); 
       if (isDigit(c)) 
        break; 
       chunk.append(c); 
       marker++; 
      } 
     } 
     return chunk.toString(); 
    } 

    public int compare(String s1, String s2) { 
     boolean swapInt = table.getRowSorter().getSortKeys().get(0).getSortOrder() == SortOrder.ASCENDING; 

     int thisMarker = 0; 
     int thatMarker = 0; 
     int s1Length = s1.length(); 
     int s2Length = s2.length(); 

     if(s1Length != 0 && s2Length != 0) { 
      while (thisMarker < s1Length && thatMarker < s2Length) { 
       String thisChunk = getChunk(s1, s1Length, thisMarker); 
       thisMarker += thisChunk.length(); 

       String thatChunk = getChunk(s2, s2Length, thatMarker); 
       thatMarker += thatChunk.length(); 

       int result = 0; 
       if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) { 
        int thisChunkLength = thisChunk.length(); 
        result = thisChunkLength - thatChunk.length(); 
        if (result == 0) { 
         for (int i = 0; i < thisChunkLength; i++) { 
          result = thisChunk.charAt(i) - thatChunk.charAt(i); 
          if (result != 0) { 
           return result; 
          } 
         } 
        } 
       } else { 
        result = thisChunk.compareTo(thatChunk); 
       } 

       if (result != 0) 
        return result; 
      } 

      return s1Length - s2Length; 
     } else { 
      if(swapInt) { 
       if(s1Length == 0) { 
        return 1; 
       } else { 
        return -1; 
       } 
      } else { 
       if(s1Length == 0) { 
        return -1; 
       } else { 
        return 1; 
       } 
      } 
     } 
    } 
} 

Qualcuno in grado di aiutare a risolvere il mio problema e spiegare perché questo comparatore sta violando il contratto Paragonabile

Eccezione analisi dello stack, se necessario

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Comparison method violates its general contract! 
    at java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:744) 
    at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:481) 
    at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:422) 
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:222) 
    at java.util.Arrays.sort(Arrays.java:1246) 
    at javax.swing.DefaultRowSorter.sort(DefaultRowSorter.java:607) 
    at javax.swing.DefaultRowSorter.setSortKeys(DefaultRowSorter.java:319) 
    at javax.swing.DefaultRowSorter.toggleSortOrder(DefaultRowSorter.java:480) 
    at javax.swing.plaf.basic.BasicTableHeaderUI$MouseInputHandler.mouseClicked(BasicTableHeaderUI.java:112) 
    at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:270) 
    at java.awt.Component.processMouseEvent(Component.java:6538) 
    at javax.swing.JComponent.processMouseEvent(JComponent.java:3324) 
    at java.awt.Component.processEvent(Component.java:6300) 
    at java.awt.Container.processEvent(Container.java:2236) 
    at java.awt.Component.dispatchEventImpl(Component.java:4891) 
    at java.awt.Container.dispatchEventImpl(Container.java:2294) 
    at java.awt.Component.dispatchEvent(Component.java:4713) 
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4888) 
    at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4534) 
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4466) 
    at java.awt.Container.dispatchEventImpl(Container.java:2280) 
    at java.awt.Window.dispatchEventImpl(Window.java:2750) 
    at java.awt.Component.dispatchEvent(Component.java:4713) 
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758) 
    at java.awt.EventQueue.access$500(EventQueue.java:97) 
    at java.awt.EventQueue$3.run(EventQueue.java:709) 
    at java.awt.EventQueue$3.run(EventQueue.java:703) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76) 
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) 
    at java.awt.EventQueue$4.run(EventQueue.java:731) 
    at java.awt.EventQueue$4.run(EventQueue.java:729) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76) 
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:728) 
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201) 
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) 
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) 
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) 
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) 
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82) 
+0

avete esempi di ingresso? – Tibrogargan

risposta

6

Penso che il problema è che il tuo codice non esamina mai s2Length quando s1Length è zero. È necessario aggiungere un altro controllo per vedere se entrambe le stringhe sono vuote, come questo:

if(swapInt) { 
    if(s1Length == 0 && s2Length != 0) { 
     return 1; 
    } else if (s2Length == 0 && s1Length != 0) { 
     return -1; 
    } else { 
     return 0; 
    } 
} else { 
    if(s1Length == 0 && s2Length != 0) { 
     return -1; 
    } else if (s2Length == 0 && s1Length != 0) { 
     return 1; 
    } else { 
     return 0; 
    } 
} 

Il tuo attuale implementazione restituisce 1 o -1 anche quando le due stringhe sono vuote (il che significa che devono confrontare come uguali e restituire zero) . Il nuovo algoritmo di ordinamento rileva questo problema e genera un'eccezione.

Nota:

Si dovrebbe essere in grado di semplificare il codice ulteriormente facendo swapInt un int che è o 1 o -1, a seconda del getSortOrder risultato:

if(s1Length == 0 && s2Length != 0) { 
    return swapInt; 
} else if (s2Length == 0 && s1Length != 0) { 
    return -swapInt; 
} else { 
    return 0; 
} 
+1

La soluzione alternativa restituisce ancora 1 o -1 non appena s1 è vuoto, senza checkijg se anche s2 è vuoto. –

+0

Sfortunatamente questo non risolve il problema. Presumo che ciò sia dovuto a ciò che ha detto @JBNizet. – Dan

+0

@Dan Modificato. Grazie! – dasblinkenlight

2

Il tuo comparatore fa:

if (s1Length != 0 && s2Length != 0) { 
    ... 
} else { 
    if (swapInt) { 
     if(s1Length == 0) { 
      return 1; 
     } else { 
      return -1; 
     } 
    } else { 
     if(s1Length == 0) { 
      return -1; 
     } else { 
      return 1; 
     } 
    } 
} 

Quindi, se non entra nel blocco if, significa che almeno una stringa è vuota. Ma entrambi potrebbero essere vuoti. Ma il tuo comparatore restituisce solo -1 o 1 in quel caso. Ciò significa che se A e B sono entrambi vuoti, e il confronto tra A e B porta a -1, quindi il confronto tra B e A porterà anche a -1, e A è quindi sia più piccolo che maggiore di B allo stesso tempo.

Basta avviare il blocco else con

if (s1Length == 0 && s2Length == 0) { 
    return 0; 
} 
+0

Grazie per la risposta – Dan