web-dev-qa-db-ja.com

なぜLambda関数の中でThread#sleep()を直接呼び出せないのですか?

以下のコードは私にコンパイル時エラーを与えています:

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)を使ってエラーを解決できることがわかります。しかし、ラムダ式の中でスコープとコンテキストがどのように機能するかを誰かが私に説明できれば非常にありがたいです。

57

Thread.sleepThreadクラスの静的メソッドです。

無名クラスで修飾子を使わずに直接sleepを呼び出すことができるのは、実際にはThreadを継承するクラスのコンテキスト内にいるからです。したがって、sleepはそこからアクセス可能です。

しかし、ラムダの場合、あなたはThreadから継承するクラスにはいません。あなたはそのコードを取り巻くどんなクラスでも内部にいます。したがって、sleepを直接呼び出すことはできず、Thread.sleepと言う必要があります。 documentation もこれをサポートしています。

ラムダ式はレキシカルスコープです。つまり、スーパータイプから名前を継承したり、新しいレベルのスコープを導入したりすることはありません。ラムダ式の中の宣言は、それを囲んでいる環境の中にあるかのように解釈されます。

基本的には、ラムダの内側では、実際にはラムダの外側にいるのと同じスコープにいるということです。ラムダの外側でsleepにアクセスできない場合、内側からもアクセスできません。

また、ここで示したスレッドを作成する2つの方法は本質的に異なります。ラムダではRunnableThreadコンストラクタに渡していますが、匿名クラスでは匿名クラスを直接作成してThreadを作成しています。

83
Sweeper

最初のアプローチでは、RunnableThreadに渡します。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.sleepthread.runを呼び出してもかまいません。

18
xingbin

これはScopeの誤解になります。

ラムダをスレッドに渡すときは、Threadのサブクラスを作成するのではなく、代わりに Runnable のFunctionalInterfaceを渡してThreadのコンストラクターを呼び出します。 Sleepを呼び出そうとすると、スコープのコンテキストはRunnable +自分のクラスの組み合わせになります(Runnableインタフェースにデフォルトのメソッドがある場合はデフォルトのメソッドを呼び出すことができます)。Threadではなく.

Runnableにはsleep()が定義されていませんが、Threadには定義されています。

無名の内部クラスを作成するときは、Threadをサブクラス化しているので、ScopeのコンテキストはThreadのサブクラスであるため、sleep()を呼び出すことができます。

クラス名なしで静的メソッドを呼び出すことは、まさにこの種の誤解のためにお勧めできません。 Thread.Sleepを使用することは、正しいことであり、あらゆる状況において明白です。

10
Ryan The Leach

あなたの疑問は、ラムダ式と無名クラスのスコープがどのように定義されているかについての誤解に由来します。以下で、これを明確にします。

ラムダ式は、新しいレベルのスコープを導入しません。つまり、その中には、すぐに囲むコードブロックでアクセスできるものと同じものにしかアクセスできません。 docs の内容を参照してください。

ラムダ式はレキシカルスコープです。つまり、スーパータイプから名前を継承したり、新しいレベルのスコープを導入したりすることはありません。ラムダ式の中の宣言は、それを囲んでいる環境の中にあるかのように解釈されます。

無名クラスは異なった働きをします。彼らは新しいレベルのスコープを導入します。それらは、 ローカルクラス (コードブロック内で宣言するクラス)のように動作しますが、できません。コンストラクタがあります。 docs の内容を参照してください。

ローカルクラスと同様に、無名クラスは変数を取り込むことができます。それらは、それを囲むスコープのローカル変数に同じアクセス権を持ちます。

  • 無名クラスは、それを囲むクラスのメンバーにアクセスできます。
  • 無名クラスは、それを包含するスコープ内で、最終的または事実上最終的として宣言されていないローカル変数にアクセスすることはできません。
  • ネストしたクラスと同様に、無名クラスの型(変数など)の宣言は、それを囲むスコープ内の同じ名前を持つ他の宣言を隠します。詳細についてはシャドウイングを参照してください。

このコンテキストでは、匿名クラスはThread内のローカルクラスのように振る舞います。したがって、このメソッドはそのスコープ内にあるため、sleep()に直接アクセスできます。しかし、ラムダ式では、sleep()はその有効範囲内には含まれないため(囲む環境ではsleep()を呼び出すことはできません)、Thread.sleep()を使用する必要があります。このメソッドはstaticであるため、呼び出すためにそのクラスのインスタンスは必要ありません。

7
Talendar

以下のコードが機能します。

    Thread t2 = new Thread(() -> {
        try { 
            Thread.sleep(1000);
        } 
        catch (InterruptedException e) {}
    });

これは、Threadインスタンスを作成してRunnableクラスコンストラクターに渡す間、sleep(int milliseconds)Threadクラスのメソッドであるためです。

2番目の方法では、Threadクラスの匿名の内部クラスインスタンスを作成しているので、すべてのThreadクラスメソッドにアクセスできます。

5
S.K.

私は提供され受け入れられた答えが好きですが、もっと簡単に言えば、thisは匿名の内部クラスからラムダに変更されたと考えることができます。

AICの場合、thisは拡張するクラスのインスタンス(この例ではThread)を表し、lambda expressionの場合、thisはラムダ式を囲むクラスのインスタンスを表します(そのクラスに含まれるものは何でも)。あなたの例)。そして私はあなたがラムダ式を使うあなたのクラスに賭けます、そのようなsleepは定義されていません。

4
Eugene
public void foo() {
    new Thread(() -> { sleep(1000); });
}

と同等です

public void foo() {
    new Thread(this::lambda$0);
}
private void lambda$0() {
    sleep(1000);
}

そのため、コンパイラはsleep内のThreadを検索しません。

2
yyyy