argius note

プログラミング関連

Java SE 8 (4) - 新しいAPIと改良されたAPI

このエントリーでは、これまでに紹介した機能以外の、新しいAPIと改良されたAPIについてまとめています。
(2014-03-21追記)APIドキュメントのリンクを差し替えました。


ラムダに伴うコアライブラリーの拡張

ラムダ式の導入に伴い、コアライブラリーにもラムダ式を使ったメソッドが多数、追加されました。
ここでは、次の3つに絞ってご紹介します*1

内部イテレーターとIterable#forEach

java.lang.Iterableインターフェイスに、Iterable#forEachデフォルトメソッドが追加されました。
これまでは、IteratorJava5の拡張for文とIterableの組み合わせによる外部イテレーターが標準的でしたが、forEachメソッドラムダ式の組み合わせによる内部イテレーターも標準搭載されたことになります。

forEachメソッドに渡すのはjava.util.function.Consumerです。


これは一見、コレクションに含まれる機能のような気もしますが、コレクションとは直接関係のない、例えば、java.nio.file.Pathjava.sql.SQLExceptionIterableを実装しているので、forEachが使えるようになっています。

// import java.util.*; // Arrays, List
Arrays.asList("1", "2", "3").forEach(x -> System.out.printf("<%s>", x));
// => <1><2><3>

// import java.nio.file.*; // Path, Paths
Path path = Paths.get("/home/argius/file");
path.forEach(x -> System.out.printf("<%s>", x));
// => <home><argius><file>


なお、Iterableには、Iterable#spliteratorデフォルトメソッドも追加されています。これは、どちらかというとストリーム関連でしょうか...

Comparatorの拡張

Comparatorインターフェイスは、元々、比較演算子の性質*2を持っています。Java8のラムダの導入に伴い、@FunctionalInterfaceが付けられ、より演算子らしくなりました。唯一のインスタンスメソッドであるComparator#compareは、ToIntBiFunction<T, T>に相当するインターフェイスを持っています。

また、仮想拡張メソッドを活用した、ファクトリーメソッドも大量に追加されました。
例えば、Comparator.comparingメソッドは、ソートキーを抽出するFunction型関数を指定することで、Comparatorインスタンスを生成することができます。

// import java.util.*; // Arrays, Comparator

String[] sa = { "Wednesday", "alphabet", "one", "Monday", "zero" };
Runnable p = () -> System.out.println(Arrays.toString(sa));

// ケース無視の辞書順を反転
Comparator<String> cmp1 = (x, y) -> String.CASE_INSENSITIVE_ORDER.compare(x, y) * -1;
Arrays.sort(sa, cmp1);
p.run(); // => [zero, Wednesday, one, Monday, alphabet]

// 反転の反転で、CASE_INSENSITIVE_ORDERと同じ条件に戻る
Arrays.sort(sa, cmp1.reversed());
p.run(); // => [alphabet, Monday, one, Wednesday, zero]

// 文字列の短い順: ソートキーを文字列の長さにする
Comparator<String> cmp2 = Comparator.comparing(String::length);
Arrays.sort(sa, cmp2);
p.run(); // => [one, zero, Monday, alphabet, Wednesday]



プリミティブラッパークラスの二項演算子メソッド

プリミティブラッパークラスのInteger,Long,Float,Doubleに、クラスメソッドsum,max,minが追加されました。
また、Booleanには、クラスメソッドlogicalAnd,logicalOr,logicalXorが追加されました。
これらは、いずれもBinaryOperatorに相当します。


sum,max,minは、ストリームなどで使われます。例えば、IntStream#sumの実装は、IntStream.reduce(0, Integer::sum)が使われています。

Booleanの方は直接は使われていないようですが、定数のような扱いなのかも知れません。

// import java.util.function.IntBinaryOperator;

IntBinaryOperator[] ops
    = { Integer::sum, Integer::max, Integer::min };
IntBinaryOperator calcAndPrint = (x, y) -> {
    System.out.printf("%2d ? %2d = ", x, y);
    for (IntBinaryOperator op : ops)
        System.out.printf("%2d, ", op.applyAsInt(x, y));
    System.out.println();
    return 0;
};
calcAndPrint.applyAsInt(5, 3);
// =>  5 ?  3 =  8,  5,  3, 
calcAndPrint.applyAsInt(12, 16);
// => 12 ? 16 = 28, 16, 12, 


// import java.util.function.BiPredicate;

