JavaSE8リリース記念!マイナーな言語仕様を紹介してみる(交差型キャスト,レシーバパラメータ(仮引数にthis))

さて,本日未明JavaSE8がリリースされましたね!アメリカ時間では18日でしたが,日本では19日になってました.
無事リリースされたのでJavaSE8で導入されたマイナーな言語仕様を2つ紹介してみたいと思います.
メジャーな言語仕様は誰かが紹介してくれると思うので・・・

交差型キャスト

一つ目はProject Lambdaで導入された交差型キャストです.

まず,Javaには交差型、あるいは合成型と呼ばれるものがあります。
今まではジェネリクスの型境界にだけ使えていた「Type1 & Type2」のように型を&でつないだものです。
これがJavaSE8からはキャスト式で利用できるようになります。
例えば次のようなものです。

     (Type1 & Type2) expr;

&で繋げられる型は3つでも4つでも問題ないです.
ただし,一つ目の型は参照型で,二つ目以降の型はインターフェースでなければいけません.

この特徴はexprのところにラムダ式を書くことを見据えたものです.
なぜこのような事をできるようにしたかというと複数のインターフェースを合成して別の関数型インターフェースを作るという事が必要になると考えられその際にインターフェースを定義するという冗長性を取り除きたかったためです。
例えばある関数型インターフェースFがありラムダ式を書く際にシリアライズ可能にする必要があるとしましょう。そうすると交差型キャストがないと次のように書かなければならないでしょう。

interface SerializeableF extends F, Serializable {}

method((SerializableF) () -> {});

このようにインターフェースを新たに定義しなければならず冗長です。
また,各々のインターフェースごとにこれらの定義を行えば莫大な数のインターフェースが生まれてしまいます.

そこでキャスト時に型を合成できるように交差型キャストが導入されました。

method((F & Serializable) () -> {});

ラムダ式と併用する場合,コンパイラは合成された型をラムダ式へのターゲット型として扱うので合成された型は関数型インターフェースになっていなければなりません。
なので次のようなキャストと共にラムダ式を使うとコンパイラエラーになります。

Object o = (F0<Integer, Integer> & F1<Integer, Integer, Integer>) (i) -> i;
o = (Object & F) () -> {};

一見使いそうな機能ですが,恐らく使わないです.
JDKのCoreAPIでもいくつか使われていますが,現時点でこれが現れるクラスは数個でした.

ぜひラムダ式を使う場合はこの交差型キャストと併用しシリアライズしてはどうでしょうか.


レシーバパラメータ(仮引数にthis)

二つ目のマイナー言語仕様はTypeAnnotationで導入されたレシーバパラメータです.

JavaSE8からはレシーバパラメータとして仮引数にthisを書くことができるようになります.
実引数にではなく仮引数(メソッドの宣言)にです.

今までインスタンスメソッドを定義する場合には次のように書いていました.

class C
{
     void m(int n1, int n2){}
}

JavaSE8では以下のように第一引数にレシーバとしてthisを書くことができるようになります.

class C
{
     void m(C this, int n1, int n2){}
}

この時thisの型はメソッドmが属する型(クラスかインターフェース)を指していなければいけません.
今回の場合はクラスCとなっていなければいけません.Objectを指定するのはNGです.

何のためにこんなことをするのかこのサンプルだけではわからないかと思います.
これはメソッドにおいてthisの型を型アノテーションで注釈したい場合のための機能です.
例えばTAnnoという型アノテーションがありメソッドmで自分自身のクラスを修飾したい場合はレシーバパラメータを用いて次のように書くことができます.

class C
{
     void m(@TAnno C this, int n1, int n2){}
}

良さげですね.使うかどうかは置いておいて・・・

ちなみにインナークラスのコンストラクタにおいてもこの特徴を利用することができます.
その場合はアウタークラスのthisを第一引数に指定します.またその際の変数名は,コンストラクタ内で参照するのと同じようにOuterClass.thisとthisの前にアウタークラスを加えます.

class Outer
{
     class Inner
     {
          Inner(Outer Outer.this, int arg1, int arg2){}

          void m(Inner this, int n1, int n2){}
     }
}

thisの型が分からなくなるという事がよくあると思うのですが,レシーバパラメータがあると安心ですね!
メソッドを定義する度に思い出せます.

ちなみに,匿名クラスとラムダ式ではこの特徴を利用することができません.
匿名クラスでは自分自身の型を表現する方法が無いためです.