web-dev-qa-db-fra.com

Quand attraper ne fait rien attraper

J'ai eu un blocage du programme en raison de mauvaises données stockées dans une base de données récemment. Cela m'a confondu, car je pensais avoir un piège pour empêcher cela.

Le code suivant a pour but de comparer les numéros de badge des employés et de les trier. S'il y a une erreur, retourne -1 et soldat - ne vous arrêtez pas, car l'un des milliers de badges est erroné:

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

Lorsque le mauvais numéro de badge a été touché (comme dans le cas présent), j'ai reçu une "Java.lang.IllegalArgumentException: la méthode de comparaison viole son contrat général!" erreur au lieu de renvoyer le -1 dans la capture.

Qu'est-ce que je ne comprends pas à propos du piège ici?

Le stacktrace complet:

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)

Le code d'appel:

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

L'exception (quelle qu'elle soit) était attrapée par catch (Exception e). Vous n'avez pas enregistré cette exception, vous ne savez donc pas ce que c'était. Vous devriez le connecter d'une manière ou d'une autre pour que vous sachiez ce qui s'est réellement passé.

Le problème se produit lorsque vous renvoyez -1. Cela permet la possibilité d'un ordre incohérent, ce que l'algorithme de tri actuel de Java utilise parfois. En bref, renvoyer -1 en cas d'erreur signifie que vous affirmez que a < b et b < a sont vrais, car l'exception sera interceptée dans les deux cas. Ceci est logiquement incorrect. L'algorithme de tri détecte cela et renvoie la variable IllegalArgumentException. Notez que la méthode compare est pas dans votre trace de pile; c'est l'appel à Collections.sort.

En plus de consigner l'exception, gérez-la avant même de passer à l'étape de comparaison de votre programme. Si vous devez analyser la chaîne en tant qu'entier, faites-le lors de la création des objets Employee, afin que la validation ait lieu avant même que vous n'ayez atteint l'étape de tri de votre programme. Un Comparator ne devrait pas avoir à valider des données; il devrait seulement comparer les données.

140
rgettman

Explication

Java.lang.IllegalArgumentException: La méthode de comparaison viole son ​​général contract!

L'exception n'est pas levée de votre try. C'est pourquoi il n'est pas attrapé. L'exception provient de NotifierHTML.Java:363 dans votre code où vous appelez Collection#sort qui utilise une classe TimSort. L'exception est ensuite levée à partir de TimSort.Java:868 par la méthode TimSort#mergeHi.

Il vous indique que votre implémentation de la méthode Comparator#compare est incorrecte. Il viole le contrat, comme expliqué dans sa documentation :

Compare ses deux arguments pour order. Renvoie un entier négatif, zéro ou un entier positif, le premier argument étant inférieur à, égal à ou supérieur à le seconde.

L'implémenteur doit s'assurersgn(x.compareTo(y)) == -sgn(y.compareTo(x)) pour tous les x et y. (Cela implique que x.compareTo(y) doit lever une exception si et seulement si y.compareTo(x) lève une exception.)

L'implémenteur doit également s'assurer que la relation est transitive: (x.compareTo(y) > 0 && y.compareTo(z) > 0) implique x.compareTo(z) > 0.

Enfin, l'implémenteur doit s'assurer que x.compareTo(y) == 0 implique que sgn(x.compareTo(z)) == sgn(y.compareTo(z)), pour tout z.

Votre implémentation ne respecte pas l'une de ces exigences et la méthode l'a détectée.


Source du problème

Le problème est que vous renvoyez -1 si une erreur se produit. Supposons que vous ayez deux valeurs first et second. Et qu’au moins l’un d’eux provoquera une exception.

Donc, si vous voulez comparer first avec second, vous obtenez -1:

compare(first, second) -> -1

Ce qui signifie que first est plus petit que second. Mais si vous le comparez dans l'autre sens, vous obtenez aussi -1:

compare(second, first) -> -1

Parce que l'exception est levée dans les deux variantes, ce qui conduit à votre return -1;. Mais cela signifie que votre méthode compare dit:

first < second
second < first

Les deux en même temps, ce qui est logiquement incorrect et viole le contrat.


Solution

Vous devez définir correctement où est placé le contenu non parsable dans votre commande. Par exemple, définissons qu’il est toujours inférieur à n’importe quel nombre. Donc nous voulons

text < number

Que faisons-nous si les deux sont imparables? Nous pourrions dire qu'ils sont égaux, nous pourrions les comparer lexicographiques. Restons simples et disons que deux textes sont considérés comme égaux:

text = text

Nous implémentons cela en vérifiant quels arguments ne sont pas analysables et en renvoyant la valeur correcte:

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

Comme indiqué dans les commentaires, vous pouvez remplacer l'ensemble de votre classe personnalisée Comparator par l'instruction suivante qui génère le même comparateur:

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

Avec une méthode tryParseInteger comme celle-ci:

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

Bien que ce ne soit pas le cas, rappelez-vous que vous pouvez lancer et attraper Throwable instances, et à part Exceptions, il existe Erreurs . Les attraper est possible, bien que quand ils se produisent, il est peu probable qu'un travail supplémentaire puisse être fait.

Donc, votre try-catch n’aurait attrapé aucune erreur ni aucun jetable autre que Exception.

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

Ce qui entraînera:

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

Cette exception n'est pas renvoyée dans compare la méthode que vous avez collée ici. Vérifiez le stacktrace. Il n'y a pas d'appel compare dedans.

2
Antoniossss

L'exception est levée depuis TimSort.mergeHi() invoqué en interne puisque vous avez explicitement appelé Collections.sort():

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

Vous pouvez déplacer l'instruction catch autour de sort() mais, par conséquent, le tri ne sera pas effectué ou ne sera pas complet. Cela ne semble donc pas être une bonne idée.
Longue histoire: ne violez pas le contrat compareTo() et vous n’auriez pas besoin d’attraper une exception qui ne se produira plus.

0
davidxxx