这篇文章适合谁?
- 对于那些阅读本文第一部分的人;
- 对于那些认为自己已经很了解 Java Core,但对 Java 中的 lambda 表达式一无所知的人。或者,也许您已经听说过有关 lambda 的一些内容,但没有了解详细信息。
- 适合那些对 lambda 表达式有一定了解,但仍然害怕和不习惯使用它们的人。
访问外部变量
这段代码会用匿名类编译吗?int counter = 0;
Runnable r = new Runnable() {
@Override
public void run() {
counter++;
}
};
不。该变量counter
必须是final
. 或者不一定final
,但无论如何它都不能改变它的值。lambda 表达式也使用相同的原理。他们可以从声明的地方访问所有对他们“可见”的变量。但 lambda 不应该更改它们(分配新值)。确实,有一个选项可以在匿名类中绕过此限制。只需创建一个引用类型的变量并更改对象的内部状态就足够了。在这种情况下,变量本身将指向同一个对象,在这种情况下,您可以安全地将其指示为final
。
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
@Override
public void run() {
counter.incrementAndGet();
}
};
这里我们的变量counter
是对类型对象的引用AtomicInteger
。而要改变这个对象的状态,就要使用该方法incrementAndGet()
。变量本身的值在程序运行时不会改变,并且始终指向同一个对象,这使得我们可以立即使用关键字声明变量final
。相同的示例,但使用 lambda 表达式:
int counter = 0;
Runnable r = () -> counter++;
它不编译的原因与匿名类的选项相同:counter
它不应在程序运行时更改。但就像这样 - 一切都很好:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
这也适用于调用方法。从 lambda 表达式内部,您不仅可以访问所有“可见”变量,还可以调用您有权访问的那些方法。
public class Main {
public static void main(String[] args) {
Runnable runnable = () -> staticMethod();
new Thread(runnable).start();
}
private static void staticMethod() {
System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
}
}
尽管该方法staticMethod()
是私有的,但它是“可访问的”,可以在方法内部调用main()
,因此也可以从方法中创建的 lambda 内部进行调用main
。
lambda表达式代码执行的时刻
这个问题对你来说似乎太简单了,但值得一问:lambda 表达式内的代码何时被执行?在创作的那一刻?或者在什么时候(仍然未知在哪里)它会被调用?这很容易检查。System.out.println("Запуск программы");
// много всякого разного codeа
// ...
System.out.println("Перед объявлением лямбды");
Runnable runnable = () -> System.out.println("Я - лямбда!");
System.out.println("После объявления лямбды");
// много всякого другого codeа
// ...
System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
显示输出:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
可以看到,lambda表达式代码是在最末尾、线程创建之后、程序执行过程到达方法实际执行时才被执行的run()
。而且在其宣布时根本没有。通过声明 lambda 表达式,我们仅创建了该类型的对象Runnable
并描述了其方法的行为run()
。该方法本身的推出要晚得多。
方法参考?
与 lambda 本身没有直接关系,但我认为在本文中对此说几句话是合乎逻辑的。假设我们有一个 lambda 表达式,它不做任何特殊的事情,只是调用一些方法。x -> System.out.println(x)
他们递给他一些东西х
,它只是召唤他System.out.println()
并把他送到那里х
。在这种情况下,我们可以将其替换为我们需要的方法的链接。像这样:
System.out::println
是的,最后没有括号!更完整的例子:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");
strings.forEach(x -> System.out.println(x));
在最后一行,我们使用forEach()
一个接受接口对象的方法Consumer
。这又是一个只有一种方法的函数式接口void accept(T t)
。因此,我们编写一个带有一个参数的 lambda 表达式(因为它是在接口本身中键入的,所以我们不指示参数的类型,而是指示它将被调用х)
。在 lambda 表达式的主体中,我们编写代码当方法被调用时将会执行accept()
。这里我们只是在屏幕上显示变量中的内容х
。方法本身forEach()
会遍历集合的所有元素,调用Consumer
传递给它的接口对象的方法(我们的 lambda)accept()
,它传递集合中的每个元素。正如我已经说过的,这是一个 lambda 表达式(简单地调用另一个方法),我们可以用对我们需要的方法的引用替换。然后我们的代码将如下所示:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");
strings.forEach(System.out::println);
最主要的是方法(println()
和接受的参数accept())
。由于该方法println()
可以接受任何内容(它对所有基元和任何对象都重载),因此我们可以forEach()
只传入对该方法的引用,而不是 lambda 表达式println()
。然后forEach()
它将获取集合的每个元素并将其直接传递给方法println()
。对于那些第一次遇到此问题的人,请注意请注意,我们不会调用该方法System.out.println()
(单词之间有点,末尾有括号),而是传递对此方法本身的引用。
strings.forEach(System.out.println());
我们会遇到编译错误。因为在调用之前forEach()
,Java 会看到它正在被调用System.out.println()
,它会理解它正在被返回void
,并会尝试void
将其传递给forEach()
在那里等待的类型的对象Consumer
。
使用方法引用的语法
这很简单:-
传递对静态方法的引用
NameКласса:: NameСтатическогоМетода?
public class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Mother"); strings.add("soap"); strings.add("frame"); strings.forEach(Main::staticMethod); } private static void staticMethod(String s) { // do something } }
-
使用现有对象传递对非静态方法的引用
NameПеременнойСОбъектом:: method name
public class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Mother"); strings.add("soap"); strings.add("frame"); Main instance = new Main(); strings.forEach(instance::nonStaticMethod); } private void nonStaticMethod(String s) { // do something } }
-
我们使用实现此类方法的类传递对非静态方法的引用
NameКласса:: method name
public class Main { public static void main(String[] args) { List<User> users = new LinkedList<>(); users.add(new User("Vasya")); users.add(new User("Коля")); users.add(new User("Петя")); users.forEach(User::print); } private static class User { private String name; private User(String name) { this.name = name; } private void print() { System.out.println(name); } } }
-
将链接传递给
NameКласса::new
构造函数 当您有一个完全满意的现成方法并且希望将其用作回调时,使用方法链接非常方便。在这种情况下,我们不需要使用该方法的代码编写 lambda 表达式,或者直接调用该方法的 lambda 表达式,而只需传递对其的引用。就这样。
匿名类和 lambda 表达式之间有趣的区别
在匿名类中,关键字this
指向该匿名类的对象。如果我们this
在 lambda 中使用它,我们将可以访问框架类的对象。我们实际编写这个表达式的地方。发生这种情况是因为 lambda 表达式在编译时会成为编写它们的类的私有方法。我不建议使用这个“功能”,因为它有副作用,与函数式编程的原则相矛盾。但这种做法非常符合OOP。;)
我从哪里获得信息或还有什么可阅读的
- Oracle 官方网站上的教程。很多,很详细,有例子,但是是英文的。
- 相同的“Oracle”教程,但本章是关于方法引用的。
- 关于 Habré 的一篇关于函数式编程的文章(另一篇文章的翻译)。关于 lambda 的多本书很少,因为它们一般都是关于函数式编程的。
- 对于那些喜欢陷入维基百科的人。
- 当然,我在谷歌上找到了很多东西:)
GO TO FULL VERSION