web-dev-qa-db-de.com

Wenn der Fang eigentlich nichts fängt

Ich hatte einen Programmabsturz wegen fehlerhafter Daten, die kürzlich in einer Datenbank gespeichert wurden. Das verwirrte mich, weil ich dachte, ich hätte einen Haken, um das zu verhindern.

Der folgende Code beabsichtigt, die Personalausweisnummern zu vergleichen und sie zu sortieren. Wenn ein Fehler auftritt, geben Sie -1 und den Soldaten weiter ein - stoppen Sie nicht, weil eine von mehreren Tausend Ausweisnummern falsch ist:

public int compare(Employee t, Employee t1) {
    Integer returnValue = -1;
    try {
        Integer tb = Integer.parseInt(t.getBadgeNumber());
        Integer t1b = Integer.parseInt(t1.getBadgeNumber());
        returnValue = tb.compareTo(t1b);
    } catch (Exception e) {
        returnValue = -1;//useless statement, I know.
    }
    return returnValue;
}

Wenn die Badge-Nummer getroffen wurde (wie in diesem Fall t), erhielt ich ein "Java.lang.IllegalArgumentException: Vergleichsmethode verletzt den allgemeinen Vertrag!" Fehler statt -1 zurückgeben.

Was verstehe ich nicht von dem Haken hier?

Der volle Stacktrace:

16-May-2018 14:28:53.496 SEVERE [http-nio-8084-exec-601] org.Apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [RequestServlet] in context with path [/AppearanceRequest] threw exception
 Java.lang.IllegalArgumentException: Comparison method violates its general contract!
at Java.util.TimSort.mergeHi(TimSort.Java:868)
at Java.util.TimSort.mergeAt(TimSort.Java:485)
at Java.util.TimSort.mergeForceCollapse(TimSort.Java:426)
at Java.util.TimSort.sort(TimSort.Java:223)
at Java.util.TimSort.sort(TimSort.Java:173)
at Java.util.Arrays.sort(Arrays.Java:659)
at Java.util.Collections.sort(Collections.Java:217)
at org.bcso.com.appearancerequest.html.NotifierHTML.getHTML(NotifierHTML.Java:363)
at org.bcso.com.appearancerequest.AppearanceRequestServlet.processRequest(AppearanceRequestServlet.Java:96)
at org.bcso.com.appearancerequest.AppearanceRequestServlet.doGet(AppearanceRequestServlet.Java:565)
at javax.servlet.http.HttpServlet.service(HttpServlet.Java:618)
at javax.servlet.http.HttpServlet.service(HttpServlet.Java:725)
at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:301)
at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:206)
at org.Apache.Tomcat.websocket.server.WsFilter.doFilter(WsFilter.Java:52)
at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:239)
at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:206)
at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.Java:393)
at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:239)
at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:206)
at org.Apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.Java:219)
at org.Apache.catalina.core.StandardContextValve.invoke(StandardContextValve.Java:106)
at org.Apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.Java:503)
at org.Apache.catalina.core.StandardHostValve.invoke(StandardHostValve.Java:136)
at org.Apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.Java:74)
at org.Apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.Java:610)
at org.Apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.Java:88)
at org.Apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.Java:516)
at org.Apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.Java:1015)
at org.Apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.Java:652)
at org.Apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.Java:222)
at org.Apache.Tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.Java:1575)
at org.Apache.Tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.Java:1533)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1145)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:615)
at Java.lang.Thread.run(Thread.Java:745)

Der aufrufende Code:

    List<Employee> employeeList = DatabaseUtil.getEmployees();
    Collections.sort(employeeList, new BadgeComparator());
50
Bob Stout

Die Ausnahme (was auch immer es war) war wurde von catch (Exception e) abgefangen. Sie haben diese Ausnahme nicht protokolliert, daher wissen Sie nicht, was es war. Sie sollten es irgendwie protokollieren, damit Sie wissen, was wirklich passiert ist.

Das Problem tritt auf, wenn Sie -1 zurückgeben. Dies ermöglicht die Möglichkeit einer inkonsistenten Reihenfolge, die der aktuelle Sortieralgorithmus von Java manchmal fängt. Kurz gesagt, die Rückgabe von -1 bei einem Fehler bedeutet, dass sowohl a < b als auch b < a wahr sind, da die Ausnahme in beiden Fällen abgefangen wird. Das ist logisch falsch. Der Sortieralgorithmus erkennt dies und wirft die Variable IllegalArgumentException. Beachten Sie, dass die compare-Methode nicht in Ihrem Stack-Trace ist. Es ist der Aufruf von Collections.sort.

Behandeln Sie die Ausnahme zusätzlich zur Protokollierung, bevor Sie den Vergleichsschritt in Ihrem Programm aufrufen. Wenn Sie die Zeichenfolge als Ganzzahl analysieren müssen, müssen Sie dies beim Erstellen der Employee-Objekte tun, damit die Überprüfung erfolgt, bevor Sie überhaupt zum Sortierschritt in Ihrem Programm gelangen. Eine Comparator sollte keine Daten validieren müssen. Es sollte nur die Daten vergleichen.

140
rgettman

Erläuterung

Java.lang.IllegalArgumentException: Die Vergleichsmethode verstößt gegen deren allgemeinen contract!

Die Ausnahme wird nicht innerhalb Ihrer try ausgelöst. Deshalb wird es nicht erwischt. Die Ausnahme kommt von NotifierHTML.Java:363 in Ihrem Code, wo Sie Collection#sort aufrufen, der eine TimSort-Klasse verwendet. Die Ausnahme wird dann von der TimSort.Java:868-Methode aus TimSort#mergeHi geworfen.

