欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

《Java核心技术卷I》lambda表达式 笔记

程序员文章站 2023-01-29 09:15:05
写在最前:本笔记全程参考《Java核心技术卷I》,添加了一些个人的思考和整理lambda表达式Lambda 表达式(lambda expression)是一个匿名函数,即没有函数名的函数。不要将其看成对象(原因) 1. 为什么引入lambda表达式lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。其作用是,将某段代码块传递到某个对象(如一个定时器,或者是Arrays的sort方法),这个代码块将在某个时间调用。比如,可以设计一个比较器(Comparator),通过比较器的....

lambda表达式

Lambda 表达式(lambda expression)是一个匿名函数,即没有函数名的函数。不要将其看成对象(原因)

1. 为什么引入lambda表达式

lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。

其作用是,将某段代码块传递到某个对象(如一个定时器,或者是Arrays的sort方法),这个代码块将在某个时间调用。

一般来说,在java中传递代码块的方式是:设计一个对象,并将代码块封装成该对象的一个方法。通过对象传递代码块

2. lambda表达式语法

基本语法

以Comparator为例:此处提供一个比较器的实现

class LengthComparator implements Comparator<String> {
    public int compare(String first, String second) {
        return first.length() - second.length();
    }
}

Arrays.sort(strings, new LengthComparator);

可以看到,核心的代码就是first.length() - second.length(),那么这里的first和``second是什么?它们都是String,java是强类型语言,我们需要声明firstsecond的类型为String:

(String first, String second) -> first.length() - second.length()

这就是一个lambda表达式,它就是一个代码块以及必须传入的代码的变量规范。

可以将其赋值给Comparator对象,因为Comparator是一个函数式接口

Comparator<String> comp = 
    (String first, String second) -> first.length() - second.length();

其他要求

  1. 如果要传入的代码块有多行,可以使用{}

  2. 如果可以推导出lambda表达式的参数的数据类型,那么可以省略这些数据类型:

    (first, second) -> first.length() - second.length()
    
  3. 即使没有参数需要传入,也需要提供空括号(就像无参方法一样):

    () -> {
    	for (int i =0; i < 10; i++) {
            System.out.println(i);
        }
    }
    
  4. 如果方法只有一个参数,且这个参数的类型可以推导得出,那么可以省略括号

    ActionListener listener = event -> {
    	System.out.println("loger");
    }
    // 相当于 (event) -> { ... }
    
  5. 无需指定表达式的返回值类型

  6. 入股一个lambda表达式只在某些分支返回一个值,而在另外的分支不返回值,那这是不合法的。

  7. lambda表达式只能引用标记了final的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
    如果lambda表达式引用了不为final的局部变量,那么该变量必须不可被后面的代码修改(即隐性的具有 final的语义)

  8. 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量

3. lambda使用示例

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

没有使用Lambda的老方法:

button.addActionListener(new ActionListener(){ // 匿名内部类
    public void actionPerformed(ActionEvent actionEvent){
        System.out.println("Action detected");
    }
});

Runnable runnable1 = new Runnable(){
    @Override
    public void run(){
        System.out.println("Running without Lambda");
    }
};

使用后:

button.addActionListener( actionEvent -> { // lambda表达式
    System.out.println("Action detected");
});

Runnable runnable2 = () -> System.out.println("Running from Lambda");

4. 函数式接口

java有很多封装代码块的接口,如ActionListener和Comparator,这些接口与lambda表达式是相兼容的。

对于只有一个抽象方法的接口,需要这个接口的对象时,可以提供一个lambda表达式来代替。这种接口被称为函数式接口 (functional interface)

也就是说,lambda表达式可以赋值给只有一个抽象方法的接口。由于Object不是函数式接口,所以不能将lambda表达式赋值给Object类型的变量。由此可见lambda表达式不是对象,它是一个匿名函数。

Arrays.sort方法的第一个参数是数组,第二个参数就是Comparator,可以提供一个lambda表达式代替:

Comparator<String> comp = 
    (String first, String second) -> first.length() - second.length();

Arrays.sort(strings, comp);
// 等价于
Arrays.sort(strings, 
            (first, second) -> first.length() - second.length());

在底层,sort方法会接收实现了Comparator<String>的某个类的对象,在这个对象上调用compare方法来指向这个lambda表达式的代码块。

ArrayList类有一个removeIf方法,它的参数就是一个Predicate接口,这个接口专门用来传递lambda表达式:

public interface Predicate<T> {
	boolean test(T t);
}
list.removeIf(e -> e == null); // 删除list中所有null值

5. 变量作用域

事实最终变量

lambda表达式只能引用标记了final的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。如果lambda表达式引用了不为final的局部变量,那么该变量必须不可被后面的代码修改(即隐性的具有 final的语义)

这是为啥捏?你看看下面这段代码:

public static void repeatMessage(String text, int delay) {
    ActionListener listener = event -> {
        Systen.out.println(text); // 注意这里
    }
    new Timer(delay, listener).start();
}

仔细观察,lambda表达式可能会在repeatMessage方法执行很久之后才开始运行,而这时这个参数变量已经不存在了(因为是形参,方法执行完毕后被回收)。而如果想要立即执行代码,则没有必要封装为lambda表达式,直接写入代码就好了鸭

在lambda表达式中,有以下3个部分:

  1. 一个代码块
  2. 参数
  3. *变量的值。这里指不是lambda的参数且不在lambda中定义的变量

所以,在java中,lambda表达式只能引用值不会改变的变量,即*变量的值不能改变。

原因是:如果在lambda表达式中更改变量,并发执行多个动作时就会不安全。

实际上,lambda表达式中捕获的变量必须是事实最终变量(effectively final)。这是指:这个变量初始化之后就不会再为它赋新值。

public static void countDown(int start, int delay) {
    int num = 1;
    num++;
    for (int i = 0; i < 10; i++) {
        
        int finalI = i;
        
        ActionListener listener = e -> {
            start--; // 出错 
            // Variable used in lambda expression should be final or effectively final
            
            num++; // 可行

            System.out.println(i); // 出错,i不是事实最终变量
            System.out.println(finalI); // 可行, finalI是事实最终变量

        };
    }
}

lambda的作用域

lambda表达式的体(代码块)与被嵌套的块具有相同的作用域。所以在lambda表达式当中不允许声明一个与局部变量同名的参数或者局部变量

在lambda表达式中使用this关键字,会调用当前域的对象,而不是lambda代表的对象。

对于下面的代码,lambda表达式被嵌套在init方法的域中,与出现在该方法的其他位置一样this代表调用init方法的Application实例对象:

public class Application {
	public void init() {
        ActionListener listener = e -> {
            // 调用Application的toString方法,而不是ActionListener的toString方法
            System.out.println(this.toString());
        }
    }
}

6. 处理lambda表达式

lambda表达式的特点是延迟执行。毕竟如果要立即执行,完全可以直接写入代码而不用写成lambda表达式中。

用到lambda表达式的地方有:

  1. 在单独一个线程中运行代码
  2. 多次运行代码
  3. 在算法适当的位置运行代码(排序)
  4. 发送某种情况时运行代码(监听器)
  5. 只在必要时运行代码

本文地址:https://blog.csdn.net/mrhanzhou5273/article/details/114337748

相关标签: Java lambda