匿名クラスで行っていた処理をラムダ式で書き換えられることから分かるように、ラムダ式も匿名クラスでの制限事項が適用されます。
匿名クラスではクラスが持つクラス変数やstatic変数は実装する処理内で扱うことは可能です。しかし、メソッド内で定義されるローカル変数や匿名クラスが定義されているメソッドの引数は、finalでない限り参照はできても何らかの処理を行うことはできません。
同様に、ラムダ式でもクラス変数やstatic変数を扱えますが、finalでないローカル変数や引数は扱えません。
例えば次の関数型インターフェースがあったとします。
@FunctionalInterface
public interface DoSomethingInterface {
void doSomething();
}
この関数型インターフェースを次のようにクラス変数やstatic変数を扱う処理を入れた実装を行う場合、エラーにはならず正常に実行できます。
public class SampleClass {
private int classField = 0;
private static int staticField = 0;
private void process() {
DoSomethingInterface functionalInterface = () -> {
classField ++; // ← この変数を扱うことは可能。
staticField ++; // ← この変数を扱うことは可能。
};
System.out.println("Before; classField =" + classField);
System.out.println("Before; staticField =" + staticField);
functionalInterface.doSomething(); // 処理を実行
System.out.println("After; classField =" + classField);
System.out.println("After; staticField =" + staticField);
}
public static void main(String[] args) {
SampleClass sample = new SampleClass();
sample.process();
}
}
Before; classField=0 Before; staticField=0 After; classField=1 After; staticField=1
しかし、メソッド内のfinalでないローカル変数やメソッドの引数を変えようとするとエラーになります。
private void process() {
int i = 0;
DoSomethingInterface functionalInterface = () -> {
i++; // ← この変数は扱えないのでエラーになります。
};
……
ただし、ローカル変数の値を変えずに参照するのみの場合はエラーにはなりません。次の場合はコンパイル時も実行時もエラーにならずに実行できます。
private void process() {
int i = 0;
DoSomethingInterface functionalInterface = () -> {
System.out.println("i=" + i);
};
……
また、ローカル変数や引数が何らかのクラスだった場合、そのクラス自体を変えることはできませんが、クラスが持つクラス変数の値を変えることは可能です。
例えば次のクラスがあったとします。
public class DummyClass {
public int field;
}
このクラスに対して、次のようにクラス自身を変えることはできませんが、クラスが持つクラス変数を変えても、エラーにはならず処理を実行できます。
private void process(DummyClass argClass) {
DummyClass localFiledClass = new DummyClass();
DoSomethingInterface functionalInterface = () -> {
// localFiledClass = new DummyClass(); // ← 不可
// argClass = new DummyClass(); // ← 不可
localFiledClass.field++; // ← 可能
argClass.field++; // ← 可能
……
匿名クラスではその匿名クラスが実装されているクラスのメソッドを呼び出せます。同様に、ラムダ式でもクラス内のメソッドを呼び出すことが可能です。
例えば、次のようにクラスが持つメソッドに対してラムダ式から呼び出すことが可能です。
public class SampleClass {
private void process() {
DoSomethingInterface functionalInterface = () -> {
privateMethod();
method();
protectedMethod();
publicMethod();
staticMethod();
};
functionalInterface.doSomething();
}
private void privateMethod() {
System.out.println("privateのメソッドが呼ばれました。");
}
void method() {
System.out.println("アクセス修飾子なしメソッドが呼ばれました。");
}
protected void protectedMethod() {
System.out.println("protectedメソッドが呼ばれました。");
}
public void publicMethod() {
System.out.println("publicメソッドが呼ばれました。");
}
private static void staticMethod() {
System.out.println("staticメソッドが呼ばれました。");
}
public static void main(String[] args) {
SampleClass sample = new SampleClass();
sample.process();
}
}
これが実行されると下記の結果が出ます。
privateのメソッドが呼ばれました。 アクセス修飾子なしメソッドが呼ばれました。 protectedメソッドが呼ばれました。 publicメソッドが呼ばれました。 staticメソッドが呼ばれました。
今まで見てきたように匿名クラスの実装をラムダ式で書き換えることはたいていの場合は可能です。ただし下記については匿名クラスとラムダ式の間で違いがあります。
匿名クラスの実装部分で書かれた「this」は、その匿名クラス自身を表しますが、ラムダ式の場合のthisは、そのラムダ式が記述されたクラス自体を表します。
例えば、引数がなく標準出力のみ行うための関数型インターフェースを下記のように実装した場合、匿名クラスのthisは匿名クラスの「SampleClass$1」を表し、ラムダ式のthisは「SampleClass」を表します。
package jp.co.atmarkit.lambda.sample02
import …… //←省略
public class SampleClass {
public static void main(String[] args) {
SampleClass sample = new SampleClass();
sample.process();
}
private void process() {
// 匿名クラスの場合
DoSomethingInterface anonymous = new DoSomethingInterface () {
@Override
public void doSomething() {
System.out.println("匿名クラス:" + this.getClass());
}
};
anonymous.doSomething();
// ラムダ式の場合
DoSomethingInterface lambda = () -> {
System.out.println("ラムダ式:" + this.getClass());
};
lambda.doSomething();
}
}
実行結果は次のようになります。
匿名クラス:class jp.co.atmarkit.lambda.sample02.SampleClass$1 ラムダ式:class jp.co.atmarkit.lambda.sample02.SampleClass
Copyright © ITmedia, Inc. All Rights Reserved.