2011-08-02追記 JDK7に致命的なバグ
重要なので先頭に追記しておきますが、JDK7のホットスポットコンパイラに致命的なバグが存在するそうです。
詳しくはコチラ。
http://d.hatena.ne.jp/takahashikzn/20110802
JDK7を使用する際はご注意下さい。
(Javadocのスタイルもガラっと変わりました!!)
http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
JDK7の正式版がリリースされています。
興味深い機能やAPIなどが追加されているわけですが、
その中でもみんな一番気になるところである、Java言語仕様の変更についてご紹介。
あとは、個人的にjava.lang.invokeなんてアヤシゲなパッケージが気になっているのですが…
APIを見る限り、動的言語サポート関連のパッケージっぽいですね、コレ。どなたか解説をお願いします(笑)
では、順次見ていきましょう。
※念のため記載:大半のサンプルコードはリリースノートからの引用(一部、翻訳のために改変)であり、著作権はOracleにあります。
Binary Literals (バイナリリテラル)
これまでは、バイナリ値のリテラルを使おうものなら
byte b = 0x42;
などと書く必要があったわけです。でもこれだと、ビットパターンをイメージしづらい。
(脳内に『16進数→2進数』変換ハードウェア回路を焼成済みの方を除く)
というわけで、JDK7からはこんな風に書くことが出来るようになりました。
byte b = 0b01000010;
byteだけじゃなくて、他の型のバイナリ表現もあります。
// An 8-bit 'byte' value: byte aByte = (byte)0b00100001; // A 16-bit 'short' value: short aShort = (short)0b1010000101000101; // Some 32-bit 'int' values: int anInt1 = 0b10100001010001011010000101000101; int anInt2 = 0b101; int anInt3 = 0B101; // The B can be upper or lower case. // A 64-bit 'long' value. Note the "L" suffix: long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;
もちろん、配列でも使えます。
public static final int[] phases = { 0b00110001, 0b01100010, 0b11000100, 0b10001001, 0b00010011, 0b00100110, 0b01001100, 0b10011000 };
整数型の定数を使える場所ならどこでも使えます。(例えばcaseラベル)
public State decodeInstruction(int instruction, State state) { if ((instruction & 0b11100000) == 0b00000000) { final int register = instruction & 0b00001111; switch (instruction & 0b11110000) { case 0b00000000: return state.nop(); case 0b00010000: return state.copyAccumTo(register); case 0b00100000: return state.addToAccum(register); case 0b00110000: return state.subFromAccum(register); case 0b01000000: return state.multiplyAccumBy(register); case 0b01010000: return state.divideAccumBy(register); case 0b01100000: return state.setAccumFrom(register); case 0b01110000: return state.returnFromCall(); default: throw new IllegalArgumentException(); } } else { final int address = instruction & 0b00011111; switch (instruction & 0b11100000) { case 0b00100000: return state.jumpTo(address); case 0b01000000: return state.jumpIfAccumZeroTo(address); case 0b01000000: return state.jumpIfAccumNonzeroTo(address); case 0b01100000: return state.setAccumFromMemory(address); case 0b10100000: return state.writeAccumToMemory(address); case 0b11000000: return state.callTo(address); default: throw new IllegalArgumentException(); } } }
Underscores in Numeric Literals (アンダースコア入り数値リテラル)
数値リテラルに、可読性のためにアンダースコアを入れられるようになりました。
int i = 1_2_3;
は、
int i = 123;
と同じ意味です。
ただし、文法上そもそもアンダースコアを入れると別の意味になることがあるので、
紛らわしいケースでは注意が必要です。
例えば、
int i = _123;
は
int i = 123;
とは違います。このとき、_123はただの変数名として扱われるため
int _123 = 999; int i = _123; // i == 999
という意味として解釈されます(これまでどおりの仕様)。
Strings in switch Statements (switch文のcaseラベルに文字列を使える)
caseラベルに文字列リテラルを使えるようになりました。
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) { String typeOfDay; switch (dayOfWeekArg) { case "Monday": typeOfDay = "Start of work week"; break; case "Tuesday": case "Wednesday": case "Thursday": typeOfDay = "Midweek"; break; case "Friday": typeOfDay = "End of work week"; break; case "Saturday": case "Sunday": typeOfDay = "Weekend"; break; default: throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg); } return typeOfDay; }
ラベルのマッチングはString#equalsで行われるため、文字列はケース依存です。
で、これの使いどころですが、
if("Monday".equals(dayOfWeekArg)) typeOfDay = "Start of work week"; else if ("Tuesday".equals(dayOfWeekArg)) typeOfday = "Wednesday"; else if...
と書くよりは、多少なりとも効率的なコードへとコンパイルしてくれるそうです。
Type Inference for Generic Instance Creation (コンストラクタ呼び出し時の型推論)
待望の型推論が実装されました。と言ってもコンストラクタ呼び出しの時だけですが。
例えば、こんなコードを
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
こんな風に書くことが出来るようになりました。コレ、超ラクチン。
Map<String, List<String>> myMap = new HashMap<>();
ちなみに、コレはダメです。単にrawタイプ警告が出ます。(従来通りの仕様)
Map<String, List<String>> myMap = new HashMap();
万能ではないので、このようなケースはコンパイルエラーです。
List<String> list = new ArrayList<>(); list.add("A"); // 引数をCollection<? extends String>と解釈してくれないのでコンパイルエラー list.addAll(new ArrayList<>());
Improved Compiler Warnings and Errors When Using Non-Reifiable Formal Parameters with Varargs Methods (型パラメータの可変長引数におけるコンパイラ警告の改善)
型パラメータの可変長引数を使っているところで、コンパイラ警告の発生具合が変わりました。
ヒープ汚染とは
例えば、このようなコードを書くことが可能です。
List l = new ArrayList<Number>(); List<String> ls = l; // 未チェック警告(その1) l.add(0, new Integer(42)); // 未チェック警告(その2) String s = ls.get(0); // ClassCastExceptionが発生
これは何をしているかというと、要するにこういう事です。
List l = new ArrayList(); List ls = l; l.add(0, new Integer(42)); String s = (String) ls.get(0); // ClassCastExceptionが発生
これをヒープ汚染と呼びます。ジェネリクスを使って型の安全性を
保証しているように見せかけて、爆弾を仕込むことが出来るということです。
可変長引数と型パラメータ
以下のようなコードを考えてみます。
ジェネリクスの配列にヒープ汚染を仕込んでいる例です。
import java.util.*; public class ArrayBuilder { public static <T> void addToList (List<T> listArg, T... elements) { for (T x : elements) { listArg.add(x); } } public static void faultyMethod(List<String>... l) { Object[] objectArray = l; // ここまでは正しい objectArray[0] = Arrays.asList(new Integer(42)); // ヒープ汚染させる String s = l[0].get(0); // ClassCastExceptionが発生 } } import java.util.*; public class HeapPollutionExample { public static void main(String[] args) { List<String> stringListA = new ArrayList<String>(); List<String> stringListB = new ArrayList<String>(); ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine"); ArrayBuilder.addToList(stringListA, "Ten", "Eleven", "Twelve"); List<List<String>> listOfStringLists = new ArrayList<List<String>>(); ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB); ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!")); } }
JDK7からは、コンパイラがArrayBuilder.addToListで以下のような警告を出すようになりました。
warning: [varargs] Possible heap pollution from parameterized vararg type T
警告: [可変長引数] 型パラメータTのヒープ汚染が発生する可能性があります。
(訳注: 実際にJDK7のコンパイラでこのコードをコンパイルしてみたわけではないので、コンパイラ警告の和訳は異なるかも)
で、この警告を出さないようにするためには、以下のいずれかの手段を使うことになります。
- 新しく追加された@SafeVarargsを使う
- 但しstaticメソッドでしか使えません
- @SuppressWarnings({"unchecked", "varargs"})を使う
- 但しメソッド呼び出し側の警告を消すことは出来ません
- コンパイラのオプション"-Xlint:varargs."を使う
以下のコードをコンパイルした時、
public class ArrayBuilder {public static
void addToList (List listArg, T... elements) {
for (T x : elements) {
listArg.add(x);
}
}@SuppressWarnings({"unchecked", "varargs"})
public staticvoid addToList2 (List listArg, T... elements) {
for (T x : elements) {
listArg.add(x);
}
}@SafeVarargs
public staticvoid addToList3 (List listArg, T... elements) {
for (T x : elements) {
listArg.add(x);
}
}// ...
}
public class HeapPollutionExample {// ...
public static void main(String[] args) {
// ...
ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);
ArrayBuilder.addToList2(listOfStringLists, stringListA, stringListB);
ArrayBuilder.addToList3(listOfStringLists, stringListA, stringListB);// ...
}
}
コンパイル結果は以下のとおりです。
- addToList
- メソッド宣言で『[unchecked] Possible heap pollution from parameterized vararg type T』
- メソッド呼び出しで『[unchecked] unchecked generic array creation for varargs parameter of type List
[]』
- addToList2
- メソッド呼び出しで『[unchecked] unchecked generic array creation for varargs parameter of type List
[]』
- メソッド呼び出しで『[unchecked] unchecked generic array creation for varargs parameter of type List
- addToList3
- (一切警告を出しません)
The try-with-resources Statement (リソース管理用構文の追加)
必ずクローズしなければならないリソースを使う場合、try-finally構文を使うのがお約束だったわけですが、
ちょっと便利な構文が追加されました。
static String readFirstLineFromFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } }
複数のリソースを使う場合はこんな感じ。宣言とは逆順にcloseが呼ばれます。
try ( java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset) ) { // Enumerate each entry for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) { // Get the entry name and write it to the output file String newLine = System.getProperty("line.separator"); String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine; writer.write(zipEntryName, 0, zipEntryName.length()); } }
この構文で指定するリソースは、java.lang.AutoCloseableインタフェースを実装する必要があります。
これは、java.io.Closeableのスーパーインタフェースです。
だからCloseableを実装しているクラスは、標準でこの構文を使用可能です。
Catching Multiple Exception Types and Rethrowing Exceptions with Improved Type Checking (複数のcatch節、例外の再スローについての改善)
複数のcatch節を書くようなケースが改善されました。
例えば、このようなコードが
try { ... } catch (IOException ex) { logger.log(ex); throw ex; } catch (SQLException ex) { logger.log(ex); throw ex; }
こんな風に書けるようになります。
try { ... } catch (IOException|SQLException ex) { logger.log(ex); throw ex; }
ほとんどのケースでは、例外をキャッチしても出来ることは限られているわけで、
結果としてcatch節の中身は同じようなコードになりがちです。
だからこの構文があると、冗長なコードを減らすことが出来るわけ。
ついでに、コンパイラも専用のコードを生成してくれるようで、catch節を並べて書くよりも処理が効率的になるそうです。
例外再スロー時の型推論
JDK7から、例外再スロー時に型推論が働くようになりました。
// FirstException, SecondException共にチェック例外 public void rethrowException(String exceptionName) throws FirstException, SecondException { try { // FirstExceptionかSecondExceptionだけが発生しうる何らかのコード } catch (Exception e) { throw e; } }
このコード、JDK6までは当然コンパイルエラーですが、JDK7からはコンパイル可能。
要するに、try節とそれに対応するcatch節の中身を見て、throws宣言として適切な型を判断してくれるということです。
以下のケースでこの型推論が機能します。
- try節が投げる可能性のある例外は、thorws節で宣言している例外だけである
- 型推論を適用したいcatch節の前に、先に例外をキャッチしてしまうcatch節が無い
- throws節で宣言する例外は、catch節で宣言する例外のサブクラスである
へぇ、何だか面白い機能ではありますが。。。
そもそもtry-catchを使わなきゃいいんじゃね?という気がしないでもないです。
逆に混乱を招いたりしないか、コレ?