Java9から17で入った新機能ピックアップ
Java9から17で入った新機能で、実際コード書く上で使いそうなものをピックアップします。
Java8を使い続けていた人向けです。
レコード (Java16)
不変なデータを保持するクラスを簡単に定義できるようになりました。
class
の代わりにrecord
で定義します。
名前の後に、フィールドのリスト(コンポーネントと呼ばれます)を記載します。
public record Rectangle(int length, int width) {
}
class
と同じくnew
でインスタンスを生成します。
Rectangle rectangle = new Rectangle(1, 2);
record
には、下記メソッドが自動的に定義されます。
- フィールド参照用のアクセサ
equals
hashCode
toString
assertThat(rectangle.length())
.isEqualTo(1);
assertThat(rectangle.width())
.isEqualTo(2);
assertThat(rectangle.toString())
.isEqualTo("Rectangle[length=1, width=2]");
Rectangle other = new Rectangle(1, 2);
assertThat(rectangle.hashCode())
.isEqualTo(other.hashCode());
assertThat(rectangle.equals(other))
.isTrue();
独自メソッドも宣言できます。
public record Rectangle(int length, int width) {
public int area() {
return length * width;
}
}
参考
テキストブロック (Java15)
複数行にまたがる文字列を宣言できます。"""
で囲んで書きます。
String text = """
1行目
2行目
""";
assertThat(text)
.isEqualTo("1行目\n2行目\n");
インデントは一番浅い位置が基準になります。
String text = """
1行目
2行目(インデント)
""";
assertThat(text)
.isEqualTo("1行目\n 2行目(インデント)\n");
末尾のスペースは自動的に除去されます。
スペースを付与したい場合には\s
を利用します。末尾に付与すれば、それより前のスペースも含めて維持されます。
String text = """
1行目 \s
2行目\s
""";
assertThat(text)
.isEqualTo("1行目 \n2行目 \n");
改行を付与したくない場合には、末尾に\
を付与します。
String text = """
1行目\
2行目\
""";
assertThat(text)
.isEqualTo("1行目2行目");
参考
String#formatted、stripIndent、translateEscapes (Java15)
String
クラスのインスタンスメソッドとして、formatted
、stripIndent
、translateEscapes
が追加されました。
テキストブロックに関連しての追加のようです。
formatted
メソッドは、今までString.format
で呼び出していたものを、String
のインスタンスメソッドとして呼び出せるようにしたものです。
String formatText = "%,d円です。";
String text = formatText.formatted(1234);
assertThat(text).isEqualTo("1,234円です。");
テキストブロックでは変数埋め込みが出来ませんが、formatted
メソッドを使うことで、下記のように書くことが出来ます。
String text = """
%sさん。
金額は%,d円です。
""".formatted("太郎", 1234);
assertThat(text).isEqualTo("太郎さん。\n金額は1,234円です。\n");
stripIndent
は複数行の文字列から、一番浅いインデント分の空白を取り除きます。
String text = " 1\n 2\n 3";
assertThat(text.stripIndent()).isEqualTo("1\n 2\n 3");
translateEscapes
はエスケープシーケンスを変換します。
たとえば、\n
という2文字をU+000Aに変換してくれます。
String text = "a\\n\\tb";
assertThat(text.translateEscapes()).isEqualTo("a\n\tb");
switch式 (Java14)
switch
を式としても書けるようになりました。
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Invalid day: " + day);
};
上記はアロー構文を使った記述ですが、switch文同様のコロンラベル+yield
で書く方法もあります。
int numLetters = switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
yield 6;
case TUESDAY:
System.out.println(7);
yield 7;
case THURSDAY:
case SATURDAY:
System.out.println(8);
yield 8;
case WEDNESDAY:
System.out.println(9);
yield 9;
default:
throw new IllegalStateException("Invalid day: " + day);
};
アロー構文でも複数の式が必要な場合は、ブロック+yield
を使います。
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> {
System.out.println(6);
yield 6;
}
case TUESDAY -> {
System.out.println(7);
yield 7;
}
case THURSDAY, SATURDAY -> {
System.out.println(8);
yield 8;
}
case WEDNESDAY -> {
System.out.println(9);
yield 9;
}
default -> throw new IllegalStateException("Invalid day: " + day);
};
アロー構文はbreak
を忘れて意図せずフォールスルーすることが無いので、アロー構文を使った方がよさそうです。
参考
switchの複数ラベル (Java14)
switch
のラベルをカンマ区切りで複数書けるようになりました。
switch
式だけでなく、既存のswitch
文でも利用できます。
switch (day) {
case MONDAY, FRIDAY, SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY, SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
default:
throw new IllegalStateException("Invalid day: " + day);
}
パターンマッチングinstanceof (Java16)
instanceof
で型をチェックするのと同時に、その型の変数を定義できるようになりました。
Object value = 10;
if (value instanceof Integer num) {
assertThat(num).isEqualTo(10);
}
ローカル変数の型推論(var) (Java10)
var
という構文が追加され、ローカル変数の場合にはvar
で型が省略できるようになりました。
var num = 1;
var list = List.of("a");
右辺で型が自明なもの(newしていたりなど)では良いと思いますが、メソッドの戻りの代入などでvar
を使ってしまうと、型がわかりずらくなるので注意です。(そこまで積極的に使わなくて良いかな..)
// 型が自明(右辺でわかる)
var calculator = new Calculator();
var list = List.of("a");
// 型がわかりずらくなる
var result = compute();
privateインタフェース・メソッドのサポート (Java9)
インターフェースでprivateメソッドが定義できるようになりました。
public interface Hoge {
private void fuga() {
}
}
privateなので実装クラスからは参照できません。インターフェースのデフォルトメソッドやstaticメソッドからの参照になります。
Stream#toList (Java16)
Stream
にtoList
メソッドが追加されました。
collect(Collectos.toList())
で書いていた箇所が、もう少しすっきりとした形で書けるようになります。
List<String> list = Stream.of("a", "b").toList();
Stream
のtoList
は不変なリストを返します。そのため、そのリストに対して追加/削除/変更を行うと、UnsuptorpedOperationException
が発生します。
Collectors.toList
で返すリストは、実装としてはArrayList
になっているため、追加/削除/変更は出来ていました。そのため、Collectors.toList
を置き換えると問題が出る箇所もありえるので注意が必要です。
なお、Java10でCollectors.toUnmodifiableList
が追加されており、こちらも不変なリストを返すものになっています。
Stream#mapMulti (Java16)
flatMap
と似たようなメソッドとしてmapMulti
が追加されました。
flatMap
がStream
を返すのに対して、mapMulti
は渡されたConsumer
に対して値を設定していく形となります。
List<String> upperChars =
Stream.of("abc", "xyz")
.mapMulti((String str, Consumer<String> consumer) -> {
for (char c : str.toCharArray()) {
consumer.accept(String.valueOf(c).toUpperCase());
}
})
.toList();
assertThat(upperChars)
.containsExactly("A", "B", "C", "X", "Y", "Z");
Stream#takeWhile、Stream#dropWhile (Java9)
takeWhile
で指定した条件を満たす間のデータを対象とします。
// 2の乗数で100以下のものを求める
int[] results = IntStream.iterate(2, x -> x * 2)
.takeWhile(x -> x <= 100)
.toArray();
assertThat(results)
.containsExactly(2, 4, 8, 16, 32, 64);
dropWhile
で指定した条件を満たす間のデータを対象外とします。
// 2の乗数で100以上、1000以下のものを求める
int[] results = IntStream.iterate(2, x -> x * 2)
.dropWhile(x -> x < 100)
.takeWhile(x -> x <= 1000)
.toArray();
assertThat(results)
.containsExactly(128, 256, 512);
Stream#ofNullable (Java9)
ofNullable
は、nullの場合は空のStream
を、null以外の場合は指定された値を持つ要素数1のStream
を返却します。
long count = Stream.ofNullable("a").count();
assertThat(count).isEqualTo(1);
long count = Stream.ofNullable(null).count();
assertThat(count).isEqualTo(0);
Predicate.not (Java11)
Predicate.not
でPredicate
を反転させられるようになりました。反転のためにメソッド参照を諦めていたような場所でメソッド参照が使えるようになります。
long notEmptyCount = Stream.of("", "x", "", "x", "x")
.filter(Predicate.not(String::isEmpty))
.count();
assertThat(notEmptyCount).isEqualTo(3);
コレクションのファクトリメソッド(List.of、Set.of、Map.of) (Java9)
コレクションクラス(List``Set``Map
)のファクトリメソッドが追加されました。
全てイミュータブル(追加/削除/変更不可)なコレクションになります。
// List
List<String> list = List.of("a", "b", "c");
// Set
Set<String> set = Set.of("a", "b", "c");
// Map
Map<Integer, String> map1 = Map.of(1, "a", 2, "b", 3, "c");
// Map#ofEntries を使った方が、キーと値のセットがわかりやすい
Map<Integer, String> map2 = Map.ofEntries(Map.entry(1, "a"), Map.entry(2, "b"), Map.entry(3, "c"));
コレクションの変更不可なコピー作成(List.copyOf、Set.copyOf、Map.copyOf) (Java10)
List
、Set
、Map
にcopyOf
というstaticメソッドが追加され、変更不可な複製が生成できるようになりました。
コピー元が変更不可な場合、複製を作る必要がないので、コピー元のインスタンス自体が返却されます。
var base = List.of("a", "b", "c");
// baseは変更不可なので、base自体が返される
var copied = List.copyOf(base);
assertThat(copied == base).isTrue();
var base = Arrays.asList("a", "b", "c");
// baseは変更可能なので、変更不可とした複製が返される
var copied = List.copyOf(base);
assertThat(copied == base).isFalse();
Collection#toArray(IntFunction<T[]> generator) (Java11)
Collection#toArray
で引数に配列をnewする関数を指定できるようになりました。
以前は配列を生成して渡していた(new String[0]
とか)のが、コンストラクタをメソッド参照で指定できるようになった感じです。
List<String> list = List.of("a", "b", "c");
String[] array = list.toArray(String[]::new);
Path.of (Java11)
Paths.get
だったのが、Path.of
で書けるようになりました。他の作法と合わせたような感じです。
Path path1 = Paths.get("dir");
Path path2 = Path.of("dir");
assertThat(path2).isEqualTo(path1);
Files.writeString、readString (Java11)
Files.writeString
が追加され、1メソッドで文字の書き込みが行えるようになりました。
同様にreadString
で1メソッドで読み込みが行えるようになりました。
Files.writeString(filePath, "あいうえお", StandardCharsets.UTF_8);
String text = Files.readString(filePath, StandardCharsets.UTF_8);
assertThat(text).isEqualTo("あいうえお");
InputStream#readAllBytes、transferTo (Java9)
InputStream
に全てを読み込むメソッドのreadAllBytes
が追加されました。
byte[] results = inputStream.readAllBytes();
ファイルの場合には、Files.readAllBytes
がありましたが、それがInputStream
に対しても同じようなことが出来るようになりました。
また、内容をOutputStream
に書き込むtransferTo
も追加されました。
inputStream.transferTo(outputStream);
String#repeat (Java11)
String#repeat
で文字列を指定回数繰り返した文字列を作成できるようになりました。
String text = "a".repeat(10);
assertThat(text).isEqualTo("aaaaaaaaaa");
String#strip、stripLeading、stripTrailing (Java11)
String#trim
、trimLeft
、trimRight
と同じようなものですが、全角スペースなども対象になるところが異なります。
String text = "\n\u3000 a \u3000\n";
assertThat(text.strip()).isEqualTo("a");
assertThat(text.stripLeading()).isEqualTo("a \u3000\n");
assertThat(text.stripTrailing()).isEqualTo("\n\u3000 a");
assertThat(text.trim()).isEqualTo("\u3000 a \u3000"); // 全角スペースは消えない
String#isBlank (Java11)
String#isBlank
で空白かどうか判定できます。
assertThat(" ".isBlank()).isTrue();
assertThat("\n\t\u3000".isBlank()).isTrue();
String#lines (Java11)
String#lines
で行ごとの文字列のStream
を取得できるようになりました。
String text = "a\nb\r\nc";
long lineCount = text.lines().count();
assertThat(lineCount).isEqualTo(3);
String#indent (Java12)
String
のindent
メソッドで、指定した文字数分のインデントを付けられるようになりました。
String base = "1\n2\n\n";
String indented = base.indent(2);
assertThat(indented).isEqualTo(" 1\n 2\n \n");
String#transform (Java12)
String
にtransform
メソッドが追加されました。
String
を引数に取るメソッドを渡すとそれを実行してその結果を返します。
int num = "1".transform(Integer::parseInt);
メソッド呼び出し1回だと、あまりメリットを感じませんが、メソッド呼び出しが続くような場合には、読みやすくなります。
int num = "12#34".transform(this::clean).transform(Integer::valueOf);
Optional#ifPresentOrElse、or (Java9)
ifPresent
は値があったときのみ実行されましたが、値が無かった時のアクションも同時に実行できるifPresentOrElse
が追加されました。
AtomicInteger hasValueCounter = new AtomicInteger();
AtomicInteger emptyValueCounter = new AtomicInteger();
Optional<String> value = Optional.ofNullable(null);
value.ifPresentOrElse(
v -> hasValueCounter.incrementAndGet(),
() -> emptyValueCounter.incrementAndGet());
assertThat(hasValueCounter.get()).isEqualTo(0);
assertThat(emptyValueCounter.get()).isEqualTo(1);
orElse
と似たようなものとして、or
が追加されました。
or
はOptional
を返すSupplier
を指定します。
Optional
のままデフォルト値で補完するようなときに使えるのかもしれません。
Optional<String> value = Optional.empty();
Optional<String> result = value.or(() -> Optional.of(""));
assertThat(result.get()).isEqualTo("");
// 取り出す際に埋めるような場合には、orElseでも十分
assertThat(value.orElse("")).isEqualTo("");
Optional#stream (Java9)
stream
メソッドでStream
に変換できるようになりました。
値がある場合、値が一つ格納されたStream
、値が無い場合、空のStream
になります。
Optional<String> value = Optional.of("a");
long count = value.stream().count();
assertThat(count).isEqualTo(1);
Optional<String> value = Optional.empty();
long count = value.stream().count();
assertThat(count).isEqualTo(0);
Optional#isEmpty (Java11)
Optional
にisEmpty
が追加されました。isPresent
の逆になります。
Optional<String> value = Optional.ofNullable(null);
assertThat(value.isEmpty()).isTrue();
assertThat(value.isPresent()).isFalse();
Discussion