LoginSignup
154
82

More than 1 year has passed since last update.

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

Last updated at Posted at 2018-01-23

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ステートメントでも有効です。

casenullも使えるようになるはずだったけど、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時点では次のようにレンダリングされている。
スクリーンショット 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

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に入りました。

154
82
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
154
82