JavaRush /Java 博客 /Random-ZH /Java 中流行的 lambda 表达式。带有示例和任务。第2部分
Стас Пасинков
第 26 级
Киев

Java 中流行的 lambda 表达式。带有示例和任务。第2部分

已在 Random-ZH 群组中发布
这篇文章适合谁?
  • 对于那些阅读本文第一部分的人;

  • 对于那些认为自己已经很了解 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

使用方法引用的语法

这很简单:
  1. 传递对静态方法的引用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
        }
    }
  2. 使用现有对象传递对非静态方法的引用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
        }
    }
  3. 我们使用实现此类方法的类传递对非静态方法的引用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);
            }
        }
    }
  4. 将链接传递给NameКласса::new
    构造函数 当您有一个完全满意的现成方法并且希望将其用作回调时,使用方法链接非常方便。在这种情况下,我们不需要使用该方法的代码编写 lambda 表达式,或者直接调用该方法的 lambda 表达式,而只需传递对其的引用。就这样。

匿名类和 lambda 表达式之间有趣的区别

在匿名类中,关键字this指向该匿名类的对象。如果我们this在 lambda 中使用它,我们将可以访问框架类的对象。我们实际编写这个表达式的地方。发生这种情况是因为 lambda 表达式在编译时会成为编写它们的类的私有方法。我不建议使用这个“功能”,因为它有副作用,与函数式编程的原则相矛盾。但这种做法非常符合OOP。;)

我从哪里获得信息或还有什么可阅读的

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION