Java SE 7 の言語拡張

Java SE 7 で導入された新しい言語拡張について簡単にまとめておきます。言語レベルの話ですので、ライブラリレベルの変更点はもっとたくさんありますが。

バイナリリテラル

int や long 値を表記する際に 0b11011 のような 2 進数表記の数値リテラルが可能になりました。バイナリ入出力でビットフラグやマスク、ビットパターンを記述する場合に便利ですね。

int x = 0b1010;
System.out.println(x);     // 10
数値リテラルのアンダースコア

数値リテラルの任意の位置にアンダースコア '_' を入れられるようになりました。これは 0x7FFF_FFFF のように可読性目的で使用するものです。とはいえ桁数の大きなリテラルを確認するケースに遭遇するのも年に何回かというところでしょうか。

long x = 9_223_372_036_854_775_807L;
System.out.printf("0x%X%n", x);    // 0x7FFFFFFFFFFFFFFF
switch 構文での文字列使用

switch 構文の値に文字列が使えるようになりました。

String x = "b";
switch(x){
case "a": System.out.println("A"); break;
case "b": System.out.println("B"); break;    // B
case "c": System.out.println("C"); break;
}

この文字列 switch は String#hashCode() を使用してジャンプテーブルを作成するため if-else 構文での文字列比較による分岐よりも効率よく動作します。またコンパイル時にジャンプテーブルを作成するため case に指定出来る文字列は定数リテラルのみです。

コンパイルされたバイトコードを読んで見ると、イメージ的には以下の様な動きとなっています。

String x = "b";
switch(x.hashCode()){
case 97 /* "a".hashCode() */: ...; break;
case 98 /* "b".hashCode() */: ...; break;
case 99 /* "c".hashCode() */: ...; break;
}
ジェネリック型推論のためのインスタンス生成の改善

総称型で定義された変数に new で代入する場合、右辺値の型パラメータが省略されていたら左辺値から推論するという機能。いわゆるダイヤモンド・オペレータという奴です。総称型の宣言文にありがちな長々としたステップを改善しコードの可視性を良くする目的で使用します。

Map<String,String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
System.out.println(map);    // {b=B, a=A}
try-with-resources 構文

ブロック終了時に自動的にリソースを開放するための構文。いわゆるローンパターンの完全形になったというところですか。java.lang.AutoCloseable または java.io.Closeable を実装したクラスを try のリソース宣言に一つ以上定義することで、try ブロックの終了時に必ず該当オブジェクトの close() が呼び出されます。

try(Reader in = new FileReader("foo.txt");
  Writer out = new FileWriter("bar.txt")){
  while(true){
    int ch = in.read();
    if(ch < 0) break;
    out.write(ch);
  }
}
複数種類の例外のキャッチ

try-catch 構文で複数の例外型をまとめて catch できるようになりました。セマンティクスとしては型だけ異なる catch ブロックが型の数だけ連なっているのと同じ意味です。

try {
  longPollingRead();
} catch(IOException | InterruptedException ex){
  ex.printStackTrace();
}

特定の例外のみ catch して共通処理を行いたい場合などに便利。catch した例外は throw する事も出来ます。

より包括的な型チェックでの再スロー

コンパイラが try-catch ブロック内で発生しうる例外の種類を判別し、その親クラス指定で catch した例外を個別の型で再 throw できるようになりました (前述の複数種類 catch と被っている気がしなくもないですが…)。

private static void read() throws IOException{ ... }
private static void poll() throws InterruptedException{ ... }
public static void foo() throws IOException, InterruptedException {
  try {
    poll();
    read();
  } catch(Exception ex){
    ex.printStackTrace();
    throw ex;
  }
}

Java 6 まで上記のコードでの foo() メソッドは throws Exception しか付けられませんでしたが、7 から try-catch 内で発生しうる例外のみを記述することが可能になります。

具体化されていない可変引数メソッドの警告

可変引数メソッドまたはコンストラクタにおいて具体化されていない (non-reifiable) フォーマルパラメータを使用した場合に宣言側で警告を出力するよう変更されました。以下 man ページから引用:

可変引数 (varargs) メソッド、特に非具象化可能引数を含むものの使用が安全でないことを警告します。次に例を示します。

public class ArrayBuilder {
  public static <T> void addToList (List<T> listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }
}

コンパイラは、varargs メソッドを検出すると、varargs の仮パラメータを配列に変換します。しかし、Java プログラミング言語では、パラメータ化された型の配列の作成を許可していません。メソッド ArrayBuilder.addToList では、コンパイラは varargs の仮パラメータ T... elements を仮パラメータ T elements(配列) に変換します。しかし、型消去のために、コンパイラは varargs の仮パラメータを Object elements に変換します。その結果、ヒープ汚染が発生する可能性があります。

このヒープ汚染に関する問題は "http://docs.oracle.com/javase/tutorial/java/generics/non-reifiable-varargs-type.html" 参照。

public static <T> String join(T... elem){
  StringBuilder buffer = new StringBuilder();
  for(T e: elem){ buffer.append(e.toString()); }
  return buffer.toString();
}

上記のコードを Java 7 でコンパイルすると以下の様な警告が出力されます。

torao@safran$ /usr/java/jdk1.7/bin/javac -Xlint:unchecked A.java
A.java:4: 警告:[unchecked] パラメータ化された可変引数型Tからのヒープ汚染の可能性があります
public static <T> String join(T... elem){
                                   ^
  Tが型変数の場合:
    メソッド <T>join(T...)で宣言されているT extends Object
警告1

英語のメッセージは "warning: [varargs] Possible heap pollution from parameterized vararg type T" です。この警告を抑止するために Java 7 コンパイラではオプション -Xlint:varargs とアノテーション @SafeVarargs, @SuppressWarnings({"unchecked", "varargs"}) が導入されています。

Java 7 で採用されなかった機能

リスト、マップの記述性改善

コレクションリテラルは個人的に入れて欲しかった。PythonRuby など Perl 系の言語で一般的な List や Map リテラルの簡略記法です。

List<String> list = ["A", "B", "C", "D"];    // Arrays.asList("A","B","C","D") と等価
Map<Integer,String> map = {0:"A", 1:"B", 2:"C", 3:"D"};

また煩わしい get/set/put の頻発を記述しやすくするために、リストやマップで添え字アクセスのシンタックスシュガーを付ける提案もあります。

List<String> list = Arrays.asList("A","B","C","D");
System.out.println(list[0]);
エルビス演算子

null 値チェックの記述を行うための簡略記法。Groovy でこのような書き方があるようです。Scala では Options を使うところでしょうか。名前の由来は ?: を横から見るとエルビスプレスリーに見えるためとか。構文展開が面倒だから見送られたのでしょうか。

String value = array?[0]?.getValue()?:"nothing";
// 以下と同じ
String value = null;
if(array != null && array[0] != null && array[0].getValue() != null){
  value = array[0].getValue();
} else {
  value = "nothing";
}
ラージ配列

現在の言語仕様で Integer.MAX_VALUE に制限されている配列の最大長をとっぱらおうというもの。 大量のログの集計など、一回実行できれば良いプログラムではわざわざ DB や B-Tree のページングなど作らなくても良いので楽かもしれません。ただそこまで大量のデータを扱うなら巨大配列クラスのような実装をライブラリレベルで提供した方が良いと個人的には思いますが。
この提案は Java VM レベルまで大改修が必要となるため見送られたのでしょうか。そんな気がします。