Home > Archives > Javaラムダ式

Javaラムダ式

Publish:

ラムダ式は、JDK1.8で導入された構文。 関数型インターフェース(抽象メソッドが1つだけ定義されているインターフェース)の変数に代入する箇所ではラムダ式を渡すことが出来る。
※インタフェースには一つだけのメソッドといっても、defalut method,static methodに限らない。0個か2個以上でしたら、実装対象のメソッドを決定できない為、 コンパイルエラーになる

匿名クラスとラムダ式


見た目上は、匿名クラスを短く実装するような気がする。ラムダ式の代入先である関数型インターフェイスには抽象メソッドが一つのみであるため、 ラムダ式がどのメソッドを実装するのかは選択の余地なく決まる。

匿名クラス ラムダ式
Runnable runner = new Runnable() {
@Override
public void run() {
 System.out.println("new thread ");
}
};
Runnable runner = () -> System.out.println("new thread ");
List<Integer> list = Arrays.asList(1, 3, 2);
Collections.sort(list, new Comparator<Integer>() {
  @Override
  public int compare(Integer o1, Integer o2) {
    return Integer.compare(o1, o2);
  }
});
List<Integer> list = Arrays.asList(1, 3, 2);
Collections.sort(list, (o1, o2) -> Integer.compare(o1, o2));

ラムダ式の構文


定義方法 ラムダ式の例
型を明確に示す
() -> {}
(String s) -> {}
(int n1, int n2) -> {}
型を省く
() -> {}
(s) -> {}
(n1, n2) -> {}
×(n1,int n2) -> {}  //一部だけの省略はだめ。
括弧を省く
s -> {}//引数が一つのみでしたら、引数を囲む括弧を省くことができる。同時に型も省かないとならない
定義方法 ラムダ式の例
基本形
(int n) -> {
  return n + 1;
}
戻り値がなし
(int n) -> {
  System.out.println( n);
}
括弧を省く
(int n) -> n + 1//処理本体に文が一つのみでしたら、引数を囲む括弧を省くことができる。
//同時にreturnと末尾のセミコロン「;」も省かないとならない

スコープ


ラムダ式で使う変数は、ラムダ式が定義されている場所の変数とスコープが一緒になる

// ラムダ式の外側で定義されている変数を、ラムダ式内部から参照することが出来る。
void scope1() {
	int n = 123;

	Runnable runner = () -> {
		System.out.println(n);
	};
	runner.run();
}

// 匿名クラスの内側からも外側の変数を参照できる。(JDK1.7以前はfinal変数である必要があった)
void scope1a() {
	int n = 123;

	Runnable runner = new Runnable() {
		@Override
		public void run() {
			System.out.println(n);
		}
	};
	runner.run();
}
// その変数の値を変える(再代入する)ことは出来ない。
void scope2() {
	int n = 123;

	Runnable runner = () -> {
		n++;// コンパイルエラーになる
	};
	runner.run();
}

void scope2a() {
	int n = 123;

	Runnable runner = new Runnable() {
		@Override
		public void run() {
			n++;// コンパイルエラーになる
		}
	};
	runner.run();
}
void scope3() {
	int n = 123;

	Runnable runner = () -> {
		System.out.println(n);
	};

	n++;// コンパイルエラーになる
	runner.run();
}

void scope3a() {
	int n = 123;

	Runnable runner = new Runnable() {
		@Override
		public void run() {
			System.out.println(n);
		}
	};

	n++;// コンパイルエラーになる
	runner.run();
}
void scope4() {
	int t = 123;

	Consumer<String> consumer = (String t) -> {// コンパイルエラーになる,「String
												// t」を「String
												// m」に入れ替えたら、エラーにならない
		System.out.println(t);
	};
	consumer.accept("abc");
}

void scope4a() {
	int t = 123;

	Consumer<String> consumer = new Consumer<String>() {
		@Override
		public void accept(String t) {
			System.out.println(t);// 匿名クラスでは別スコープになる。
		}
	};
	consumer.accept("abc");
}
// 「ラムダ式を定義したメソッド」が属しているクラスのインスタンスを指す。
void scopeThis() {
	Runnable runner = () -> {
		System.out.println(this);//☞com.whisper.Test$1@548c4f57
	};
	runner.run();
}

// thisは、匿名クラスのインスタンスを指す。
void scopeThisA() {
	Runnable runner = new Runnable() {
		@Override
		public void run() {
			System.out.println(this);//☞com.whisper.Test@15db9742 匿名クラスのインスタンスを指す(new LMDExpressionScope())
		}
	};
	runner.run();
}
static void scopeThisStatic() {
	  Runnable runner = () -> {
	    System.out.println(this);// コンパイルエラーになる
	  };
	  runner.run();
}

