Android Studio 2.4 Preview 4 から Java8の一部の機能が正式にサポートされたので、この機会にJava8の追加機能について学び直そうと思いまとめました。
Java8についてほぼ触れたことがなかったので、間違っている箇所などありましたら指摘して頂けると嬉しいです。
Android Studio で Java8を使うために
こちらのドキュメントを参考にしています。
1. Android Studio をアップデート
Android Studio 2.4 Preview 4 以上をダウンロードしてください
2. Androidプラグインをアップデート
プロジェクト配下のbuild.gradleを開いてAndroidプラグインのバージョンを2.4.0-alpha6
以上にします。
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:2.4.0-alpha6'
}
}
3. Javaバージョンを追加
モジュール配下のbuild.gradleに下記を追加してください。
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
これでJava8が使えるようになりました。
ラムダ式(Lambda expressions)
関数型インターフェースに対して匿名クラスの記述を簡略化した書式で実装することができます。
説明だけでは解りづらいので、まずはコードをみてください。
ラムダ式を使わないJava 8以前の書き方だと以下の様なコードになると思います。
public class Test {
public static void main(String args[]) {
Man man = new Man() {
@Override
public void laugh(String voice) {
System.out.println(voice);
}
};
man.laugh("Hahaha!");
}
interface Man {
void laugh(String voice);
}
}
これをJava 8から導入されたラムダ式を使用して書き換えるとこのようになります。
public class Test {
public static void main(String args[]) {
Man man = (voice) -> {System.out.println(voice);}
man.laugh("Hahaha!");
}
@FunctionalInterface
interface Man {
void laugh(String voice);
}
}
インターフェースの実装部分がかなり簡略化されたのがわかりますね。
ラムダ式は関数型インターフェース、簡単に説明すると1つの抽象メソッドしか持たないインターフェースの場合に使用することができます。
ラムダ式の書式は以下のようになります。
( 実装するメソッドの引数 ) -> { 処理 }
実装するメソッドの引数
はvoice
としましたが、任意の文字列(x
など)でも問題ありません。
引数の前に型の宣言がない事が不思議に思うかもしれませんが、抽象メソッドの定義時に決まっているので(型推論により)型が不要になります。
引数が単一の場合は()
も不要になります。
処理は1行の場合には{}
は不要です。
以上を踏まえると下記のようにさらに簡略化できます。
Man man = voice -> System.out.println(voice);
また、Man
インターフェースの前に@FunctionalInterface
アノテーションが付いているに気がついたでしょうか?
このアノテーションもラムダ式と一緒に導入され、関数型インターフェースであることを表しています。
もし、@FunctionalInterface
が定義されているインターフェースにもう一つ抽象メソッドを定義するとコンパイルエラーになります。
もし、いままでラムダ式で記述していたインターフェースにもう一つ抽象メソッドを追加したとします。
その場合、ラムダ式の記述ができなくなってしまうので、新たに匿名クラスでコードを書きなおす必要がでてきます。
このようなことが起きない様に@FunctionalInterface
をつける事によって実装者に事前に知らせる役目があります。
匿名クラスをラムダ式で書き換えられることからわかる様に、ラムダ式は匿名クラスの仕様を引き継いでいます。
匿名クラスではfinal
で宣言されていないローカル変数にアクセスできませんでしたが、ラムダ式の場合も同様にアクセスすることができません。
しかし!
Java8からはローカル変数宣言時に値が導入され、それ以降変更されていない場合はfinal
とみなし、final
を付けなくても問題ありません。
public static void main(String args[]) {
String name = "hoge";
Man man = new Man() {
@Override
public void laugh(String voice) {
System.out.print(voice);
System.out.print(name); // コンパイルエラーにならない
}
};
// name = "bar"; // コメントを外すとコンパイルエラーになる。
}
匿名クラスとラムダ式では、this
の参照先に違いがあります。
匿名クラス内でthis
を使用した場合は匿名クラス自身を参照します。
しかし、ラムダ式内でthis
を使用した場合は実装されているクラスを参照します。
Man man = new Man() {
@Override
public void laugh(String voice) {
System.out.println(this.getClass()); // class xxx.yyy.zzz.Test$1
}
};
Man man2 = voice -> System.out.println(this.getClass()); // class xxx.yyy.zzz.Test
メソッド参照(Method References)
メソッド参照によって、ラムダ式をさらに簡略化した記述ができます。
この様なインターフェースを使用する場合、
interface Man {
void laugh(String voice);
}
ラムダ式ではこの様に記述していましたが、
Man man = voice -> System.out.print(voice);
メソッド参照ではこの様になります。
Man man = System.out::print;
引数が消えて、メソッドチェーンに::
が含まれているのがわかります。
メソッド参照の書式は下記のようになります。
クラス名::メソッド名
インスタンスメソッドの場合は下記のようになります。
Test test = new Test();
Man man = test::method;
メソッド参照は抽象メソッドの引数と処理に渡す引数が同じ場合、要するに抽象メソッドと処理するメソッドのシグネチャが同じ場合に使用することができます。
System$out#print(String s)
とMan#laugh(String voice)
はどちらもString
型の引数を1つ
であることからメソッド参照が可能なわけです。
引数を2つにして見ましょう。
interface Man {
void laugh(String voice, String voice2);
}
TextUtils#split(String text, String expression)
は2つのString
型を引数に持つため、このようにメソッド参照で記述することができます。
Man man = TextUtils::split;
メソッド参照を使うことでコードがスッキリしますが、可読性が落ちるのでラムダ式にとどめておくのがいいでしょう。
タイプアノテーション
以前からタイプアノテーションはありますが、Java8から新たにTYPE_PARAMETER
とTYPE_USE
が追加されました。
TYPE_PARAMETER
とTYPE_USE
は、API level 24 以上で使用可能です。
タイプアノテーションはアノテーションが定義できる箇所を宣言するために使用します。
例えば、@Override
の場合は以下の様に定義されています。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
タイプアノテーションは@Target(ElementType.METHOD)
のことで、ElementType.METHOD
はメソッドにこのアノテーションが宣言可能であることを表しています。
Java8から新しく追加されたTYPE_PARAMETER
とTYPE_USE
について説明します。
TYPE_PARAMETER
はジェネリクスのパラメータに使用することができます。
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestParam {}
public class Test<@TestParam E>{
public <@TestParam F> F method(){
//...
}
}
TYPE_USE
は型が宣言されている全ての箇所で使用することができます。
@Target(ElementType.TYPE_USE)
public @interface TestType {}
すべての例を書き出しませんが、とりあえず型の宣言がある箇所すべてで使用可能です。
@TestType // 型宣言
public class MyApp<@TestType T> // 型パラメータ宣言
extends @TestType Application{ // 拡張(or 実装)する型
@Override
public void start(@TestType Stage stage) { // パラメータの型
}
インターフェースにDefault と staticメソッドの追加
API level 24以上で使用可能です。
しかし、現段階ではInstant Runに対応していない様です。(Issue #302460)
static メソッド
java8からインターフェースにstaticメソッドを定義できるようになりました。
interface Man {
static void staticMethod(){
System.out.print("static method");
}
}
staticメソッドを呼び出す場合は普段staticメソッドを呼び出す時と同様です。
Man.staticMethod();
Default
これまではインターフェースは抽象メソッドのみしか定義できませんでしたが、実装メソッドも定義できるようになりました。
実装メソッドを定義するには、default
をメソッドの先頭に付けます。
interface Man {
void laugh();
default void sayGreeting() {
System.out.println("Hello");
}
}
上記のインターフェースを実装する場合は、最低限laugh()
メソッドのみ実装するだけで良くなります。
public class Test {
public static void main(String args[]) {
Man man = new Man() {
@Override
public void laugh() {
System.out.println("Hahaha!");
}
};
man.laugh(); // "Hahaha!"
man.sayGreeting(); // "Hello"
}
interface Man {
void laugh();
default void sayGreeting() {
System.out.println("Hello");
}
}
}
もちろん、sayGreeting()
をオーバーライドすることでsayGreeting()
の処理も変更することができます。
感覚としては、abstract
クラスににてますね。
Javaでは多重継承が認められていませんが、インターフェースに限っては多重継承する事ができます。
なので、今までインターフェースを多重継承したabstract
クラスをextends
していたところをimplements
だけで済むようになります。
また、コールバックインターフェースを実装する際に使用しないメソッドもあると思います。いままでは、それらのメソッドを全てオーバーライドしなくてはなりませんでしたが、
default
を使用する事によって無駄なメソッドをオーバーライドせずに済むようになりました。
おわりに
メソッド参照は初見だと全く意味がわかりませんでした。
わかっても慣れていないので、頭混乱します。
できれば使って欲しく無いと思いました。
以上。