本題
昨日のこのタイトルとほぼ同名の記事を書いた後にさくらばさんに
アドバイスもらいました。
@mike_neck ヒント http://ow.ly/bSyBt
— Yuichi Sakurabaさん (@skrb) 6月 28, 2012
おお、そういえば、いろふさんのブログにもあったような…
Throwable#getSuppressed()
なるものがあって、最初に投げられた例外にそのあとに投げられた例外が保存されているようです。
気を取り直して
昨日のコードを書きなおしてみました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.mikeneck.j7; | |
import org.mikeneck.j7.resource.*; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import java.util.Arrays; | |
import java.util.List; | |
/** | |
* Copyright 2012- @mike_neck | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
public class AutoCloseableSample { | |
Logger logger = LoggerFactory.getLogger(AutoCloseableSample.class); | |
public static void main (String[] args) { | |
AutoCloseableSample sample = new AutoCloseableSample(); | |
sample.run(); | |
} | |
public void run () { | |
for (When when : When.values()) { | |
logger.info("{} - [1] before open resource", when.formattedName()); | |
try (Resource act = new Resource(when)) { | |
logger.info("{} - [2] before act something", when.formattedName()); | |
act.something(); | |
logger.info("{} - [3] after act something", when.formattedName()); | |
} catch (OnConstructorException e) { | |
logger.info("{} - [4] exception on construction {}", | |
when.formattedName(), e.getMessage()); | |
messagingSuppressedErrors(when, 5, e); | |
} catch (OnMethodException e) { | |
logger.info("{} - [6] exception on act something {}", | |
when.formattedName(), e.getMessage()); | |
messagingSuppressedErrors(when, 7, e); | |
} catch (OnCloseException e) { | |
logger.info("{} - [8] exception on closing {}", | |
when.formattedName(), e.getMessage()); | |
} | |
logger.info("{} - [9] finished", when.formattedName()); | |
} | |
} | |
private void messagingSuppressedErrors (final When when, | |
final Integer messageNumber, | |
final Throwable t) { | |
List<Throwable> list = Arrays.asList(t.getSuppressed()); | |
if (list.size() > 0) { | |
String errors = extractMessages(list); | |
logger.info("{} - [{}] suppressed exceptions {}", | |
new Object[]{when.formattedName(), messageNumber, errors}); | |
} | |
} | |
private String extractMessages(List<Throwable> list) { | |
StringBuilder builder = new StringBuilder(); | |
builder.append("["); | |
for (Throwable throwable : list) { | |
builder.append(throwable.getMessage()); | |
} | |
builder.append("]"); | |
return builder.toString(); | |
} | |
} |
catch
の部分で他に例外がsuppressされていないか確認してみました。で、実行結果がこれです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AutoCloseableSample - OnConstructorOnly - [1] before open resource | |
Resource - OnConstructorOnly - [a] constructing | |
AutoCloseableSample - OnConstructorOnly - [4] exception on construction OnConstructorOnly | |
AutoCloseableSample - OnConstructorOnly - [9] finished | |
AutoCloseableSample - OnConstructorWithClose - [1] before open resource | |
Resource - OnConstructorWithClose - [a] constructing | |
AutoCloseableSample - OnConstructorWithClose - [4] exception on construction OnConstructorWithClose | |
AutoCloseableSample - OnConstructorWithClose - [9] finished | |
AutoCloseableSample - OnMethodOnly - [1] before open resource | |
Resource - OnMethodOnly - [a] constructing | |
AutoCloseableSample - OnMethodOnly - [2] before act something | |
Resource - OnMethodOnly - [b] acting | |
Resource - OnMethodOnly - [c] closing | |
AutoCloseableSample - OnMethodOnly - [6] exception on act something OnMethodOnly | |
AutoCloseableSample - OnMethodOnly - [9] finished | |
AutoCloseableSample - OnMethodWithClose - [1] before open resource | |
Resource - OnMethodWithClose - [a] constructing | |
AutoCloseableSample - OnMethodWithClose - [2] before act something | |
Resource - OnMethodWithClose - [b] acting | |
Resource - OnMethodWithClose - [c] closing | |
AutoCloseableSample - OnMethodWithClose - [6] exception on act something OnMethodWithClose | |
AutoCloseableSample - OnMethodWithClose - [7] suppressed exceptions [OnMethodWithClose] | |
AutoCloseableSample - OnMethodWithClose - [9] finished | |
AutoCloseableSample - OnCloseOnly - [1] before open resource | |
Resource - OnCloseOnly - [a] constructing | |
AutoCloseableSample - OnCloseOnly - [2] before act something | |
Resource - OnCloseOnly - [b] acting | |
AutoCloseableSample - OnCloseOnly - [3] after act something | |
Resource - OnCloseOnly - [c] closing | |
AutoCloseableSample - OnCloseOnly - [8] exception on closing OnCloseOnly | |
AutoCloseableSample - OnCloseOnly - [9] finished | |
AutoCloseableSample - NoException - [1] before open resource | |
Resource - NoException - [a] constructing | |
AutoCloseableSample - NoException - [2] before act something | |
Resource - NoException - [b] acting | |
AutoCloseableSample - NoException - [3] after act something | |
Resource - NoException - [c] closing | |
AutoCloseableSample - NoException - [9] finished |
メソッド実行中とクローズ中に発生した例外について、
メソッド実行中の例外にクローズ中に発生した例外がsuppressされているのが確認できます。
実際、どうなってんの?
さて、さくらばさんからこんなアドバイスももらっています。
@mike_neck try...with...resourcesをデコンパイルしてみると、おもしろいですよ。Suppressedがどうやって使われているか分かります。
— Yuichi Sakurabaさん (@skrb) 6月 28, 2012
というわけで、デコンパイルしてみようとしたらJDってツールがダウンロード出来ない…(´・ω・`)
Jadの方をダウンロードしてデコンパイルしてみました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Decompiled through IntelliJad */ | |
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. | |
// Jad home page: http://www.kpdus.com/jad.html | |
// Decompiler options: packfields(3) packimports(3) splitstr(64) radix(10) lradix(10) | |
// Source File Name: AutoCloseableSample.java | |
package org.mikeneck.j7; | |
import java.util.*; | |
import org.mikeneck.j7.resource.OnCloseException; | |
import org.mikeneck.j7.resource.OnConstructorException; | |
import org.mikeneck.j7.resource.OnMethodException; | |
import org.mikeneck.j7.resource.Resource; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
// Referenced classes of package org.mikeneck.j7: | |
// When | |
public class AutoCloseableSample | |
{ | |
public AutoCloseableSample() | |
{ | |
logger = LoggerFactory.getLogger("org/mikeneck/j7/AutoCloseableSample"); | |
} | |
public static void main(String args[]) | |
{ | |
AutoCloseableSample sample = new AutoCloseableSample(); | |
sample.run(); | |
} | |
public void run() | |
{ | |
When arr$[]; | |
int len$; | |
int i$; | |
arr$ = When.values(); | |
len$ = arr$.length; | |
i$ = 0; | |
_L3: | |
if(i$ >= len$) goto _L2; else goto _L1 | |
_L1: | |
When when; | |
when = arr$[i$]; | |
logger.info("{} - [1] before open resource", when.formattedName()); | |
Resource act; | |
Throwable throwable; | |
act = new Resource(when); | |
throwable = null; | |
try | |
{ | |
logger.info("{} - [2] before act something", when.formattedName()); | |
act.something(); | |
logger.info("{} - [3] after act something", when.formattedName()); | |
} | |
catch(Throwable throwable1) | |
{ | |
throwable = throwable1; | |
throw throwable1; | |
} | |
if(act != null) | |
if(throwable != null) | |
try | |
{ | |
act.close(); | |
} | |
catch(Throwable x2) | |
{ | |
throwable.addSuppressed(x2); | |
} | |
else | |
act.close(); | |
break MISSING_BLOCK_LABEL_276; | |
Exception exception; | |
exception; | |
if(act != null) | |
if(throwable != null) | |
try | |
{ | |
act.close(); | |
} | |
catch(Throwable x2) | |
{ | |
throwable.addSuppressed(x2); | |
} | |
else | |
act.close(); | |
throw exception; | |
OnConstructorException e; | |
e; | |
logger.info("{} - [4] exception on construction {}", when.formattedName(), e.getMessage()); | |
messagingSuppressedErrors(when, Integer.valueOf(5), e); | |
break MISSING_BLOCK_LABEL_276; | |
e; | |
logger.info("{} - [6] exception on act something {}", when.formattedName(), e.getMessage()); | |
messagingSuppressedErrors(when, Integer.valueOf(7), e); | |
break MISSING_BLOCK_LABEL_276; | |
e; | |
logger.info("{} - [8] exception on closing {}", when.formattedName(), e.getMessage()); | |
logger.info("{} - [9] finished", when.formattedName()); | |
i$++; | |
goto _L3 | |
_L2: | |
} | |
private void messagingSuppressedErrors(When when, Integer messageNumber, Throwable t) | |
{ | |
List list = Arrays.asList(t.getSuppressed()); | |
if(list.size() > 0) | |
{ | |
String errors = extractMessages(list); | |
logger.info("{} - [{}] suppressed exceptions {}", new Object[] { | |
when.formattedName(), messageNumber, errors | |
}); | |
} | |
} | |
private String extractMessages(List list) | |
{ | |
StringBuilder builder = new StringBuilder(); | |
builder.append("["); | |
Throwable throwable; | |
for(Iterator i$ = list.iterator(); i$.hasNext(); builder.append(throwable.getMessage())) | |
throwable = (Throwable)i$.next(); | |
builder.append("]"); | |
return builder.toString(); | |
} | |
Logger logger; | |
} |
ところどころ残念なことになってはいますが、まあ読めますね。
メソッド実行中とクローズ中の例外に例外が発生するパターンでは…
- 52行目の
try
からが本来のtryに該当するところで、55行目のact.something()
で例外が発生。 - 63行目と64行目の条件はtrueになるので、67行目の
act.close()
を実行して例外が発生。 - 二度目に発生した例外は69行目の
catch
で捕まって、71行目で最初に発生した例外にaddSuppressed(java.lang.Throwable)
される。 - 76行目以降の同じようなコードはここまで例外が発生しなかった場合のauto
close()
処理ですかね。 - 92行目以降の
e;
ってなってるのは本来のJavaコードのcatch
部分で、各例外のハンドリングが実施される。
といった感じでしょうか。
なるほど、こういう風にコンパイル時にコードが展開されるんですね。
副作用
というわけで、昨日の記事を訂正したんですが、
どうやらバイトコードに興味を持ちました。
で、そういったあたりをTwitterで呟いたら
@mike_neck それは当然ですよw Javaやっているのに、バイトコード知らないなんてありえないじゃないですかwwww そういえば、Facebookでコメントしたんだけど、invoke dynamicじゃなくて、invokedynamicです。
— Yuichi Sakurabaさん (@skrb) 6月 29, 2012
ツイッター怖っ!