BiPredicate<Boolean, Boolean> and = Boolean::logicalAnd;
BiPredicate<Boolean, Boolean> nand = and.negate();
System.out.println(nand.test(false, false)); // => true
System.out.println(nand.test(false, true));  // => true
System.out.println(nand.test(true,  false)); // => true
System.out.println(nand.test(true,  true));  // => false



それにしても、テストスイートでないAPItestを名前に使うのは、どうにも抵抗がありますね...


新しい日付・時刻API

Java8の新機能の中で、ラムダの次に大きなものが、これです。
これまでの日時APIは、改良が重ねられたものの、あまり使いやすいものとは言えず、外部ライブラリーのお世話になることが多かったりと、ちょっと残念なものでした。
今回、ついに本格的な日時APIが標準搭載されることになりました。

java.timeパッケージとそのサブパッケージのAPI群が追加されています。

既存の日時APIとの相互変換は、下記のメソッドを使います。相互と言っても、新API側には直接変換する機能はありません*3Instantとしか変換できなかったりして、使い勝手はあまり良くありませんけどね。

新しい日付・時刻APIは、かなり大きなAPI(public型だけでも69)なので、ここでは簡単な例を示すだけに留めておきます。

  • サンプル: 新しい日付・時刻APIの日時表現
// import java.time.*;
LocalDateTime local0 = LocalDateTime.now();
LocalDateTime local1 = LocalDateTime.parse("2014-01-23T12:34:56.789");
LocalDateTime local2 = LocalDateTime.parse("2014-01-23T12:34");
System.out.println("local0  = " + local0);
System.out.println("local1  = " + local1);
System.out.println("local2  = " + local2);
// local0  = 2014-01-23T18:44:38.905
// local1  = 2014-01-23T12:34:56.789
// local2  = 2014-01-23T12:34

ZonedDateTime zoned0 = ZonedDateTime.now(ZoneId.of("+0900"));
ZonedDateTime zoned1 = local0.atZone(ZoneOffset.UTC);
ZonedDateTime zoned2 = local0.atZone(ZoneOffset.of("+0900"));
System.out.println("zoned0  = " + zoned0);
System.out.println("zoned1  = " + zoned1);
System.out.println("zoned2  = " + zoned2);
// zoned0  = 2014-01-23T18:44:38.963+09:00
// zoned1  = 2014-01-23T18:44:38.905Z
// zoned2  = 2014-01-23T18:44:38.905+09:00

OffsetDateTime offset0 = OffsetDateTime.now(ZoneId.of("Asia/Tokyo"));
OffsetDateTime offset1 = zoned0.toOffsetDateTime();
OffsetDateTime offset2 = offset0.withOffsetSameInstant(ZoneOffset.UTC);
System.out.println("offset0 = " + offset0);
System.out.println("offset1 = " + offset1);
System.out.println("offset2 = " + offset2);
// offset0 = 2014-01-23T18:44:38.964+09:00
// offset1 = 2014-01-23T18:44:38.963+09:00
// offset2 = 2014-01-23T09:44:38.964Z

Base64エンコード・デコード

Java8では、Base64エンコード・デコードの標準APIである、java.util.Base64クラスとそのサブクラスjava.util.Base64.Decoder
java.util.Base64.Encoderが追加されました。

import java.util.Base64;
final class Base64EncodingAndDecoding {
    public static void main(String[] args) {
        byte[] input = args[0].getBytes();
        // encode
        Base64.Encoder encoder1 = Base64.getEncoder();
        Base64.Encoder encoder2 = Base64.getMimeEncoder();
        String encoded1 = encoder1.encodeToString(input);
        String encoded2 = encoder2.encodeToString(input);
        System.out.println("encoded1 = " + encoded1);
        System.out.println("encoded2 = " + encoded2);
        // decode
        Base64.Decoder decoder1 = Base64.getDecoder();
        Base64.Decoder decoder2 = Base64.getMimeDecoder();
        byte[] decoded1 = decoder1.decode(encoded1);
        byte[] decoded2 = decoder2.decode(encoded2);
        System.out.println("decoded1 = " + new String(decoded1));
        System.out.println("decoded2 = " + new String(decoded2));
    }
}
  • 実行結果
$ java -cp bin Base64EncodingAndDecoding Base64エンコード・デコードテストああああああああああああああああああああああああああああああ
encoded1 = QmFzZTY0g0eDk4NSgVuDaIFFg2aDUoFbg2iDZYNYg2eCoIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqA=
encoded2 = QmFzZTY0g0eDk4NSgVuDaIFFg2aDUoFbg2iDZYNYg2eCoIKggqCCoIKggqCCoIKggqCCoIKggqCC
oIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqCCoIKggqA=
decoded1 = Base64エンコード・デコードテストああああああああああああああああああああああああああああああ
decoded2 = Base64エンコード・デコードテストああああああああああああああああああああああああああああああ
$ 

