Почему мы не можем вызвать Thread # sleep() непосредственно внутри функции Lambda?

Код ниже дает мне ошибку времени компиляции:

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) в моем первом подходе. Тем не менее, я бы очень признателен, если бы кто-нибудь мог объяснить мне, как область и контекст работают в выражении лямбда.

Ответы

Ответ 1

Thread.sleep - это статический метод в классе Thread.

Причина, по которой вы можете вызвать sleep напрямую без каких-либо квалификаторов в анонимном классе, состоит в том, что вы фактически находитесь в контексте класса, который наследует от Thread. Таким образом, sleep доступен там.

Но в случае с лямбдой вы не находитесь в классе, который наследуется от Thread. Вы находитесь внутри любого класса, окружающего этот код. Поэтому sleep не может быть вызван напрямую, и вам нужно сказать Thread.sleep. Документация также поддерживает это:

Лямбда-выражения лексически ограничены. Это означает, что они не наследуют никаких имен от супертипа или не вводят новый уровень охвата. Объявления в выражении лямбда интерпретируются так же, как и в окружении.

В основном это говорит о том, что внутри лямбды вы на самом деле находитесь в том же объеме, как если бы вы были вне лямбды. Если вы не можете получить доступ к sleep за пределами лямбда, вы также не можете на внутренней стороне.

Кроме того, обратите внимание, что два способа создания потока, который вы здесь показали, по своей сути отличаются друг от друга. В lambda вы передаете Runnable в конструктор Thread, тогда как в анонимном классе вы создаете Thread, создавая его анонимный класс.

Ответ 2

В первом подходе вы передаете 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);

В то время как во втором вы переопределяете метод thread.run напрямую, так что нормально вызывать thread.sleep в thread.run.

Ответ 3

Это в конечном итоге является непониманием области.

Когда вы передаете ленму в поток, вы не создаете подкласс Thread, вместо этого вы проходите в FunctionalInterface Runnable и вызываете конструктор Thread. Когда вы пытаетесь вызвать "Сон", контекст вашей области действия представляет собой комбинацию Runnable + your class (вы можете вызывать методы по умолчанию, если у них был Runnable-интерфейс), а не Thread.

Runnable не имеет sleep(), но Thread делает.

Когда вы создаете анонимный внутренний класс, вы подклассифицируете Thread, поэтому sleep() доступен для вызова, так как контекст Scope является подклассом Thread.

Вызов статических методов, без имени класса, обескуражен именно для такого недоразумения. Использование Thread.Sleep является правильным и недвусмысленным при любых обстоятельствах.

Ответ 4

Ваше сомнение возникает из-за непонимания о том, как определяются области лямбда-выражения и анонимного класса. Ниже я попытаюсь прояснить это.

Лямбда-выражения НЕ вводят новый уровень охвата. Это означает, что внутри него вы можете получить доступ только к тем вещам, к которым вы могли бы получить доступ, в сразу же входящем кодовом блоке. Посмотрите, что говорят документы:

Лямбда-выражения лексически ограничены. Это означает, что они не наследуют никаких имен от супертипа или не вводят новый уровень охвата. Объявления в выражении лямбда интерпретируются так же, как и в окружении.

Анонимные классы работают по-разному. Они вводят новый уровень охвата. Они ведут себя как локальный класс (класс, который вы объявляете внутри блока кода), хотя у них не может быть конструкторов. Посмотрите, что говорят документы:

Подобно локальным классам, анонимные классы могут захватывать переменные; они имеют одинаковый доступ к локальным переменным охватывающей области:

  • Анонимный класс имеет доступ к членам входящего класса.
  • Анонимный класс не может получить доступ к локальным переменным в своей охватывающей области, которые не объявлены окончательными или фактически окончательными.
  • Подобно вложенному классу объявление типа (такого как переменная) в анонимном классе затеняет любые другие объявления в охватывающей области, имеющие одно и то же имя. См. "Тени" для получения дополнительной информации.

В этом контексте анонимный класс будет действовать как локальный класс внутри Thread и, таким образом, он сможет напрямую обращаться к sleep(), так как этот метод будет находиться в пределах его области действия. Однако в выражении лямбда sleep() не будет находиться в пределах своей области (вы не можете вызвать sleep() в окружении), так что вы должны использовать Thread.sleep(). Обратите внимание, что этот метод является статическим и, следовательно, не требует экземпляра его класса для вызова.

Ответ 5

Следующие работы кода:

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

Это связано с тем, что sleep(int milliseconds) является методом класса Thread при создании и передаче экземпляра Runnable в конструктор класса Thread.

Во втором методе вы создаете анонимный внутренний класс класса Thread и, таким образом, имеете доступ ко всем методам класса Thread.

Ответ 6

Мне нравится ответ, который был предоставлен и принят, но в гораздо более простых словах вы можете подумать, что this изменилось с анонимного внутреннего класса на лямбду.

В случае AIC this относится к экземпляру класса, который вы расширяете (в вашем примере, являющемся Thread), в случае lambda expression this относится к экземпляру класса, который окружает лямбда-выражение (независимо от того, что этот класс в вашем примере). И я уверен, что в вашем классе, где вы используете выражение лямбда, не существует такого sleep.

Ответ 7

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

эквивалентно

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

поэтому компилятор не будет искать sleep в Thread

Ответ 8

Thread.sleep - статический метод...

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