概要

Java14が出ました。変更点、新機能は色々ありますが、気になるのはやはりプレビューとして入ったrecordですよね?ね?というわけで早速試してみました。

注意点

recordはプレビューとして入った機能なので、使うにはおまじないが必要です。

java --enable-preview --source 14 RecordSample.java

また、ここに書いた内容は将来変更される可能性があります。(プレビューですからね)

内容

recordの基本的な使い方

recordを使うと、定義時に指定したフィールドを取得するメソッド(getter)が自動的に生成されます。

public class RecordSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);

        System.out.println(person.name());    // Mario
        System.out.println(person.age());     // 26
    }
}

// recordの定義
record Person(String name, int age) {}

recordの定義が非常に簡素で済むのがポイントですね。あと、recordを使う側のコードは従来のクラスと全く同じように書けます。
またgetterはgetName()とかではなく、フィールドと同じ名前のメソッドになります。setterは生成されないとのことです。setterも生成するかどうか選べたほうがいい気もしますが、まあ一般的にオブジェクトは不変のほうがいいですから、これはこれでいいのかもしれません。

また、recordを使うとObjectクラスで定義されているtoString()、equals()、hashCode()が自動的にオーバーライドされます。それぞれの例を見ていきます。

toStirng()の例

public class ToStringSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);

        System.out.println(person.toString());     // Person[name=Mario, age=26]
    }
}

// recordの定義
record Person(String name, int age) {}

いい感じですね。これを直接使うことはあまりないでしょうけど、デバッグ時に便利です。

equals()の例

public class EqualsSample {
    public static void main(String[] args) {
        var person1 = new Person("Mario", 26);
        var person2 = new Person("Mario", 26);

        System.out.println(person1.equals(person2));  // true

        person1 = new Person("Mario1", 26);
        person2 = new Person("Mario2", 26);

        System.out.println(person1.equals(person2));  // false

        person1 = new Person("Mario", 26);
        person2 = new Person("Mario", 27);

        System.out.println(person1.equals(person2));  // false
    }
}

// recordの定義
record Person(String name, int age) {}

同じ値を持つ別オブジェクトを比較してtrueとなる(一番上の例)ので、Objectクラスの元の挙動ではなく、ちゃんとフィールドの値を比較してくれていることがわかりますね。一応、特定のフィールドだけ値が違うのも試していますが、もちろんfalseになります。

hashCode()の例

public class HashCodeSample {
    public static void main(String[] args) {
        var person1 = new Person("Mario", 26);
        var person2 = new Person("Mario", 26);

        System.out.println(person1.hashCode());  // -1997440586
        System.out.println(person2.hashCode());  // -1997440586
    }
}

// recordの定義
record Person(String name, int age) {}

これも同じ値の別オブジェクトで同じ値になりました。ちゃんとオーバーライドされていますね。

コンストラクタの例

public class ConstructorSample {
    public static void main(String[] args) {
        new Person("Mario", -1);
        // Exception in thread "main" java.lang.IllegalArgumentException
        // at Person.<init>(ConstructorSample.java:11)
        // at ConstructorSample.main(ConstructorSample.java:3)
    }
}

// recordの定義
record Person(String name, int age) {
    public Person {
        if(age < 0) {
            throw new IllegalArgumentException();
        }
    }
}

コンストラクタも定義できるようです。カッコはつかないのですね。まあ引数を書くとなると上と全く同じことを書かないといけなくなるし、かといって引数なしでカッコがあると引数なしコンストラクタが存在するかのように見えてしまうし、ということでカッコなし表記になったのでしょう。良いと思います。
あと、アクセス修飾子はpublicじゃないとコンパイルエラーになりました。

別のフィールドやメソッドを定義する例

public class OtherMemberSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);
        System.out.println(person.getGender());  // male
    }
}

// recordの定義
record Person(String name, int age) {
    private static String gender = "male";

    public String getGender() {
        return gender;
    }
}

そんなことをしたいことがあるかどうかはわかりませんが、他のフィールドやメソッドを定義できるかやってみました。フィールドはstaticでないといけないようですが、一応できるようです。

自動的にオーバーライドされるメソッドを明示的に定義しちゃった例

public class ExplicitOverrideSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);
        System.out.println(person.toString());      // overrided

        System.out.println(person.equals(person));  // false

        System.out.println(person.hashCode());      // -1
    }
}

// recordの定義
record Person(String name, int age) {
    @Override
    public String toString() {
        return "overrided";
    }

    @Override
    public boolean equals(Object o) {
        return false;
    }

    @Override
    public int hashCode() {
        return -1;
    }
}

これも誰がそんなことするんだよという例ですが、やってみました。どうやら明示的に書いた方が有効になるみたいですね。

感想

早く正式な機能になってください。

参考リンク