Edited at

Amberで検討されているJava構文の変更


Amberとは

Java言語を拡張するプロジェクトです

http://openjdk.java.net/projects/amber/


Amberのブランチ

http://hg.openjdk.java.net/amber/amber/branches


  • datum
    データクラス

  • patterns
    パターンマッチ

  • switch
    拡張switch

  • raw-string-leterals
    文字列リテラルの拡張

  • lambda-leftovers
    ラムダの微修正

  • enhanced-enums
    拡張enum

  • lvti
    ローカル変数型推論

これが、JDK10、11、12...と半年ごとに五月雨式にリリースされていくことになると思います。結構恐怖ですね。


Record

データ保持用のクラスです。

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 {

// 状態がpriate 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();
// 設定されなかったフィールドはあとで設定される
}
}


sealed型

recordとセットでsealed型の仕様も決められています。

JEP draft: Records and Sealed Types

継承を限定することができます。パターンマッチング用

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 {}


パターンマッチング

パターンマッチングです。

http://openjdk.java.net/jeps/305

値 instanceof パターンで、値をマッチさせることができます。

パターンは、定数か変数定義です。変数定義の場合には、型が一致していた場合にtrueになりその変数に値が割り当てられます。

if (x instanceof Integer i) {

// can use i here
}

switchでもパターンマッチが使えます。

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();
}

つまり、switchでdoubleやbooleanも使えるようになるということです。

switch (obj) {

case 12 : msg = "12だ"; break;
case true : msg = "とぅるー"; break;
case 3.14: msg = "円周率では"; break;
}

JEP 305には含まれませんが、データクラスと組み合わせて構造の分解を行うことができるようになるということも目標です。

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

http://openjdk.java.net/jeps/8222184

プレビューのJEP

http://openjdk.java.net/jeps/325

switchはステートメントでしたが、多くのswitchで同一の変数に値を割り当てたりすべてのcaseでreturnしたりといった使いかたがされていたため、効率的に書けるように式としても使えるようになります。

つまり、こう。

int numLetters = switch (day) {

case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};

break-withで値を返すということもできます。

int result = switch (s) {

case "Foo":
break-with 1;
case "Bar":
break-with 2;
default:
System.out.println("Neither Foo nor Bar, hmmm...");
break-with 3;
}

基本的な形はbreak-withで値を返すものです。

case LABEL: break-with expression;

そのシンタックスシュガーとしてラムダっぽく書けるということのようです。

case LABEL -> expression;

また、caseに複数の値を指定できるようにもなります。

switch (day) {

case MONDAY, FRIDAY, SUNDAY:
numLetters = 6;
break;
...
};

このようなcaseの拡張は、既存のswitchステートメントでも有効です。

casenullも使えるようになるはずだったけど、JEP325からは外れてるようですね。

String formatted = switch (s) {

case null -> "(null)";
case "" -> "(empty)";
default -> s;
}

case nullがない場合には次のようなcaseが自動的に挿入されます。

case null: throw new NullPointerException();

PreviewとしてJDK12に入りました。JDK13に正式版が入りそう


文字列リテラルの拡張

http://openjdk.java.net/jeps/326

改行などを含んだ文字列を定義できます。バッククオートで囲みます。

var str1 = `string literal is "test".`

var str2 = ``raw string literal is `test`.``
var str3 = `
You can write
two line string.` // same as " You can write\n two line string."

"""で囲む形式も提案されてたけど、バッククオートに一本化された模様。->結局バッククオートを簡単に使わないほうがいいということで"""になるかも。

当初は言語仕様としてインデントを扱うようにしていたけど、言語仕様としては書かれたままを文字列として扱うようになり、align()indent()などのメソッドで対応することになりました。

PreviewとしてJDK12に入る予定です。 入る予定でしたが、却下されました。

Qiitaが```のようにバッククオートをバッククオートで囲めないので困る人が続出する気がする。

2018/5/8時点では次のようにレンダリングされている。

スクリーンショット 2018-05-08 4.38.25.png


ラムダの微修正

ラムダの微修正

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


メソッド定義の拡張

http://openjdk.java.net/jeps/8209434

メソッド定義で->を使えるようにする

int twice(int x) -> x * x;

メソッド参照は=

int min(int x, int y) = Integer::min;

オーバーロード・オーバーライドなどでの移譲がすっきり書けます。


ローカル変数型推論

ローカル変数の型推論を導入するものです。

http://openjdk.java.net/jeps/286

var x = 12;

JDK 10で導入されています。


ラムダへのvar対応

ラムダの変数定義にも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に入りました。