Amberとは
Java言語を拡張するプロジェクトです
http://openjdk.java.net/projects/amber/
Amberのブランチ
- records
データ保持用のクラスです - sealed-types
シールドタイプ - newesapes
line blockのエスケープ対応 - patterns
パターンマッチの全体的な開発 - patterns-deconstruction
パターンマッチでのデコンストラクション - patterns-stage-1
instanceofのみのパターンマッチ - pattern-runtime
パターンマッチのランタイム? - local-methods
ローカルメソッド - lambda-leftovers
ラムダで_使えるようにする - concise-method-declarations
メソッド定義の簡略化 - enhanced-enums
拡張enum - stats-before-this-super
なぞ - amber-demo-II
switch式とパターンマッチが動いてた
これが、JDK14、15、16...と半年ごとに五月雨式にリリースされていくことになると思います。結構恐怖ですね。
Records[second preview in JDK15, preview in JDK14]
データ保持用のクラスです。
JEPはこちらで14にPreviewとして入って15でsecond previewになっています。
JEP 384: Records (Second Preview)
JEP 359: Records (Preview)
Java 15でpreview 2を経てJava 16で正式化という流れ。
JEPは今のところ見当たりません。 JEPのドラフトはこちら
JEP draft: Records and Sealed Types
http://cr.openjdk.java.net/~briangoetz/amber/datum.html
http://hg.openjdk.java.net/amber/amber/file/15a61b5af8f5/test/langtools/tools/javac/datum
recordとして定義します。
record Foo(int x, int y) {}
他のクラスは継承できません。
record レコード名(状態) { 定義 }
これは次のようなクラスになります。
class Foo extends Record{
// 状態がprivate finalとして定義
private final int x;
private final int y;
// 追加のインスタンスフィールドは定義できない
// 状態をフィールドに設定するコンストラクタが定義される
public Foo(int x, int y) {
this.x = x;
this.y = y;
}
// 状態と同じ名前のメソッドが定義される
public int x() {
return x;
}
public int y() {
return y;
}
// 状態を反映するhashCodeが定義される
public int hashCode() { ... }
// 状態を比較するequals が定義される
public boolean equals() { ... }
// 状態を表示するtoStringが定義される
public String toString() { ... }
}
状態の検査や正規化にコンストラクタが定義できます。
record Range(int lo, int hi) {
public Range {
if (lo > hi) throw IllegalArgumentException();
// 設定されなかったフィールドはあとで設定される
}
}
Preview 2での改善点はこちらで提案されています。
https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-January/001913.html
-
java.util.Record
はクラスですが、Valhallaのinline classはinterfaceしか継承できないので、これをinterfaceにしたほうがいいかどうか。 - 必須メンバーへの可視性(private recordのメンバがpublicなのはおかしい?)
- 現状ではstaticではないinner classではrecordを入れ子にすることができないけど対応したい
- abstract record
- パターンマッチでのdeconstructionへの対応
Sealed Classes[preview in JDK 15]
継承を限定するsealed型も進められています。
JEP 360: Sealed Classes(Preview)
Sealed TypesからSealed Classesになりました。
recordとセットでsealed型の仕様も決められています。
JEP draft: Records and Sealed Types
JDK15でpreviewに入ると思われます。
継承を限定することができます。パターンマッチング用
sealed interface Node
permits A, B, C {}
Nodeを実装するのはpermits
で指定したA, B, Cクラスということになります。同じコンパイル単位に入っていればpermits
は省略できます。
sealed型を継承するinterfaceや抽象クラスはsealed型になります。sealed型を実装・継承する非抽象クラスはfinalになります。
sealed interface Expr {};
record AddExpr(Expr a, Expr b) implements Expr {}
record SubExpr(Expr a, Expr b) implements Expr {}
record MulExpr(Expr a, Expr b) implements Expr {}
record DivExpr(Expr a, Expr b) implements Expr {}
non-sealed
というハイフン区切りのキーワードが入るのも面白いです。
パターンマッチング[second preview in JDK 15, preview in JDK 14]
パターンマッチングです。
まずはinstanceofを使ったパターンマッチが14にプレビューとして入り15でsecond previewになっています。
JEP 375: Pattern Matching for instanceof (Second Preview)
JEP 305: Pattern Matching for instanceof (Preview)
値 instanceof パターン
で、値をマッチさせることができます。
パターンは、定数か変数定義です。変数定義の場合には、型が一致していた場合にtrueになりその変数に値が割り当てられます。
if (x instanceof Integer i) {
// can use i here
}
16以降になりますがswitchでもパターンマッチが使えます。
JEP draft: Pattern matching for switch (Preview)
String formatted;
switch (obj) {
case Integer i: formatted = String.format("int %d", i); break;
case Byte b: formatted = String.format("byte %d", b); break;
case Long l: formatted = String.format("long %d", l); break;
case Double d: formatted = String.format("double %f", d); break;
case String s: formatted = String.format("String %s", s); break
default: formatted = obj.toString();
}
まだJEPになってませんが、データクラスと組み合わせて構造の分解を行うことができるようになるということも目標です。
record Point(int x, int y) {}
int getLen(Point p) {
return switch (p) {
case Point(0, int y) -> y;
case Point(int x, 0) -> x;
case Point(int x, int y) -> (int)sqrt(x * x + y * y);
}
}
そうすると、switchでdoubleやbooleanも使えるようになるかもしれません。
switch (obj) {
case 12 : msg = "12だ"; break;
case true : msg = "とぅるー"; break;
case 3.14: msg = "円周率では"; break;
}
拡張switch[standard in JDK 14, preview in JDK 12,13]
いままでステートメントであったswitch
を式として使えるようになります。Java 12でプレビューとして導入され、Java 13で仕様変更、そしてJava 14で正式機能として導入されるようです。
https://openjdk.java.net/jeps/361
プレビューのJEP
https://openjdk.java.net/jeps/325
https://openjdk.java.net/jeps/354
switch
はステートメントでしたが、多くのswitchで同一の変数に値を割り当てたりすべてのcaseでreturnしたりといった使いかたがされていたため、効率的に書けるように式としても使えるようになります。
つまり、こう。
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
yield
で値を返すということもできます。
int result = switch (s) {
case "Foo":
yield 1;
case "Bar":
yield 2;
default:
System.out.println("Neither Foo nor Bar, hmmm...");
yield 3;
}
基本的な形はyield
で値を返すものです。
case LABEL: yield expression;
そのシンタックスシュガーとしてラムダっぽく書けるということのようです。
case LABEL -> expression;
また、case
に複数の値を指定できるようにもなります。
switch (day) {
case MONDAY, FRIDAY, SUNDAY:
numLetters = 6;
break;
...
};
このようなcase
の拡張は、既存のswitch
ステートメントでも有効です。
case
にnull
も使えるようになるはずだったけど、JEP325からは外れてるようですね。
String formatted = switch (s) {
case null -> "(null)";
case "" -> "(empty)";
default -> s;
}
case null
がない場合には次のようなcase
が自動的に挿入されます。
case null: throw new NullPointerException();
文字列リテラルの拡張[standard in JDK 15, preview in JDK 13,14]
https://openjdk.java.net/jeps/378
https://openjdk.java.net/jeps/368
改行などを含んだ文字列を定義できます。"""
で囲みます。
JDK13にPreviewとして入っています。
https://openjdk.java.net/jeps/355
JDK14では改行のエスケープなど少し仕様変更が入りました。JDK15でstandardになる予定。
最初はRaw String LiteralsとしてJDK12に入る予定でしたが、却下されました。
http://openjdk.java.net/jeps/326
// same as "You can write\ntwo line string.\n"
var str = """
You can write
two line string.
""";
開始の"""
のあとには文字列を続けれません。また、インデントは"""
や内部の文字列で一番浅いところが基準になります。
var str = """
..You can write
..two line string.
""";
var str = """
.. You can write
.. two line string.
""";
改行をエスケープすることもできます。
var str = """
You can write \
two line string, \
but this is single.
""";
これは"You can write two line string, but this is single."
になります。
行末のスペースは削除されます。
そこで、行末にスペースが必要なときは\s
を入れてスペースが必要なことを示します。
var str = """
test\s
test \s
""";
これは"test_\ntest__\n"
になります。(Qiitaでは複数スペースをいれてもスペースひとつになってしまう)
関係ないけどQiitaが```のようにバッククオートをバッククオートで囲めないので困る人が続出する気がする。
2018/5/8時点では次のようにレンダリングされている。
ラムダの微修正
ラムダの微修正
http://openjdk.java.net/jeps/302
使わない変数に_
を使えます。
BiFunction<Integer, String, String> biss = (i, _) -> String.valueOf(i);
あと、次のような場合、false
を返すのでPredicate
になることは明確ですが、現状ではエラーになってます。これをちゃんと型推論できるようにしようというもの。
m(Predicate<String> ps) { ... }
m(Function<String, String> fss) { ... }
m(s -> false) //ambiguous
拡張enum[取り下げ]
enumでgenericsを指定できるようにしようというものです。
http://openjdk.java.net/jeps/301
enum Argument<X> { // declares generic enum
STRING<String>(String.class),
INTEGER<Integer>(Integer.class), ... ;
Class<X> clazz;
Argument(Class<X> clazz) { this.clazz = clazz; }
Class<X> getClazz() { return clazz; }
}
Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant
withdrawn、取り下げになりました。
メソッド定義の簡略化
メソッド定義で->
を使えるようにする
int twice(int x) -> x * x;
メソッド参照は=
で
int min(int x, int y) = Integer::min;
オーバーロード・オーバーライドなどでの移譲がすっきり書けます。
ローカルメソッド
2019/11/16時点ではJEPなどはないですが、amberリポジトリのブランチを見ると、メソッドの中でメソッドが定義できるようになる拡張をやってる気配があります。
次のようにメソッド内メソッドの定義ができるようにするものです。
int norm(int x, int y) {
int square(int n) {
return n * n;
}
return square(x) + square(y);
}
メソッド定義の簡略化と組み合わせると次のように書けるようになるはず
int norm(int x, int y) {
int square(int n) -> n * n;
return square(x) + square(y);
}
ローカル変数型推論[standard in JDK10]
ローカル変数の型推論を導入するものです。
http://openjdk.java.net/jeps/286
var x = 12;
JDK 10で導入されています。
ラムダへのvar対応[standard in JDK11]
ラムダの変数定義にもvar
を明示的に書けるようにしようというものです。
http://openjdk.java.net/jeps/323
いままでこう書けていました。
(x, y) -> x + y
var
で明示的に型推論であることを指定しようというものです。
(var x, var y) -> x + y
混在はできません
(var x, y) -> x + y
JDK 11に入りました。
構文以外
condyという名前で、indyのコンストラクタ版が開発されているぽい。
-> Constant Dynamicだった。
http://openjdk.java.net/jeps/309
JDK 11に入りました。