最近、データベースに保存された不良データが原因でプログラムがクラッシュしました。私はこれを防ぐためにキャッチがあると思ったので、これは私を混乱させました。
次のコードの目的は、従業員のバッジ番号を比較して並べ替えることです。エラーが発生した場合は、-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;
}
不正なバッジ番号がヒットした場合(この場合はt)、「Java.lang.IllegalArgumentException:比較メソッドがその一般契約に違反しています!」 catchに-1を返す代わりにエラー。
ここでのキャッチについて理解できないことがありますか?
完全なスタックトレース:
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());
例外(それが何であれ)wasはcatch (Exception e)
によってキャッチされました。この例外をログに記録しなかったため、それが何であるかわかりません。何が実際に起こったかを知るために、何らかの形でログに記録する必要があります。
この問題は、-1
を返すときに発生します。これにより、Javaの現在のソートアルゴリズムがキャッチすることがある、一貫性のない順序付けの可能性が考慮されます。要するに、エラー時に-1
を返すということは、a < b
とb < a
の両方がtrueであるとアサートしていることを意味します。両方の場合で例外がキャッチされるからです。これは論理的に間違っています。ソートアルゴリズムはこれを検出し、IllegalArgumentException
をスローします。 compare
メソッドは、スタックトレースではnotであることに注意してください。 Collections.sort
の呼び出しです。
例外をログに記録することに加えて、プログラムの比較ステップに進む前に例外を処理します。文字列を整数として解析する必要がある場合は、Employee
オブジェクトを作成するときに実行して、プログラムの並べ替え手順に進む前に検証が行われるようにします。 Comparator
はデータを検証する必要はありません。データを比較するだけです。
Java.lang.IllegalArgumentException:比較メソッド違反その一般契約!
try
内から例外はスローされません。それがつかまえられない理由です。例外は、TimSort
クラスを使用するNotifierHTML.Java:363
を呼び出すコード内のCollection#sort
から発生します。その後、TimSort.Java:868
メソッドによってTimSort#mergeHi
から例外がスローされます。
Comparator#compare
メソッドの実装が間違っていることがわかります。 documentation で説明されているように、契約に違反しています。
2つの引数の順序を比較します。 負整数、ゼロ、または正整数を返します。最初の引数はより小、等しい、またはより大きい秒。
実装者必ず確認
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
すべてのx
およびy
に対して。 (これは、x.compareTo(y)
が例外をスローする場合、y.compareTo(x)
が例外をスローする必要があることを意味します。)実装者また確認する必要があるリレーションがtransitiveであること:
(x.compareTo(y) > 0 && y.compareTo(z) > 0)
はx.compareTo(z) > 0
を意味します。最後に、実装者必ず確認
x.compareTo(y) == 0
は、すべてのz
について、sgn(x.compareTo(z)) == sgn(y.compareTo(z))
を意味します。
実装がこれらの要件の1つに違反しており、メソッドがそれを検出しました。
問題は、エラーが発生した場合に-1
を返すことです。 first
とsecond
の2つの値があるとします。そして、そのうちの少なくとも1つが例外を引き起こします。
したがって、first
とsecond
を比較したい場合は、-1
を取得します。
compare(first, second) -> -1
これは、first
がsmallersecond
よりも大きいことを意味します。しかし、他の方法で比較すると、-1
も得られます:
compare(second, first) -> -1
両方のバリアントで例外がスローされるため、return -1;
につながります。しかし、これはあなたのcompare
メソッドが言うことを意味します:
first < second
second < first
両方とも論理的に正しくなく、契約に違反しています。
解析できないコンテンツが配置される順序を正しく定義する必要があります。たとえば、それが常にどんな数字よりも小さいことを定義してみましょう。だから欲しい
text < number
両方が解析できない場合はどうしますか?それらは等しいと言うことができ、辞書式に比較することができます。シンプルに保ち、任意の2つのテキストは等しいと見なされます。
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<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;
}
}
これはそうではありませんが、 Throwable インスタンスをスローしてキャッチでき、例外とは別に Errors があることに注意してください。それらをキャッチすることは可能ですが、それらが発生した場合、それ以上の作業を行うことはできません。
したがって、try-catchは、エラーまたは例外以外のThrowableをキャッチしませんでした。
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 ...
この例外は、ここで貼り付けたcompareメソッドではスローされません。スタックトレースを確認してください。 compare
呼び出しはありません。
TimSort.mergeHi()
を明示的に呼び出したときに、内部で呼び出されたCollections.sort()
から例外がスローされます。
java.util.TimSort.mergeHi(TimSort.Java:868)で
Catchステートメントをsort()
の周りに移動することもできますが、結果としてソートは実行されないか、完全ではありません。したがって、それは良い考えではないようです。
長い話:compareTo()
コントラクトに違反しないでください。これ以上発生しない例外をキャッチする必要はありません。