static void scopeThisStaticA() {
	  Runnable runner = new Runnable() {
	    @Override
	    public void run() {
	      System.out.println(this);//匿名クラスのインスタンスを指す(new LMDExpressionScope())
	    }
	  };
	  runner.run();
}

プリミティブ型とラッパークラスの注意点


「関数型インターフェースのメソッドの引数の型」と「ラムダ式の引数の型」に関しては、自動変換のような事はできない。プリミティブ型(intなど)とラッパークラスう(Integerなど)は区別されている。 皮肉にメソッドの戻り値の型は自動変換できる

public class LambdaScope2 {

	interface IntArgument {
		public void method(int n);
	}

	interface IntegerArgument {
		public void method(Integer n);
	}

	void call(IntArgument func) {
		func.method(123);
	}

	IntArgument test11 = (int n) -> System.out.println(n);
	IntArgument test12 = (Integer n) -> System.out.println(n);// 引数の型が異なるのでコンパイルエラー。
	IntArgument test13 = (n) -> System.out.println(n);// 引数の型を省略していれば問題ない。

	IntegerArgument test21 = (int n) -> System.out.println(n); // 引数の型が異なるのでコンパイルエラー。
	IntegerArgument test22 = (Integer n) -> System.out.println(n);
	IntegerArgument test23 = (n) -> System.out.println(n); // 引数の型を省略していれば問題ない。



	public static void main(String[] args) {
		IntArgument test31 = (n) -> System.out.println(n);
		LMDExpressionScope2 tester = new LMDExpressionScope2();
		tester.call(test31);
		IntegerArgument test32 = (n) -> System.out.println(n);
		tester.call(test32);	//IntArgumentとIntegerArgumentに互換性(継承関係)は無いのでコンパイルエラー。
		tester.call((int n) -> System.out.println(n));
		tester.call((Integer n) -> System.out.println(n));	//引数の型が異なるのでコンパイルエラー。
		tester.call(n -> System.out.println(n));	//引数の型を省略していれば問題ない。
	}
}

キャスト


JDK1.8から交差型キャストという仕様が導入された。 「(インターフェース名1 & インターフェース名2 & …)」という形で、キャストするときに「&」でインターフェース名を繋いでいく。

Runnable object = () -> System.out.println("abc");
// Object object1 = () -> System.out.println("abc"); // コンパイルエラー。ラムダ式がどの関数型インターフェースのものなのか分からない為。
Object object2 = (Runnable) () -> System.out.println("abc"); // 関数型にキャストしたうえで、Objectクラスの変数に代入できるようになる

Runnable runner = () -> System.out.println(123);
System.out.println("runner-------:");
System.out.println(runner instanceof Runnable);
System.out.println(runner instanceof Serializable);
runner.run();

Object object3 = (Runnable) () -> System.out.println(123);
System.out.println("object3-------:");
System.out.println(object3 instanceof Runnable);
System.out.println(object3 instanceof Serializable);

// 交差型キャストでSerializableを指定するとSerializableが実装(合成)されている扱いになる。
Runnable runner3 = (Runnable & Serializable) () -> System.out.println(123);
System.out.println("runner3------:");
System.out.println(runner3 instanceof Runnable);
System.out.println(runner3 instanceof Serializable);
runner3.run();

Object object4 = (Runnable & Serializable & Cloneable) () -> System.out.println("abc");//CloneableでもOK。
System.out.println("object4------:");
System.out.println(object4 instanceof Runnable);
System.out.println(object4 instanceof Serializable);
System.out.println(object4 instanceof Cloneable);

//結果は以下となる
runner-------:
true
false
123
object3-------:
true
false
runner3------:
true
true
123
object4------:
true
true
true
	

シリアライズ


交差型キャストによってシリアライズを実装することは可能となります。

public class LambdaSerialize {

	// シリアライズ
	static byte[] serialize(String s) throws IOException {
		Object object = (Supplier<String> & Serializable) () -> "serialize-" + s;

		try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
			try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
				oos.writeObject(object);
			}
			return bos.toByteArray();
		}
	}

	// デシリアライズ
	static Supplier<String> deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
		try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
				ObjectInputStream ois = new ObjectInputStream(bis)) {
			@SuppressWarnings("unchecked")
			Supplier<String> supplier = (Supplier<String>) ois.readObject();
			return supplier;
		}
	}

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		byte[] buf1 = serialize("str1");
		byte[] buf2 = serialize("str2");

		Supplier<String> supplier1 = deserialize(buf1);
		Supplier<String> supplier2 = deserialize(buf2);

		System.out.println(supplier1.get());
		System.out.println(supplier2.get());
	}

}

結果↓
serialize-str1
serialize-str2

Java8 Tutorial

Disclaimer: This article is based on BY-NC-SA license. Reproduced please specify switched from: whisper