Когда улов не на самом деле ничего не поймает
У меня был сбой программы из-за плохих данных, хранящихся в базе данных в последнее время. Это смутило меня, потому что я думал, что у меня есть улов, чтобы предотвратить это.
Целью следующего кода является сравнение номеров значков сотрудников и их сортировка. Если есть ошибка, верните -1 и солдат - не останавливайтесь, потому что одна из нескольких тысяч номеров значков неверна:
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;
}
Когда номер плохого значка попал (как и в этом случае), я получил метод java.lang.IllegalArgumentException: Comparison, нарушающий его общий контракт! " вместо того, чтобы возвращать -1 в catch.
Что я не понимаю об уловах здесь?
Полная стек:
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)
Вызывающий код:
List<Employee> employeeList = DatabaseUtil.getEmployees();
Collections.sort(employeeList, new BadgeComparator());
Ответы
Ответ 1
Исключение (независимо от того, что было) было поймано catch (Exception e)
. Вы не регистрировали это исключение, поэтому не знаете, что это было. Вы должны его каким-то образом зарегистрировать, чтобы вы знали, что на самом деле произошло.
Проблема возникает, когда вы возвращаете -1
. Это допускает возможность несогласованного упорядочения, который иногда ломает алгоритм сортировки Java. Короче говоря, возвращение -1
при ошибке означает, что вы утверждаете, что оба a < b
и b < a
истинны, потому что исключение будет обнаружено в обоих случаях. Это логически неверно. Алгоритм сортировки обнаруживает это и выдает IllegalArgumentException
. Обратите внимание, что метод compare
не находится в вашей трассировке стека; это вызов Collections.sort
.
Помимо регистрации исключения, обработайте его, прежде чем вы даже дойдете до этапа сравнения в своей программе. Если вам нужно проанализировать строку как целое число, сделайте это при создании объектов Employee
, чтобы проверка выполнялась до того, как вы даже дошли до этапа сортировки в своей программе. Comparator
не нужно проверять данные; он должен только сравнивать данные.
Ответ 2
объяснение
java.lang.IllegalArgumentException: метод сравнения нарушает общий контракт !
Исключение не выбрасывается из вашей try
. Вот почему он не пойман. Исключение исходит от NotifierHTML.java:363
в вашем коде, где вы вызываете Collection#sort
который использует класс TimSort
. Затем исключение TimSort.java:868
из TimSort.java:868
методом TimSort#mergeHi
.
Он говорит вам, что ваша реализация метода Comparator#compare
неверна. Он нарушает контракт, как поясняется в его документации:
Сравнивает два аргумента для заказа. Возвращает отрицательное целое число, ноль или положительное целое число, поскольку первый аргумент меньше, равен или больше второго.
Разработчик должен обеспечить sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
для всех x
и y
. (Это означает, что x.compareTo(y)
должен генерировать исключение, если y.compareTo(x)
генерирует исключение).
Разработчик должен также гарантировать, что отношение транзитивно: (x.compareTo(y) > 0 && y.compareTo(z) > 0)
подразумевает x.compareTo(z) > 0
.
Наконец, разработчик должен убедиться, что x.compareTo(y) == 0
означает, что sgn(x.compareTo(z)) == sgn(y.compareTo(z))
для всех z
.
Ваша реализация нарушает одно из этих требований, и метод обнаружил это.
Источник проблемы
Проблема в том, что вы возвращаете -1
если возникает ошибка. Предположим, что у вас есть два значения: first
и second
. И это по крайней мере один из них спровоцирует исключение.
Поэтому, если вы хотите first
сравнить со second
, вы получите -1
:
compare(first, second) -> -1
Это означает, что first
меньше second
. Но если вы сравните это, вы также получите -1
:
compare(second, first) -> -1
Потому что исключение return -1;
в обоих вариантах, что приводит к вашему return -1;
, Но это означает, что ваш метод compare
говорит:
first < second
second < first
И в то же время, что является логически неправильным и нарушает контракт.
Решение
Вы должны правильно определить, где в вашем заказе находится неповторимый контент. Например, определим, что он всегда меньше любого числа. Итак, мы хотим
text < number
Что мы будем делать, если обе они не пропадут? Мы могли бы сказать, что они равны, мы могли бы сравнить их лексикографические. Пусть это будет просто и сказать, что любые два текста считаются равными:
text = text
Мы реализуем это, проверяя, какой из аргументов не просматриваем, а затем возвращаем правильное значение:
@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);
}
Как указано в комментариях, вы можете заменить весь собственный класс Comparator
следующим выражением, которое генерирует тот же Comparator:
Comparator<Employee> comp = Comparator.nullsLast(
Comparator.comparing(e -> tryParseInteger(e.getBadgeNumber())));
Вместе с методом tryParseInteger
:
public static Integer tryParseInteger(String text) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
return null;
}
}
Ответ 3
Хотя это не так, помните, что вы можете бросать и захватывать Throwable экземпляры, и кроме Исключения есть Ошибки. Ловить их возможно, хотя, когда они происходят, маловероятно, что дальнейшая работа может быть выполнена.
Таким образом, ваш try-catch не поймал бы ошибку или любое Throwable, кроме 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");
}
}
Это приведет к:
Error caught in throwable catch
Exception in thread "main" java.lang.Error: test exception try-catch
at ...
Ответ 4
Это исключение не выбрасывается в методе сравнения, который вы вставили здесь. Проверьте стек. Не Там будет не compare
заехать в него.
Ответ 5
Исключение TimSort.mergeHi()
из TimSort.mergeHi()
вызванного внутренне, поскольку вы явно вызывали Collections.sort()
:
в java.util.TimSort.mergeHi(TimSort.java:868)
Вы можете переместить оператор catch вокруг sort()
но, как следствие, сортировка не будет выполнена или не будет полной. Так что, похоже, это не очень хорошая идея.
Короче говоря: не нарушайте договор compareTo()
и вам не нужно будет ловить никаких исключений, которых больше не будет.