Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java8から17へ

onozaty
September 16, 2022

 Java8から17へ

Java9から17で入った新機能で、実際コード書く上で使いそうなものをピックアップします。
Java8を使い続けていた人向けです。

onozaty

September 16, 2022
Tweet

More Decks by onozaty

Other Decks in Programming

Transcript

  1. レコード (Java16) • イミュータブル(不変な)データを保持するクラスを簡単に定義でき るようになった • フィールドのリストを記載するだけで、下記が自動的に定義される • コンストラクタ •

    各フィールドのアクセサ • equals • hashCode • toString • レコード・クラス - Java言語更新 - Oracle Help Center https://github.com/onozaty/redmine-view-customize 参考
  2. レコード (Java16) • classの代わりにrecordで定義し、名前の後にフィールドのリストを 記載 • 下記の標準クラスと同等になる public final class

    Rectangle { private final int length; private final int width; public Rectangle(int length, int width) { this.length = length; this.width = width; } public int length() { return this.length; } public int width() { return this.width; } public boolean equals(Object obj) {...} // 省略(各フィールドを使った適切な処理) public int hashCode() {...} // 省略(各フィールドを使った適切な処理) public String toString() {...} // 省略(各フィールドを使った適切な処理) } public record Rectangle(int length, int width) { }
  3. レコード (Java16) // フィールドを指定して生成 Rectangle rectangle = new Rectangle(1, 2);

    // getプリフィックスは付かない 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(); assertThat(rectangle.area()).isEqualTo(2);
  4. レコード (Java16) • 独自メソッドも宣言できる • コンストラクタにチェック処理を追加したり、独自コンストラクタ を追加できる public record Rectangle(int

    length, int width) { public Rectangle { if (length < 0 || width < 0) { throw new IllegalArgumentException(); } } public Rectangle(int length) { this(length, 0); } } public record Rectangle(int length, int width) { public int area() { return length * width; } }
  5. テキストブロック (Java15) • “”” で囲んで書く • “”” の後に改行で次の行からが文字列の開始 • テキストブロック内の改行は¥nになる

    • インデントは一番浅い位置が基準になる String text = """ 1行目 2行目 """; assertThat(text) .isEqualTo("1行目¥n2行目¥n"); String text = """ 1行目 2行目(インデント) """; assertThat(text) .isEqualTo("1行目¥n 2行目(インデント)¥n");
  6. テキストブロック (Java15) • 末尾のスペースは自動的に除去される • スペースを付与したい場合には ¥s を利用 末尾に付与すれば、それより前のスペースも含めて維持される •

    改行を付与したくない場合には、末尾に ¥ を付与 String text = """ 1行目 ¥s 2行目¥s """; assertThat(text) .isEqualTo("1行目 ¥n2行目 ¥n"); String text = """ 1行目¥ 2行目¥ """; assertThat(text) .isEqualTo("1行目2行目");
  7. String#formatted (Java15) ※テキストブロック関連で追加 • 今までString.formatで呼び出していたものを、Stringのインスタン スメソッドとして呼び出せるようにしたものとしてformattedメ ソッドが追加 String formatText =

    "%,d円です。"; String text = formatText.formatted(1234); assertThat(text).isEqualTo("1,234円です。"); // String.formatで同等のコード String text = String.format(formatText, 1234);
  8. String#formatted (Java15) ※テキストブロック関連で追加 • テキストブロックに変数埋め込みは入らなかったが、formattedメ ソッドを使うことで、(多少)わかりやすく書くことができる • 変数埋め込み的なものは、別の機能改善として検討 • JEP

    draft: String Templates (Preview) https://openjdk.org/jeps/8273943 String text = """ %sさん。 金額は%,d円です。 """.formatted("太郎", 1234); assertThat(text).isEqualTo("太郎さん。¥n金額は1,234円です。¥n");
  9. String#stripIndent (Java15) ※テキストブロック関連で追加 • stripIndentは、複数行の文字列から一番浅いインデント分の空白を 取り除く String text = "

    1¥n 2¥n 3"; assertThat(text.stripIndent()).isEqualTo("1¥n 2¥n 3"); // 先頭行にあわせてインデントが1つ取り除かれた
  10. String#translateEscapes (Java15) • translateEscapesは、エスケープシーケンスを変換 • たとえば、¥nという2文字を改行コード(U+000A)に変換してくる • 入力値としてスケープシーケンスを有効にしたい場合に使えそうだ が、Unicodeエスケープ(¥u0020とか)には対応していないので注意 •

    https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/lang/String.ht ml#translateEscapes() String text = "a¥¥n¥¥tb"; System.out.println(text); // a¥n¥tb assertThat(text.translateEscapes()).isEqualTo("a¥n¥tb"); System.out.println(text.translateEscapes()); // a // b
  11. String#strip、stripLeading、stripTrailing (Java11) • String#trim、trimLeft、trimRight と同じようなものだが、全角ス ペースなども対象になるところが異なる • trimだとASCIIで0x20以下が対象 (スペース、タブ、改行、その他制御コード) •

    stripだと上記にプラスしてUnicodeのカテゴリで空白文字となるようなもの なども対象 (全角スペースや段落区切り文字(PARAGRAPH SEPARATOR)とか) 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"); // trim系だと全角スペースは消えない assertThat(text.trim()).isEqualTo("¥u3000 a ¥u3000");
  12. String#indent (Java12) • String の indent メソッドで、指定した文字数分のインデントを付け られるようになった String base

    = "1¥n2¥n¥n"; String indented = base.indent(2); assertThat(indented).isEqualTo(" 1¥n 2¥n ¥n");
  13. switch式 (Java14) • switchを式としても書けるようになった • 式なのでswitchが値を返すように • 式 - Java言語更新

    - Oracle Help Center https://docs.oracle.com/javase/jp/13/language/switch-expressions.html 参考
  14. switch式 (Java14) • switch文で条件に応じて値を設定するようなものが、switch式に置 き換えられる // switch文 int numLetters; switch

    (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException(); } // switch式 int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException(); };
  15. switch式 (Java14) • 新たに追加されたアローcaseラベル(アロー構文)か、今まで通りの コロンcaseラベル+yieldで値を返す // アローcaseラベル int numLetters =

    switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException(); }; // コロンcaseラベル+yield int numLetters = switch (day) { case MONDAY: case FRIDAY: case SUNDAY: yield 6; case TUESDAY: yield 7; case THURSDAY: case SATURDAY: yield 8; case WEDNESDAY: yield 9; default: throw new IllegalStateException(); };
  16. switch式 (Java14) • アローcaseラベルでも、複数の式が必要な場合は、ブロック+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(); };
  17. switch式 (Java14) • コロンcaseラベルでも、ラベルをカンマ区切りで複数書けるように なった • 既存の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(); }
  18. パターンマッチングinstanceof (Java16) • instanceof で型チェックするのと同時に、その型の変数を定義でき るようになった Object value = 10;

    if (value instanceof Integer num) { assertThat(num).isEqualTo(10); } // 以前だと再度キャストする必要があった if (value instanceof Integer) { Integer num = (Integer) value; assertThat(num).isEqualTo(10); }
  19. コレクションのファクトリメソッド (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"));
  20. コレクションの変更不可なコピー作成 (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();
  21. 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); // 以前は配列自体を渡していた String[] array = list.toArray(new String[list.size()]);
  22. Files.writeString、readString (Java11) • Files.writeString が追加され、1メソッドで文字の書き込みが行える ようになった • 今までも Files.write でバイト配列は書き込めたが、それをStringのまま指定

    できるようになった感じ • 同様に readString で1メソッドで読み込みが行えるようになった • こちらも今までは Files.readAllBytes でバイト配列は読み込めた Files.writeString(filePath, "あいうえお", StandardCharsets.UTF_8); String text = Files.readString(filePath, StandardCharsets.UTF_8); assertThat(text).isEqualTo("あいうえお");
  23. InputStream#readAllBytes、transferTo (Java9) • InputStream に全ての内容を読み込む readAllBytes が追加された • ファイルの場合には、Files.readAllBytes があるが、それと同じことが

    InputStream に対しても出来るようになった • 内容を OutputStream に書き込む transferTo も追加された byte[] results = inputStream.readAllBytes(); inputStream.transferTo(outputStream);
  24. Stream#toList (Java16) • StreamのtoListは不変なリストを返す • 追加/削除/変更を行うと、UnsuptorpedOperationExceptionが発生 • Collectors.toListで返すリストは、API仕様としては可変性は保証さ れていないが、現在の実装としてはArrayListになっているため、追 加/削除/変更は出来てしまう状態

    • collect(Collectors.toList())をtoList()に置き換えると、追加/削除/変更ができ なくなり、問題が出る可能性もあるので注意が必要 • Java10でCollectors.toUnmodifiableListも追加されている List<String> list = Stream.of("a", "b").toList(); list.set(0, "x"); // UnsupportedOperationException が発生 Stream.of("a", "b").toList(); // 不変 Stream.of("a", "b").collect(Collectors.toList()); // API仕様としては可変性は保証されないとなっているが、現在の実装としては可変 Stream.of("a", "b").collect(Collectors.toUnmodifiableList()); // 不変
  25. 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"); // flatMapで書いた場合 List<String> upperChars = Stream.of("abc", "xyz") .flatMap(str -> str.chars() .mapToObj(c -> String.valueOf((char) c).toUpperCase())) .toList();
  26. Stream#takeWhile、dropWhile (Java9) • takeWhileで指定した条件を満たす間のデータを対象に • dropWhileで指定した条件を満たす間のデータを対象外に // 2の乗数で100以下のものを求める int[] results

    = IntStream.iterate(2, x -> x * 2) .takeWhile(x -> x <= 100) .toArray(); assertThat(results) .containsExactly(2, 4, 8, 16, 32, 64); // 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);
  27. Stream#takeWhile、dropWhile (Java9) • filterとの違いとして、takeWhileは条件に一致しなくなったら Stream処理自体を止める • 要素が無制限なStreamや、大きなStreamの先頭の一部しか使わないような 場合に有用 • filterだと全ての要素に対して処理を行うことになる

    // takeWhileだと、無制限なStreamでも条件に一致しなくなった時点で止めてくれる IntStream.iterate(2, x -> x * 2) .takeWhile(x -> x <= 100) .toArray(); // filterだと、条件に一致しなくてもStream処理は継続される=終わらない IntStream.iterate(2, x -> x * 2) .filter(x -> x <= 100) .toArray();
  28. Predicate.not (Java11) • Predicate.not で Predicate を反転させられるようになった • 反転のためにメソッド参照を諦めていたような場所でメソッド参照 が使えるようになる

    long notEmptyCount = Stream.of("", "x", "", "x", "x") .filter(Predicate.not(String::isEmpty)) .count(); assertThat(notEmptyCount).isEqualTo(3); // 以前は反転させたいときはラムダで long notEmptyCount = Stream.of("", "x", "", "x", "x") .filter(x -> !x.isEmpty()) .count();
  29. Optional#ifPresentOrElse (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);
  30. Optional#or (Java9) • 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("");
  31. 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);
  32. Optional#isEmpty (Java11) • Optional に isEmpty が追加 • isPresent の逆

    Optional<String> value = Optional.ofNullable(null); assertThat(value.isEmpty()).isTrue(); assertThat(value.isPresent()).isFalse();
  33. おわりに • リリースサイクルが変わり、新しい機能が半年に1回入るように なったことで、Javaもどんどん書きやすくなっている • 1.4から8くらいまでは、リリース間隔も数年と長くて、停滞している感じ がした • 機能とバージョンを紐づけてリリースタイミングを考えるのではなく、リ リース周期を決めて、そのタイミングで出せる機能を出すといった形に

    なったことで、早めにPreviewとしてリリースし、フィードバックを得れる ようになったのは、Javaの進化のスピードを速めたのでは • 今後もよさそうな機能が予定されているので、また次のLTSがリ リースされたタイミングで試してみて、発表したい