カリフォルニア州エルセガンドに本拠をおくカスタムアプリケーション開発会社のTechEmpowerが,"Java 8のすべて(Everything about Java 8)" と題するブログ記事を公開している。まもなく来るJava 8において,開発者が直面する変更点を包括的にとりまとめたものだ。ここではその記事の概要を紹介する。詳細な内容についてはTechEmpowerのブログ記事を参照してほしい。
インターフェースの改善
インターフェースでstaticメソッドが定義できるようになった。例えばjava.util.Comparatorには,次のようにstaticなneutralOrderメソッドが追加されている。
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE; }
デフォルトメソッドの定義が可能になった。これによってインターフェースを実装する既存コードを損なうことなく,新しいメソッドを追加することができる。例えばjava.lang.Iterableには,次のようなforEachメソッドがdefaultで定義されている。
public default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
インターフェースではObjectクラスのメソッドのデフォルト実装は定義できない。その点は注意が必要だ。
関数型インターフェース
関数型インターフェース(functional interface)は,ただひとつの抽象メソッドを定義したインターフェースである。インターフェースが関数型インターフェースであることを示す手段として FunctionalInterface アノテーションが導入された。例えば,java.lang.Runnableは,次のように関数型インターフェースだ。
@FunctionalInterface public interface Runnable { public abstract void run(); }
ただしJavaコンパイラは,関数型インターフェースの定義を満足するインターフェースであれば,FunctionalInterfaceアノテーションの有無に関わらず関数型インターフェースとして取り扱う。
ラムダ
関数型インターフェースの重要な特性として,ラムダを使用したインスタンス化が可能なことがある。ラムダ式 (lambda expression) は関数定義をメソッド引数として,つまりコードをデータとして扱うことができる。以下にラムダの実例をいくつか示す。いずれも左辺が入力値で,右辺がコードである。入力値の型は推論可能なので省略してもよい。
(int x, int y) -> { return x + y; }
(x, y) -> x + y
x -> x * x
() -> x
x -> { System.out.println(x); }
次の例はRunnbale関数型インターフェースをインスタンス化するものだ。
Runnable r = () -> { System.out.println("Running!"); }
メソッド参照
メソッド参照(method reference) は,すでに名前を持っているメソッドを対象としたラムダ式の簡略形である。メソッド参照の例を,等価なラムダ式と合わせて以下に示す。
String::valueOf x -> String.valueOf(x) Object::toString x -> x.toString() x::toString () -> x.toString() ArrayList::new () -> new ArrayList<>()
ラムダとキャプチャ
ラムダの外部で定義されたstaticでない変数やオブジェクトにアクセスすることを,ラムダがそれらを "キャプチャ" すると呼ぶ。例えば次のラムダは変数xにアクセスしている。
int x = 5; return y -> x + y;
ラムダ式からアクセス可能なのは,ローカル変数または包含ブロックのパラメータのうち,finalあるいは実質的final(effectively final)なものに限られる。
java.util.function
java.util.functionパッケージには新たに多数の関数型インターフェースが追加された。いくつか例を挙げる。
- Function<T, R> - Tを入力として取り,Rを出力として返す
- Predicate<T> - Tを入力として取り,論理型を出力として返す
- Consumer<T> - Tを入力として取り,何も返さない
- Supplier<T> - 入力を取らず,Tを返す
- BinaryOperator<T> - 2つのTを入力として取り,1つのTを出力として返す
java.util.stream
新しい java.util.stream パッケージでは,ストリーム内のデータを関数形式で操作するクラスが提供されている。ストリームを取得する手段として,一般的なもののひとつはコレクションである。
Stream<T> stream = collection.stream();
パッケージのJavadocから一例を挙げる。
int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum();
この例では,ストリームのソースとしてblocksというCollectionを使用している。そのストリームにfilter-map-reduceを実行して,赤(RED)のblockの重量(weight)の総和(sum)を求める。
使用するストリームは無限長でも,ステートフルでも,あるいはシーケンシャルでもパラレルでも構わない。ストリームを使用する場合,まず何らかのソースからstreamインスタンスを取得する。そのstreamにひとつ以上の中間操作を行った上で,最後の終了処理を実行する,というのが通常の方法だ。中間操作としてはfilter, map, flatMap, peel, distinct, sorted, limit, あるいはsubstreamなどが,終了処理にはforEach, toArray, reduce, collect, min, max, count, anyMatch, allMatch, noneMatch, findFirst, findAnyなどがある。それらの処理で非常に便利なユーティリティクラスのひとつが java.util.stream.Collectors だ。 streamからcollectionへの変換や,要素の集約などといった,さまざまなreduction操作が実装されている。
ジェネリック型インターフェースの改善
この改善は,Javaコンパイラが総称型を推論する能力を備えることで,ジェネリック型メソッド呼び出し時の明示的な型引数の必要性を低減するものだ。Java 7でのコードは次のようなものだった。
foo(Utility.<Type>bar()); Utility.<Type>foo().bar();
Java 8では,引数と連鎖呼び出しの推論が改善されたことにより,次のような記述が可能になる。
foo(Utility.bar()); Utility.foo().bar();
java.time
新しい日付/時刻APIが java.time パッケージに追加されている。クラスはすべて不変(immutable)かつスレッドセーフである。日付と時刻に関する型としてはInstant, LocalDate, LocalTime, LocalDateTime, ZonedDateTimeなどが,日付と時刻以外ではDurationとPeriodがある。新たに追加された値型はMonth, DayOfWeek, Year, Month, YearMonth, MonthDay, OffsetTime, OffsetDateTimeなどだ。これら新しい日付/時刻クラスは,その大部分がJDBCでサポートされている。
Collections APIの拡張
インターフェースがdefaultメソッドを持てるようになったことにより,Java 8のCollection APIには多数のメソッドが新たに追加されている。インターフェースにはすべてdefaultメソッドが実装された。また,可能な部分については,具象クラスでさらに効率的な実装が追加されている。以下は新しいメソッドの一覧である。
- Iterable.forEach(Consumer)
- Iterator.forEachRemaining(Consumer)
- Collection.removeIf(Predicate)
- Collection.spliterator()
- Collection.stream()
- Collection.parallelStream()
- List.sort(Comparator)
- List.replaceAll(UnaryOperator)
- Map.forEach(BiConsumer)
- Map.replaceAll(BiFunction)
- Map.putIfAbsent(K, V)
- Map.remove(Object, Object)
- Map.replace(K, V, V)
- Map.replace(K, V)
- Map.computeIfAbsent(K, Function)
- Map.computeIfPresent(K, BiFunction)
- Map.compute(K, BiFunction)
- Map.merge(K, V, BiFunction)
- Map.getOrDefault(Object, V)
Concurrency APIの拡張
Concurrency APIにも機能が追加されている。いくつか簡単に紹介しよう。ForkJoinPool.commonPool()は,すべての並列ストリーム操作を処理する構造体である。ForkJoinTaskは明示的に固有のプールを持たず,共通のプールを使用するようになった。ConcurrentHashMapが完全に書き直された。新たなロック処理実装であるStampedLockは,ReentrantReadWriteLockの代替として使用できる。Futureインターフェースの実装であるCompletableFutureでは,非同期タスクの実行とチェーンのためのメソッドが提供される。
IO/NIO APIの拡張
IO/NIOにもメソッドが追加されていて,ファイルや入力ストリームからjava.util.stream.Streamメソッドを生成するために使用される。
- BufferedReader.lines()
- Files.list(Path)
- Files.walk(Path, int, FileVisitOption...)
- Files.walk(Path, FileVisitOption...)
- Files.find(Path, int, BiPredicate, FileVisitOption...)
- Files.lines(Path, Charset)
- DirectoryStream.stream()
新たなクラスのUncheckedIOExceptionは,RuntimetimeExceptionを拡張するIOExceptionである。クローズが可能かつ必要なストリームクラスとしてCloseableStreamも追加された。
リフレクションとアノテーションの変更
型アノテーション がこれまでより多くの場所に記述できるようになった。例えばList<@Nullable String>のように,ジェネリックの型パラメータに記述することもできる。これによって静的解析ツールの検出するエラー範囲が拡大すると同時に,Javaの組み込み型システムが強化され,洗練される。
Nashorn JavaScript エンジン
Nashornは新たにJDKに統合された,軽量で高性能なJavaScript実装である。Rhinoの後継であり,パフォーマンスとメモリ消費が改善されている。javax.script APIはサポートされているが,ただしDOM/CSSとブラウザプラグインAPIは含まれていない。
java.lang, java.uti, その他の拡張
ここまでに述べた以外のパッケージにも,まだ多数の追加機能がある。注目すべきものをいくつか挙げよう。ThreadLocal.withInitial(Supplier)は,よりコンパクトなスレッドローカル変数の定義を可能にする。長年の懸案だったStringJoinerとString.join(...)がJava 8で実現される。Comparatorは,チェーンあるいはフィールドベースの比較を行う新たなメソッドをいくつか提供する。 Stringプールマップのサイズ既定値が25~50K程度まで拡張された。
ぜひJava 8のすべてブログポストを参照して,すべての詳細を確認して欲しい。ブログ記事は2013年9月に最後の修正が行われている。