Closed17

Java11 to Java21勉強用スクラップ

K1mu21K1mu21

String Templates

文字列に式の値を埋め込める機能

String name = "Joan";
String info = "My name is"+ name;
assert info.equals("My name is Joan"); 

今までは+演算子が必要だったが、

String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan"); 

テンプレートプロセッサ."テンプレート"の形式で書けるようになった
テンプレート内の式は{~}で記載する
STR、FMT、RAWが用意されている

String title = "My Web Page";
String text  = "Hello, world";
String html = STR."""
        <html>
          <head>
            <title>\{title}</title>
          </head>
          <body>
            <p>\{text}</p>
          </body>
        </html>
        """;

テンプレートには"""で囲む複数行文字列も使える

他にqueryBuilderによるインジェクションを防ぐのに使えるっぽい

String query = "SELECT * FROM Person p WHERE p.last_name = '" + name + "'";

name が Smith' OR p.last_name <> 'Smith の場合
結果として次のように解釈されてしまう

SELECT * FROM Person p WHERE p.last_name = 'Smith' OR p.last_name <> 'Smith'

条件が常に真になってしまい、すべての行が取得されてしまうのでインジェクションのアタックの原因になってしまう

var DB = new QueryBuilder(conn);
PreparedStatement ps = DB."SELECT * FROM Person p WHERE p.last_name = \{name}";
ResultSet rs = ps.executeQuery();

この書き方になるとインジェクションの原因を解決できる

https://openjdk.org/jeps/430

K1mu21K1mu21

record

recordは,enumと同様に特別なクラスになる

record Point(int x, int y) {}

これは,次のようなクラスと機能上はほぼ同等

class Point {
    private final int x;
    private final int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }

    public boolean equals(Object o) {
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return other.x == x && other.y = y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }

    public String toString() {
        return String.format("Point[x=%d, y=%d]", x, y);
    }
}

https://qiita.com/ReiTsukikazu/items/6dc3ec9ea9646c472db0

K1mu21K1mu21

record Pattern

レコードパターンを使うと、レコードのフィールドを一括で分解しながら同時に変数へバインドできる

Point p = new Point(10, 20);

if (p instanceof Point(int px, int py)) {
    // レコード p の x, y をそれぞれ px, py に自動的に展開
    System.out.println("x = " + px);
    System.out.println("y = " + py);
}

条件式と合わせることもできる

p instanceof Point(var xx, var yy) ? "x:%d y:%d".formatted(xx, yy) : "no match"

以下のように書くこともできるが、

record Box(Point topLeft, Point bottomRight){}
var b = new Box(new Point(3, 4), new Point(7, 9))

b instanceof Box b ?
"(%d, %d)-(%d, %d)".formatted(b.topLeft().x(), b.topLeft().y(), 
b.bottomRight().x(), b.bottomRight().y()) :
"no match"

直感的に以下のように書けるようになるので見やすくなる

b instanceof Box(Point(var left, var top), Point(var right, var bottom)) ?
"(%d, %d)-(%d, %d)".formatted(left, top, right, bottom) : "no match"
K1mu21K1mu21

Switch

パターンマッチがswitchで使えるようになった

以前まではif文でちまちましてた

static String formatter(Object obj) {
    String formatted = "unknown";
    if (obj instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (obj instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (obj instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (obj instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

Switchが使えるようになったことで以下のように書けるようになった

static String formatterPatternSwitch(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();
    };
}

単純になってわかりやすい!

case nullにするとnullの場合が処理されて、なければnullpointerが発生する

case null, "" -> "empty";
default -> s;

みたいに2つの値をトリガーにすることもできる

record Pattern + Sealed Classと組み合わしたりするとさらに単純にできる


sealed interface Expression
  permits IntExpr, NegExpr, AddExpr, MulExpr {}
record IntExpr(int i) implements Expression {}
record NegExpr(Expr n) implements Expression {}
record AddExpr(Expr left, Expr right) implements Expression {}
record MulExpr(Expr left, Expr right) implements Expression {}

Expression n = getExp();
return switch(n) {
    case IntExpr(int i) -> i;
    case NegExpr(Expr n) -> -eval(n);
    case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
    case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
};

Expressionが取りうるのが4つのレコードで全てということがわかるのでdefault句が不要になる

https://openjdk.org/jeps/441

K1mu21K1mu21

Sealed Class

クラスに sealed 修飾子をつけるとSealedクラスになる
さらに,extends節やimplements節の後にpermits節を付けて,自身を継承可能なクラスを明示することが可能

public abstract sealed class Shape
    permits Circle, Rectangle, Square { ... }

Shape を継承できるのは Circle Rectangle Square の3つのみになる

public abstract sealed class Shape
    permits Circle, Rectangle, Square, WeirdShape { ... }

public final class Circle extends Shape { ... }

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle { ... }
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }

public final class Square extends Shape { ... }

public non-sealed class WeirdShape extends Shape { ... }

許可されたサブクラスは,元の sealedクラスを直接継承する必要がある
また,下記のようなスーパークラスの sealing を伝搬するかどうかを示す修飾子を必ずつける必要がある

それ以上の継承を許さない場合は従来通りの final
さらにサブクラスを制限する場合には,スーパークラスと同様の sealed
さらなるサブクラスに制限を付けない場合には,non-sealed

K1mu21K1mu21

sealed interface

クラスと同様にインターフェースにも sealed 修飾子を付与できる
実装クラス及びサブインターフェースも,同様に permits 節で指定可能

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }

public final class ConstantExpr implements Expr { ... }
public final class PlusExpr     implements Expr { ... }
public final class TimesExpr    implements Expr { ... }
public final class NegExpr      implements Expr { ... }

record クラスは暗黙に final なので,sealedインターフェースを実装できる

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }

public record ConstantExpr(int i)       implements Expr { ... }
public record PlusExpr(Expr a, Expr b)  implements Expr { ... }
public record TimesExpr(Expr a, Expr b) implements Expr { ... }
public record NegExpr(Expr e)           implements Expr { ... }

https://qiita.com/ReiTsukikazu/items/710c60745f757cc103df

K1mu21K1mu21

Unnamed Patterns and Variables

_を使うことで、値を使わないことを明示できる
goみたいにできて結構嬉しい

case AddExpr(Expr left, Expr right) -> print(left);

こういったrightの変数が使われていない場合

case AddExpr(Expr left, Expr _) -> print(left);

_を使って利用しない値を明示できるようになった
型にも拘らない場合はvarにもできるし、記載することも必要がない

case AddExpr(Expr left,var _) -> print(left);
case AddExpr(Expr left,_) -> print(left);

使えるのは次の6か所。

ローカル変数
try-with-resource
for文
拡張for文
catch句
ラムダ式の引数

try {
  Thread.sleep(500);
} catch (InterruptedException _) {}

例外を捕まえたいけど値は使わないと時に使える

https://openjdk.org/jeps/443

K1mu21K1mu21

Unnamed Classes and Instance Main Methods

クラスの定義が不要になる
mainメソッドはインスタンスメソッドでよくなる
mainメソッドの引数を省略できる
mainメソッドがpublicじゃなくてもよくなる

public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello Java!");
  }
}

これが

void main() {
  System.out.println("Hello Java!");
}

これで良くなる

K1mu21K1mu21

数値が指定範囲に収まるようにする

数値が指定範囲に収まるようにするclampメソッドがMathクラスに用意されている
型違いで次の4つが用意されている

int Math.clamp(long value, int min, int max)
long Math.clamp(long value, long min, long max)
double Math.clamp(double value, double min, double max)
float Math.clamp(float value, float min, float max)

数値を10~100に制限した場合はこうなる

Math.clamp(1234, 10, 100)
==> 100

Math.clamp(1, 10, 100)
==> 10
K1mu21K1mu21

区切り文字を含む文字列分割

文字列分割ではsplitメソッドがありますが、ここには分割文字列は含まれない
正規表現を指定した場合には、どういう文字列で分割したか知りたいこともあるので、分割文字列も含まれるsplitWithDelimiterメソッドがStringとPatternに導入された

"aa:bbb::cc:::dd".split(":+")
String[4] { "aa", "bbb", "cc", "dd" }

"aa:bbb::cc:::dd".splitWithDelimiters(":+", 0)
String[7] { "aa", ":", "bbb", "::", "cc", ":::", "dd" }
K1mu21K1mu21

Emoji判定

絵文字を判定できるメソッドが入った

Character.isEmoji('A')
Character.isEmoji('⚽')

サロゲートペアになっている場合はfalseになってしまう

Integer.toHexString("🏀".codePointAt(0))

char単体で判定するのではなく、文字列からcodePointAtメソッドでコードを取得して判定する必要あり

Character.isEmoji("🤗".codePointAt(0))
K1mu21K1mu21

StringBufferのrepeatメソッド

var s = new StringBuilder("test")
s.repeat("er", 3)
==> testererer
K1mu21K1mu21

Sequenced Collections

Listなど順序付きのコレクションに共通のインタフェースが導入された

interface SequencedCollection<E> extends Collection<E> {
    // new method
    SequencedCollection<E> reversed();
    // methods promoted from Deque
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

getLast使えば最後の値取得できるようになるの楽

K1mu21K1mu21

テキストブロック

"""でテキストをまとめることができるようになった

String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

nativeQueryが書きやすくなったの最高

https://openjdk.org/jeps/378

K1mu21K1mu21

Pattern Matching for instanceof

インスタンスの型を判断して処理を行う instanceof-and-cast イディオムが変わった

if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s);
}

instanceof パターンマッチングでは、以下のように書くことができるようになる

if (obj instanceof String s) {
    System.out.println(s);
}

パターン変数 s がキャスト不要で利用可能になる

https://openjdk.org/jeps/394

このスクラップは2025/01/31にクローズされました