Java SE 8 (4) - 新しいAPIと改良されたAPI
このエントリーでは、これまでに紹介した機能以外の、新しいAPIと改良されたAPIについてまとめています。
(2014-03-21追記)APIドキュメントのリンクを差し替えました。
- 目次
ラムダに伴うコアライブラリーの拡張
ラムダ式の導入に伴い、コアライブラリーにもラムダ式を使ったメソッドが多数、追加されました。
ここでは、次の3つに絞ってご紹介します*1。
内部イテレーターとIterable#forEach
java.lang.Iterable
インターフェイスに、Iterable#forEach
デフォルトメソッドが追加されました。
これまでは、Iterator
やJava5の拡張for文とIterable
の組み合わせによる外部イテレーターが標準的でしたが、forEach
メソッドとラムダ式の組み合わせによる内部イテレーターも標準搭載されたことになります。
forEach
メソッドに渡すのはjava.util.function.Consumer
です。
これは一見、コレクションに含まれる機能のような気もしますが、コレクションとは直接関係のない、例えば、java.nio.file.Path
やjava.sql.SQLException
もIterable
を実装しているので、forEach
が使えるようになっています。
- サンプル:
Iterable#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
インスタンスを生成することができます。
- サンプル: ラムダ式を使った
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
それにしても、テストスイートでないAPIでtest
を名前に使うのは、どうにも抵抗がありますね...
新しい日付・時刻API
Java8の新機能の中で、ラムダの次に大きなものが、これです。
これまでの日時APIは、改良が重ねられたものの、あまり使いやすいものとは言えず、外部ライブラリーのお世話になることが多かったりと、ちょっと残念なものでした。
今回、ついに本格的な日時APIが標準搭載されることになりました。
java.time
パッケージとそのサブパッケージのAPI群が追加されています。
- 新しい日付・時刻APIのパッケージ一覧
既存の日時APIとの相互変換は、下記のメソッドを使います。相互と言っても、新API側には直接変換する機能はありません*3。Instant
としか変換できなかったりして、使い勝手はあまり良くありませんけどね。
- 旧日時APIとの相互変換メソッド(一部)
java.util.Date
-from
,toInstant
java.util.Calendar
-toInstant
java.util.TimeZone
-getTimeZone
,toZoneId
java.sql.Timestamp
-valueOf
,toLocalDateTime
新しい日付・時刻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の改良
主に次の改良が加えられています。
- 新規Atomic値クラス(下記4クラス)の導入によるスケーラビリティ向上
java.util.concurrent.ConcurrentHashMap
の(主に参照の)スケーラビリティ向上ConcurrentHashMap.KeySetView
サブクラスの新規追加- 並列検索のメソッド(
ConcurrentHashMap#search
など)の追加
java.util.concurrent.ForkJoinPool
のパフォーマンス向上- システムプロパティで、
ForkJoinPool
のデフォルト値が設定できる(詳細はクラスのドキュメントに記載されている)
- システムプロパティで、
JDBC 4.2
JDBC 4.2としていますが、ここではjava.sql
パッケージの変更をまとめて書いてしまいます。*4
- 総称SQL型(generic SQL type)の導入
java.sql.SQLType
インターフェイスとjava.sql.JDBCType
列挙型の新規追加SQLType
を使用した更新メソッドの追加(PreparedStatement#setObject
など)
REF_CURSOR
のサポート- Java EE環境のための
DataSource
プロパティの指定(詳細不明) DatabaseMetaData#getIndexInfo
の結果(ResultSet
)に、CARDINALITY
列とPAGES
を追加DatabaseMetaData#getMaxLogicalLobSize
デフォルトメソッドの追加- (Additional clean up of the spec as needed)
- 新しい日時APIとの相互変換
Timestamp.valueOf
,Timestamp.toLocalDateTime
メソッドなどTypes.TIMESTAMP_WITH_TIMEZONE
long
版update
メソッドの新設- 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
パッケージに含まれる、新たに追加されたクラスとメソッドについて、ざっとご紹介します。
java.util.StringJoiner
クラスとString.join
メソッド- プリミティブラッパークラスの
hashCode
クラスメソッド(Integer.hashCode(int)
など) - プリミティブラッパークラスの
Unsigned
関連メソッド Math
,StrictMath
クラスの整数オーバーフローチェック付き計算(XXXExact
)ThreadLocal.withInitial
-set
する前にnull
以外の初期値が返せるCharSequence#chars
とCharSequence#codePoints
デフォルトメソッド -CharSequence
をIntStream
に変換するjava.util.Optional
-null
でない空値java.util.Calendar.Builder
-Calendar
をBuilderパターンで構築
- シリーズ目次
- Java SE 8 (1) - 概要と一覧
- Java SE 8 (2) - ラムダ式、メソッド参照、ストリーム
- Java SE 8 (3) - 新しい言語機能
- Java SE 8 (4) - 新しいAPIと改良されたAPI (このエントリー)
- Java SE 8 (5) - プラットフォーム、セキュリティー、他