ぱぴブログ

さんたろのブログ

ゲーム開発が好きな人のひとりごと

Java奮闘記4 メソッド参照とラムダ式

はじめに

環境などは以下の記事をご覧いただきたい。
papyrustaro.hatenablog.jp

概要

Java8から追加されたラムダ式、メソッド参照、ストリーム処理についての解説。
ラムダ式とメソッド参照は、メソッド単体を参照するための機能。
ストリーム処理とは繰り返し処理をパイプライン処理として抽象化したもの。

メソッド参照

文法

・クラスメソッド → クラス名::クラスメソッド名
インスタンスメソッド → オブジェクト参照::インスタンスメソッド名
インスタンスメソッド(レシーバオブジェクトが第一引数) → 型名::インスタンスメソッド名
・コンストラクタ → クラス名::new

サンプルコード

public class Sampler{
	public static void main(String[] args) {
	    //メソッド参照を渡す側
		execPrinter(Sampler::printMessage, "AAA");
	}
	
	//メソッド参照を受け取る側
	static void execPrinter(Printer printer, String str) {
	    printer.print(str);
	}
	
	static void printMessage(String message) {
	    System.out.println(message);
	}
	
	@FunctionalInterface
	interface Printer{
	    public void print(String msg);
	}
}

Sampler::printMessageがSamplerクラスのprintMessageクラスメソッドのメソッド参照。

メソッド参照を引数で受けるのがexecPrinter。

・メソッド参照でメソッド単体を取り出して渡せる
・メソッド参照を受け取る側はオブジェクト参照で受け取る。
オブジェクトの型はインターフェース型。

関数型インターフェース

関数型インターフェースはメソッド単体の型として働く、
抽象メソッドがひとつのインターフェース。

メソッドの型は引数の型と返り値の型で決まる(メソッド名は関係ない)。

サンプルコード

public static void main(String[] args) {
    Creator<StringBuilder> creator = StringBuilder::new; //コンストラクタのメソッド参照の代入
    StringBuilder sb = creator.createInSAM(8);
	    
    Appender appender = sb::append; //StringBuilderオブジェクトのappendメソッドの参照を代入
    appender.appendInSAM("ABC");
	    
    Appender2 appender2 = StringBuilder::append; //メソッド参照の代入
    appender2.appendInSAM2(sb, "DEF");
    System.out.println(sb);
}
	
//コンストラクタのメソッド参照のための関数型インターフェース
@FunctionalInterface
interface Creator<E>{
    public E createInSAM(int capacity);
}
    
//インスタンスメソッドのメソッド参照のための関数型インターフェース
@FunctionalInterface
interface Appender{
    public StringBuilder appendInSAM(String str);
}
    
//インスタンスメソッドのメソッド参照のための関数型インターフェース
@FunctionalInterface
interface Appender2{
    public void appendInSAM2(StringBuilder sb, String s);
}

ラムダ式

ラムダ式とは

ラムダ式はメソッド定義を式として記述できる機能。
メソッド単体をリテラルとして記述した式とみなせる。
メソッド単体を値やオブジェクト参照のように扱える。

上記のサンプルコードをラムダ式を用いると以下のようになる

public static void main(String[] args) {
    Appender2 appender2 = (sb, s) -> sb.append(s);
	    
    StringBuilder result = new StringBuilder();
    appender2.appendInSAM2(result, "ABC");
    appender2.appendInSAM2(result, "DEF");
	    
    System.out.println(result);
}
	
@FunctionalInterface
interface Appender2{
    public void appendInSAM2(StringBuilder sb, String s);
}
文法
(仮引数列) -> { 処理本体の文(複数) }
または
(仮引数列) -> 処理本体の式

省略の具体例

(x, y) -> x + y //仮引数列の引数の型名は省略可能、型名は省略するならすべてする
x -> x + y // 引数が一つの場合()を省略可能
() -> 1 //引数なしの場合()は省略不可
x -> { return x + 1; } //処理本体を{}で囲った場合、文にする必要がある
ラムダ式の型

メソッド参照と同じように、ラムダ式の型は引数列の型と返り値の型で決まる。
先ほどのコードを例にエラーになる例を挙げる。

//返り値の型が異なる
Appender2 appender = (sb, s) -> { return sb.appender(s) ; };

//引数列の型が異なる
Appender2 appender = (sb) -> sb.append("");
Appender2 appender = (String sb, String s) -> sb.append(s);
Appender2 appender = (StringBuilder sb, String s, int n) -> sb.append(s);
ラムダ式とローカル変数

ラムダ式内から、ラムダ式外、メソッド内の実質final(final修飾子がついてなくても良い)のローカル変数およびパラメータ変数にアクセス可能。

public class Sampler{
    Consumer<String> createProc(String paramValue){
        final String localValue = "local";
        
        Consumer<String> proc = s -> System.out.println(s + ": " + paramValue + ", " + localValue);
        return proc;
    }
    
    public static void main(String[] args) {
        Sampler sampler1 = new Sampler();
        Consumer<String> proc = sampler1.createProc("param");
        //ラムダ式のメソッド実行
        proc.accept("showing");
    }
}
ラムダ式と変数のスコープ

ラムダ式自体のパラメータ変数及び式内で宣言したローカル変数のスコープはラムダ式内に閉じている。

ラムダ式とメソッド参照の使い分け

両者は書き換え可能。
名前を付けて再利用したい場合はメソッド参照、
そうでない場合はラムダ式で書くのが良い。

ストリーム処理

まだ理解できない部分が多いため、落ち着いたら追記しようと思う。

以下に参考になりそうな記事を載せておく↓
qiita.com