以下のコードは私にコンパイル時エラーを与えています:
Thread t2 = new Thread(() -> {
try {
sleep(1000);
}
catch (InterruptedException e) {}
});
メソッドsleep(int)は、タイプAでは未定義です(Aは私のクラス名です)。
一方、無名の内部クラスを使用しても、コンパイル時エラーは発生しません。
Thread t1 = new Thread(){
public void run(){
try {
sleep(1000);
} catch (InterruptedException e) {}
}
};
以下のコードでもうまくいきます。
Thread t3 = new Thread(() -> System.out.println("In lambda"));
ラムダ式本体の中ではどのように機能しますか?助けてください。
多くの答えから、私の最初のアプローチでThread.sleep(1000)
を使ってエラーを解決できることがわかります。しかし、ラムダ式の中でスコープとコンテキストがどのように機能するかを誰かが私に説明できれば非常にありがたいです。
Thread.sleep
はThread
クラスの静的メソッドです。
無名クラスで修飾子を使わずに直接sleep
を呼び出すことができるのは、実際にはThread
を継承するクラスのコンテキスト内にいるからです。したがって、sleep
はそこからアクセス可能です。
しかし、ラムダの場合、あなたはThread
から継承するクラスにはいません。あなたはそのコードを取り巻くどんなクラスでも内部にいます。したがって、sleep
を直接呼び出すことはできず、Thread.sleep
と言う必要があります。 documentation もこれをサポートしています。
ラムダ式はレキシカルスコープです。つまり、スーパータイプから名前を継承したり、新しいレベルのスコープを導入したりすることはありません。ラムダ式の中の宣言は、それを囲んでいる環境の中にあるかのように解釈されます。
基本的には、ラムダの内側では、実際にはラムダの外側にいるのと同じスコープにいるということです。ラムダの外側でsleep
にアクセスできない場合、内側からもアクセスできません。
また、ここで示したスレッドを作成する2つの方法は本質的に異なります。ラムダではRunnable
をThread
コンストラクタに渡していますが、匿名クラスでは匿名クラスを直接作成してThread
を作成しています。
最初のアプローチでは、Runnable
をThread
に渡します。Thread.sleep
を呼び出す必要があります。
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
});
それはの短いバージョンです:
Runnable runnable = new Runnable() {
public void run(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
};
Thread t2 = new Thread(runnable);
2番目の方法では、thread.run
メソッドを直接オーバーライドしているので、thread.sleep
でthread.run
を呼び出してもかまいません。
これはScopeの誤解になります。
ラムダをスレッドに渡すときは、Threadのサブクラスを作成するのではなく、代わりに Runnable のFunctionalInterfaceを渡してThreadのコンストラクターを呼び出します。 Sleepを呼び出そうとすると、スコープのコンテキストはRunnable +自分のクラスの組み合わせになります(Runnableインタフェースにデフォルトのメソッドがある場合はデフォルトのメソッドを呼び出すことができます)。Threadではなく.
Runnableにはsleep()が定義されていませんが、Threadには定義されています。
無名の内部クラスを作成するときは、Threadをサブクラス化しているので、ScopeのコンテキストはThreadのサブクラスであるため、sleep()を呼び出すことができます。
クラス名なしで静的メソッドを呼び出すことは、まさにこの種の誤解のためにお勧めできません。 Thread.Sleepを使用することは、正しいことであり、あらゆる状況において明白です。
あなたの疑問は、ラムダ式と無名クラスのスコープがどのように定義されているかについての誤解に由来します。以下で、これを明確にします。
ラムダ式は、新しいレベルのスコープを導入しません。つまり、その中には、すぐに囲むコードブロックでアクセスできるものと同じものにしかアクセスできません。 docs の内容を参照してください。
ラムダ式はレキシカルスコープです。つまり、スーパータイプから名前を継承したり、新しいレベルのスコープを導入したりすることはありません。ラムダ式の中の宣言は、それを囲んでいる環境の中にあるかのように解釈されます。
無名クラスは異なった働きをします。彼らは新しいレベルのスコープを導入します。それらは、 ローカルクラス (コードブロック内で宣言するクラス)のように動作しますが、できません。コンストラクタがあります。 docs の内容を参照してください。
ローカルクラスと同様に、無名クラスは変数を取り込むことができます。それらは、それを囲むスコープのローカル変数に同じアクセス権を持ちます。
- 無名クラスは、それを囲むクラスのメンバーにアクセスできます。
- 無名クラスは、それを包含するスコープ内で、最終的または事実上最終的として宣言されていないローカル変数にアクセスすることはできません。
- ネストしたクラスと同様に、無名クラスの型(変数など)の宣言は、それを囲むスコープ内の同じ名前を持つ他の宣言を隠します。詳細についてはシャドウイングを参照してください。
このコンテキストでは、匿名クラスはThread
内のローカルクラスのように振る舞います。したがって、このメソッドはそのスコープ内にあるため、sleep()
に直接アクセスできます。しかし、ラムダ式では、sleep()
はその有効範囲内には含まれないため(囲む環境ではsleep()
を呼び出すことはできません)、Thread.sleep()
を使用する必要があります。このメソッドはstaticであるため、呼び出すためにそのクラスのインスタンスは必要ありません。
以下のコードが機能します。
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {}
});
これは、Thread
インスタンスを作成してRunnable
クラスコンストラクターに渡す間、sleep(int milliseconds)
はThread
クラスのメソッドであるためです。
2番目の方法では、Thread
クラスの匿名の内部クラスインスタンスを作成しているので、すべてのThread
クラスメソッドにアクセスできます。
私は提供され受け入れられた答えが好きですが、もっと簡単に言えば、this
は匿名の内部クラスからラムダに変更されたと考えることができます。
AICの場合、this
は拡張するクラスのインスタンス(この例ではThread
)を表し、lambda expression
の場合、this
はラムダ式を囲むクラスのインスタンスを表します(そのクラスに含まれるものは何でも)。あなたの例)。そして私はあなたがラムダ式を使うあなたのクラスに賭けます、そのようなsleep
は定義されていません。
public void foo() {
new Thread(() -> { sleep(1000); });
}
と同等です
public void foo() {
new Thread(this::lambda$0);
}
private void lambda$0() {
sleep(1000);
}
そのため、コンパイラはsleep
内のThread
を検索しません。