Es sagt Ihnen, dass Ihre Implementierung der Comparator#compare-Methode falsch ist. Es verstößt gegen den Vertrag, wie in seiner Dokumentation beschrieben :

Vergleicht seine beiden Argumente mit der Reihenfolge. Gibt eine negative ganze Zahl, zero oder eine positive ganze Zahl zurück, da das erste Argument kleiner als, gleich oder größer als ist zweite.

Der Implementierer musssgn(x.compareTo(y)) == -sgn(y.compareTo(x)) für alle x und y sicherstellen. (Dies bedeutet, dass x.compareTo(y) eine Exception auslösen muss, wenn y.compareTo(x) eine Exception auslöst.)

Der Implementierer muss auch sicherstellen, dass die Beziehung transitiv ist: (x.compareTo(y) > 0 && y.compareTo(z) > 0) impliziert x.compareTo(z) > 0.

Schließlich muss der Implementierer sicherstellen, dass x.compareTo(y) == 0 für alle zsgn(x.compareTo(z)) == sgn(y.compareTo(z)) impliziert.

Ihre Implementierung verstößt gegen eine dieser Anforderungen, und die Methode hat dies erkannt.


Quelle des Problems

Das Problem ist, dass Sie -1 zurückgeben, wenn ein Fehler auftritt. Angenommen, Sie haben zwei Werte first und second. Und dass mindestens einer von ihnen die Ausnahme provozieren wird.

Wenn Sie also first mit second vergleichen möchten, erhalten Sie -1:

compare(first, second) -> -1

Was bedeutet, dass firstkleiner als second ist. Aber wenn Sie es anders vergleichen, erhalten Sie auch -1:

compare(second, first) -> -1

Da die Ausnahme in beiden Varianten ausgelöst wird, führt dies zu Ihrem return -1;. Ihre compare-Methode sagt jedoch:

first < second
second < first

Beides gleichzeitig, was logisch falsch ist und gegen den Vertrag verstößt.


Lösung

Sie müssen richtig definieren, wo in Ihrer Bestellung nicht analysierbare Inhalte abgelegt werden. Zum Beispiel definieren wir, dass es immer kleiner als eine beliebige Anzahl ist. Also wollen wir

text < number

Was tun wir, wenn beide nicht zu analysieren sind? Wir könnten sagen, sie sind gleichwertig, wir könnten sie lexikografisch vergleichen. Halten wir es einfach und sagen, dass zwei beliebige Texte als gleich betrachtet werden:

text = text

Wir implementieren dies, indem wir prüfen, welche der Argumente nicht analysierbar sind, und dann den korrekten Wert zurückgeben:

@Override
public int compare(Employee first, Employee second) {
    Integer firstValue;
    Integer secondValue;
    try {
        firstValue = Integer.parseInt(first.getBadgeNumber());
    } catch (NumberFormatException e) {
        // Could not parse, set null as indicator
        firstValue = null;
    }
    try {
        secondValue = Integer.parseInt(second.getBadgeNumber());
    } catch (NumberFormatException e) {
        // Could not parse, set null as indicator
        secondValue = null;
    }

    if (firstValue == null && secondValue != null) {
        // text < number
        return -1;
    }
    if (firstValue != null && secondValue == null) {
        // number > text
        return 1;
    }
    if (firstValue == null && secondValue == null) {
        // text = text
        return 0;
    }

    // Both are numbers
    return Integer.compare(firstValue, secondValue);
}

Wie in den Kommentaren angedeutet, können Sie Ihre gesamte benutzerdefinierte Comparator-Klasse durch die folgende Anweisung ersetzen, die denselben Comparator generiert:

Comparator<Employee> comp = Comparator.nullsLast(
    Comparator.comparing(e -> tryParseInteger(e.getBadgeNumber())));

Zusammen mit einer tryParseInteger-Methode wie folgt:

public static Integer tryParseInteger(String text) {
    try {
        return Integer.parseInt(text);
    } catch (NumberFormatException e) {
        return null;
    }
}
50
Zabuza

Wenn dies nicht der Fall ist, denken Sie daran, dass Sie Throwable -Instanzen werfen und abfangen können, und abgesehen von Ausnahmen gibt es Fehler . Sie zu fangen ist möglich, aber wenn sie auftreten, ist es unwahrscheinlich, dass weitere Arbeiten ausgeführt werden können.

Ihr Try-Catch hätte also keinen Fehler oder ein anderes Throwable als Exception gefangen.

public static void main(String[] args) {

    try {
        throw new Error("test exception try-catch");
    } catch (Throwable e) {
        System.out.println("Error caught in throwable catch");
    }

    try {
        throw new Error("test exception try-catch");
    } catch (Exception e) {
        System.out.println("Error caught in exception catch");
    }
}

Was führt zu:

Error caught in throwable catch
Exception in thread "main" Java.lang.Error: test exception try-catch
    at ...
4
Dariusz

Diese Ausnahme wird nicht in der hier eingesetzten compare - Methode geworfen. Überprüfen Sie den Stacktrace. Es gibt keinen compare-Aufruf.

2
Antoniossss

Die Ausnahme wird von TimSort.mergeHi() ausgelöst, die intern aufgerufen wird, wenn Sie explizit Collections.sort() aufgerufen haben:

bei Java.util.TimSort.mergeHi (TimSort.Java:868)

Sie könnten die catch -Anweisung um sort() verschieben, aber die Sortierung wird daher nicht durchgeführt oder ist nicht vollständig. Es scheint also keine gute Idee zu sein.
Lange Geschichte kurz: Verstößt nicht gegen den compareTo()-Vertrag, und Sie müssten keine Ausnahme erkennen, die nicht mehr auftritt.

0
davidxxx