並行処理APIの改良

主に次の改良が加えられています。

  1. 新規Atomic値クラス(下記4クラス)の導入によるスケーラビリティ向上
  2. java.util.concurrent.ConcurrentHashMapの(主に参照の)スケーラビリティ向上
  3. java.util.concurrent.ForkJoinPoolのパフォーマンス向上
    • システムプロパティで、ForkJoinPoolのデフォルト値が設定できる(詳細はクラスのドキュメントに記載されている)

JDBC 4.2

JDBC 4.2としていますが、ここではjava.sqlパッケージの変更をまとめて書いてしまいます。*4

  1. 総称SQL型(generic SQL type)の導入
  2. REF_CURSORのサポート
  3. Java EE環境のためのDataSourceプロパティの指定(詳細不明)
  4. DatabaseMetaData#getIndexInfoの結果(ResultSet)に、CARDINALITY列とPAGESを追加
  5. DatabaseMetaData#getMaxLogicalLobSizeデフォルトメソッドの追加
  6. (Additional clean up of the spec as needed)
  7. 新しい日時APIとの相互変換
  8. longupdateメソッドの新設
  9. JDBCドライバーの登録解除サポート

配列の並列ソート

java.util.ArraysクラスにArrays.parallelSortメソッドオーバーロード含む)が追加されました。
この並列ソートは、java.util.concurrent.ForkJoinPoolで実現されています。
ちなみに、今回確認しているJDKの実装では、配列の要素数8192以下または並列処理レベル(parallelism level)が1の場合は、通常のソートが使われるようです。


RFC 4647 (BCP 47) 言語タグマッチング

Javaロケールの、言語タグマッチングのサポートです。言語タグ自体のサポートは、Java7で既に導入されています。今回の変更は、マッチング機能の追加です。言語タグと言語タグマッチングについては、下記のリンクを参照。

java.util.Localeクラスのサブ列挙型Locale.FilteringModeと、サブクラスLocale.LanguageRangeが追加され、Localeクラスにもこの機能に関連するメソッドが追加されています。

  • サンプル: 言語タグマッチング
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Locale.LanguageRange;
import java.util.stream.Collectors;
final class LanguageTagMatching {
    public static void main(String... args) {
        List<Locale> locales = Arrays.asList(args).stream()
            .map(x -> Locale.forLanguageTag(x))
            .collect(Collectors.toList());
        System.out.printf("locales=%s%n", locales);
        List<LanguageRange> priorityList1 = LanguageRange.parse("de-DE");
        List<LanguageRange> priorityList2 = LanguageRange.parse("de-*-DE");
        List<LanguageRange> priorityList3 = LanguageRange.parse("ja-*-jp");
        System.out.printf("filtered1=%s%n", Locale.filter(priorityList1, locales));
        System.out.printf("filtered2=%s%n", Locale.filter(priorityList2, locales));
        System.out.printf("filtered3=%s%n", Locale.filter(priorityList3, locales));
    }
}
  • 実行結果
$ java LanguageTagMatching de-Latn-DE ja-JP-u-ca-japanese-x-lvariant-JP de-x-DE ja-JP
locales=[de_DE_#Latn, ja_JP_JP_#u-ca-japanese, de__#x-de, ja_JP]
filtered1=[]
filtered2=[de_DE_#Latn]
filtered3=[ja_JP_jp_#u-ca-japanese, ja_JP]
$ 

Unicode 6.2 サポート

java.lang.CharacterクラスのサブクラスであるCharacter.UnicodeBlockに新しくサポートされたブロックが追加されています。

詳細は、下記のリンク先を参照してください。

その他の拡張

ここまで紹介したもの以外にも、拡張されたAPIは多数あります。
なお、セキュリティー関連などの一部のAPIについては、次のエントリーで触れています。


最後に、主にjava.lang,java.utilパッケージに含まれる、新たに追加されたクラスとメソッドについて、ざっとご紹介します。







*1:本項の元となるJEP 109とは主旨が変わっています。

*2:Perlなら等値演算子のcmp,<=>

*3:古いAPIを消しても新しいAPI自体には影響が無いように依存関係を絶っておく、というのは良くあることです。

*4:1~6がJEPに記載されている変更です。