下面的代码给我一个编译时错误:
Thread t2 = new Thread(() -> {
try {
sleep(1000);
}
catch (InterruptedException e) {}
});
未为类型A(是我的班级名称)定义方法sleep(int)。
相反,当我使用匿名内部类时,没有编译时错误:
Thread t1 = new Thread(){
public void run(){
try {
sleep(1000);
} catch (InterruptedException e) {}
}
};
下面的代码也可以正常工作:
Thread t3 = new Thread(() -> System.out.println("In lambda"));
事情如何在lambda表达体内发挥作用?请帮忙。
从许多答案中,我可以看到在第一种方法中可以使用Thread.sleep(1000)
解决错误。但是,如果有人可以向我解释lambda表达式中的作用域和上下文如何工作,我将不胜感激。
答案 0 :(得分:83)
Thread.sleep
是Thread
类中的静态方法。
在匿名类中无需任何限定符就可以直接调用sleep
的原因是,因为您实际上处于从Thread
继承的类的上下文中。因此,sleep
可以在此处访问。
但是在lambda情况下,您不在从Thread
继承的类中。您位于该代码所在的任何类中。因此,不能直接调用sleep
,而您需要说Thread.sleep
。 documentation也支持:
Lambda表达式具有词法范围。这意味着他们不 从超类型继承任何名称或引入新的级别 范围界定。 Lambda表达式中的声明与 它们在封闭的环境中。
基本上就是说,在lambda内,您实际上处于与在lambda之外相同的范围。如果您无法在lambda之外访问sleep
,那么也不能在其内部访问。
此外,请注意,这里显示的创建线程的两种方式本质上是不同的。在lambda中,您将Runnable
传递给Thread
构造函数,而在匿名类中,您是通过直接创建匿名类来创建Thread
。>
答案 1 :(得分:18)
第一种方法是将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
。
答案 2 :(得分:10)
这最终是对范围的误解。
在将lambda传递给线程时,您不是在创建Thread的子类,而是在传递Runnable的FunctionalInterface并调用Thread的构造函数。尝试调用Sleep时,作用域的上下文是Runnable +类的组合(如果Runnable接口包含默认方法,则可以调用默认方法),而不是Thread。
Runnable没有定义sleep(),但是Thread定义了。
创建匿名内部类时,您将线程化为子类,因此sleep()可供您调用,因为Scope的上下文是Thread的子类。
由于这种误解,不建议使用没有类名的静态方法。使用Thread.Sleep既正确又在所有情况下都是明确的。
答案 3 :(得分:7)
您的疑问源自对如何定义lambda表达式的作用域和匿名类的误解。下面,我将尝试澄清这一点。
Lambda表达式不会引入新的作用域范围。这意味着,在它内部,您只能访问与在直接封闭的代码块中可以访问的相同的东西。看看docs怎么说:
Lambda表达式具有词法范围。这意味着他们不 从超类型继承任何名称或引入新的级别 范围界定。 Lambda表达式中的声明与 它们在封闭的环境中。
匿名类的工作方式不同。他们确实引入了新的作用域范围。它们的行为很像local class(您在代码块中声明的类),尽管它们没有构造函数。看看docs怎么说:
类似于本地类,匿名类可以捕获变量;它们对包含范围的局部变量具有相同的访问权限:
- 匿名类可以访问其封闭类的成员。
- 匿名类无法在其封闭范围内访问未声明为final或有效地为final的局部变量。
- 就像嵌套类一样,匿名类中的类型声明(例如变量)会遮盖住封装中的其他任何声明 具有相同名称的作用域。有关更多信息,请参见阴影。
在这种情况下,匿名类将像Thread
内的本地类一样起作用,因此,由于此方法在其范围之内,因此它将能够直接访问sleep()
。但是,在lambda表达式中,sleep()
不在其范围内(您不能在封闭环境中调用sleep()
),因此必须使用Thread.sleep()
。请注意,此方法是 static ,因此不需要调用其类的实例。
答案 4 :(得分:5)
以下代码有效:
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {}
});
这是因为在创建sleep(int milliseconds)
实例并将其传递给Thread
类构造函数时,Runnable
是Thread
类的方法。
在第二种方法中,您正在创建Thread
类的匿名内部类实例,因此可以访问所有Thread
类方法。
答案 5 :(得分:4)
我喜欢所提供和接受的答案,但是用简单得多的话来说,您可以认为this
已从匿名内部类更改为lambda。
对于AIC,this
指您要扩展的类的实例(在您的示例中为Thread
),对于lambda expression
,{{1} }指的是包围lambda表达式的类的实例(无论您的示例中是哪个类)。我敢打赌,在您使用lambda表达式的课堂上,还没有这样的this
定义。
答案 6 :(得分:2)
public void foo() {
new Thread(() -> { sleep(1000); });
}
等同于
public void foo() {
new Thread(this::lambda$0);
}
private void lambda$0() {
sleep(1000);
}
因此编译器将不会在sleep
中查找Thread
答案 7 :(得分:-2)
Thread.sleep是静态方法...
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {}
});