LoginSignup
9
7

More than 5 years have passed since last update.

Android Studio2.4 から使えるようになった Java8を再入門

Posted at

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_PARAMETERTYPE_USEが追加されました。
TYPE_PARAMETERTYPE_USEは、API level 24 以上で使用可能です。

タイプアノテーションはアノテーションが定義できる箇所を宣言するために使用します。

例えば、@Overrideの場合は以下の様に定義されています。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

タイプアノテーションは@Target(ElementType.METHOD)のことで、ElementType.METHODはメソッドにこのアノテーションが宣言可能であることを表しています。

Java8から新しく追加されたTYPE_PARAMETERTYPE_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を使用する事によって無駄なメソッドをオーバーライドせずに済むようになりました。

おわりに

メソッド参照は初見だと全く意味がわかりませんでした。
わかっても慣れていないので、頭混乱します。
できれば使って欲しく無いと思いました。

以上。

9
7
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
9
7