Java_lambda表达式

1. lambda表达式

1.0 引出

我们平常会使用Arrays.sort方法对数组进行排序,如果我们想自定义排序,就会使用sort的一个重载方法,a是待排序的数组,c是一个比较器,可以说是一个实现了Comparator接口的类的对象。

public static <T> void sort(T[] a, Comparator<? super T> c);

下面以按照字符串的长度从小到大排序为例看一下这个方法的使用:

首先定义Comparator的实现类:

class StringLengthCompare implements Comparator<String>{        
    @Override    
    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
}

然后使用sort方法进行排序

@Test
public void lambdaTest(){
    String[] strs = {"Alice", "Black", "Cindy", "Mr Green", "Mike", "Zip"};
    //实例化一个Comparator对象    
    Comparator<String> comparator = new StringLengthCompare();
    Arrays.sort(strs, comparator);
    System.out.println(Arrays.toString(strs));
}

排序结果:

在这里插入图片描述

考虑上面的做法,功能可以实现,没问题,但是如果想要用多种方式排序,那么就要写多个实现类,这样的做法让代码看起来不是那么简洁,或许可以说,我们可以使用匿名类实现这个接口,但这样又会有代码难以复用的问题,所以我们引入了lambda表达式来解决这个问题,做法如下,这样看起来比上面的做法简洁很多:

@Test
public void lambdaTest(){
    String[] strs = {"Alice", "Black", "Cindy", "Mr Green", "Mike", "Zip"};
    //lambda表达式
    Comparator<String> comparator = (o1, o2) ->  o1.length() - o2.length();
    Arrays.sort(strs, comparator);   
    System.out.println(Arrays.toString(strs));
}

下面我们来详细介绍一下lambda表达式

1.1 简介

  1. 定义:lambda表达式是一个可执行的代码块,可以重复执行一次或多次

  2. 作用

    1. 使代码更简洁,更易读
    2. 使代码延迟执行(deferred execution),这个作用将在之后进行介绍
  3. 组成

    1. 参数
    2. 一个代码块
    3. 自由变量的使用,在1.2.2中说明

1.2 语法

1.2.1 基本组成

  • (参数列表) -> 代码块
//一个字符串比较器,按照字符串的长度比较大小,
Comparator<String> comparator = (String o1, String o2) -> {
    return o1.length() - o2.length();
};
  • 如果编译器可以推导出参数的类型,则参数列表中可以不加类型
//下面这个比较器可以推导出o1, o2是字符串类型,可省略,Comparator<T> -> Comparator<String>
Comparator<String> comparator = (o1, o2) -> {
    return o1.length() - o2.length();
};
  • 如果代码块中只有一个语句,则可以省略“{}”
Comparator<String> comparator = (o1, o2) -> o1.length() - o2.length();

说明:如果加上{},若有返回值,则必须写return xxx;如果写成上面这种形式,lambda表达式会根据上下文推导出这个语句是否作为返回值。

Comparable的原型方法如下,返回值为int,所以会推出o1.length() - o2.length()的结果作为返回值返回

int compare(T o1, T o2);
  • 如果参数列表只有一个参数,则可以省略()
ActionListener listen = e ->
System.out.println(e);
  • 如果无参数,则必须写上()
() -> {
    for(int i = 0; i < n; ++ i){
        System.out.println(i);    
    }
}

1.2.2 自由变量的使用

  1. 定义:自由变量是非参数而不在代码块中定义的变量【这两种变量该咋用咋用】,如类的成员变量,方法的参数等
  2. 使用限制:在lambda中使用的自由变量不可以被修改,即初始化完成后不可以有任何其它的修改操作,这种变量有个新的名字叫做:事实最终变量(effectively final)
  3. 举例说明:

两处Error的地方都会出现*Variable used in lambda expression should be final or effectively final*的错误

@Test
public void lambdaTest(){
    //初始化一个时间
    LocalDate date = LocalDate.of(2004, 10, 1);
    //Objects.requireNonNullElseGet这个方法的作用是,如果第一个参数不为null,则返回第一个参数,如果第一个参数是空对象,则返回默认的值,即第二个参数返回或表示的对象
    LocalDate outPutDate = Objects.requireNonNullElseGet(null, () ->{
        //date = date.plusDays(10);//Error         
        return date;
    });
    //date = date.plusDays(10); //Error   
    System.out.println(outPutDate);
}

为什么要这些使用限制:并发执行时不安全

1.3 函数式接口

1.3.1 理解

  1. 定义:只有一个抽象方法的接口,需要这种接口的对象时,可以提供一个lambda表达式,这种接口叫做函数式接口

  2. 为什么只有一个抽象方法

    说一下我个人的理解:lambda表达式相当于是对一个接口中的抽象方法的实现,方法名和参数列表唯一标识了一个方法,而lambda表达式只有参数列表无方法名,如果接口里面有多个抽象方法且参数列表相同,则编译器不知道是实现的哪一个方法,导致错误。

    经过测试,如果接口中有两个方法,则会出现*Multiple non-overriding abstract methods found in interface xxx的错误*

  3. 为什么强调抽象方法

    因为lambda可以看成是对抽象方法的实现方法,接口可以对方法提供默认的实现方式。

  4. lambda表达式做的事是转化为函数式接口,不能把lambda表达式赋值给Object类型的变量

1.4 方法引用

1.4.1 概念

  1. 定义:方法引用指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法
  2. 要求:只有当lambda表达式的体只调用一个方法不做其它操作时,才能把lambda表达式写成方法引用
  3. 方法引用不能独立存在,总是会转换为函数式接口的实例

1.4.2 用法

从以下可以看出,方法引用可以简化lambda表达式的书写,可以增强代码复用,如比较器调用compareToIgnoreCase方法

  1. object::instanceMethod,等价于向lambda表达式传递参数的lambda表达式
    //这个类可以设置定时任务,每隔1000秒执行一次System.out::println
    //方法引用System.out::println <=> e -> System.out.println(e)
    Timer timer = new Timer(1000, System.out::println);
    
  2. Class::instanceMethod,这种第一个参数会成为方法的隐式参数
    //比较器
    //String::compareToIgnoreCase <=> (o1, o2) -> o1.compareToIgnoreCase(o2)
    Arrays.sort(strs, String::compareToIgnoreCase);
    
  3. Class::staticMethod,所有的参数都传到静态方法
    //Integer::valueOf <=> x -> Integer.valueOf(x)

1.4.3 构造器引用

构造器引用与方法引用类似,只不过方法是new,在使用stream、collect时可能会使用。

1.5 lambda表达式延迟执行

1.5.1 理解

  1. 延迟执行就是不立刻执行,如果想立刻执行,直接写出来即可,无需用lambda表达式包装
  2. 为什么要延迟执行
  • 特定情况下执行代码,非必要不执行,减少资源浪费
  • 多次执行代码
  • 在一个单独的线程中运行代码

1.5.2 举例

@Test    
public void lambdaTest1(){
    LocalDate day = LocalDate.now();
//        LocalDate day = null;       
    Supplier<? extends LocalDate> supplier = () -> {
        System.out.println("supplier 执行了");
        return LocalDate.of(1970, 1, 1);
    };
    LocalDate localDate = Objects.requireNonNullElseGet(day, supplier);
    System.out.println("localDate = " + localDate);
}
@Test   
public void lambdaTest6(){
    LocalDate day = LocalDate.now();
    //        LocalDate day = null;        
    LocalDate defaultDate = LocalDate.of(1970, 1, 1);
    LocalDate localDate = Objects.requireNonNullElse(day, defaultDate);
    System.out.println("localDate = " + localDate);
    System.out.println("defaultDate = " + defaultDate);
 }

对于lambdaTest1,day不是null,结果如下
在这里插入图片描述

day是null,结果如下:
在这里插入图片描述

对于lambdaTest2,不管day是不是null,结果如下:
在这里插入图片描述

在1中,如果用不到默认日期,则不会生成一个日期为1970-01-01的对象,用到了则加载,2中只要方法被调用则会造一个1970-01-01的对象,如果实际情况中用到默认情况的次数很少,则可以使用lambda表达式的方式提供,不会产生无用的实例对象,减少资源浪费。

1.6 自定义处理lambda表达式

1.6.1 使用Java提供的函数式接口

class LambdaExample{
    /**     
     * 把list中的奇数变成其相反数     
     * 模仿定义     
     * boolean removeIf(Predicate<? super E> filter)     
     * @param list 待处理的集合     
     * @param filter 检测是否是奇数,如果是,则返回true     
     */    
     public void usePredicate(List<Integer> list, Predicate<Integer> filter){
        System.out.println("处理前的list = " + list);
        for (int i = 0; i < list.size(); ++ i) {
            if(filter.test(list.get(i))){
                list.set(i, -list.get(i));
            }
        }
        System.out.println("处理后的list = " + list);
    }
}

@Test
public void testMakeLambda1(){
    List<Integer> list = new ArrayList<>();
    Collections.addAll(list, 1, 2, 3, 4, 5, 6);
    LambdaExample lambdaExample = new LambdaExample();
    lambdaExample.usePredicate(list, integer -> integer % 2 == 1);
}

结果:
在这里插入图片描述

1.6.2 使用自定义的函数式接口

interface LExample{
    void accept(int value);
}

class LambdaExample{
    public void repeat(int n, LExample action){
        System.out.println("action = " + action);
        for (int i = 0; i < n; ++i){
            action.accept(2);
        }
    }
}

//延迟加载->重复执行一段代码
@Test
public void testMakeLambda(){
    LambdaExample lambdaExample = new LambdaExample();
    lambdaExample.repeat(3, e -> {
        System.out.println("value = " + e);
    });
}

结果:

在这里插入图片描述

1.7 总结

  • lambda表达式就是一个可执行的代码块,可以重复执行一次或多次,可以让代码看起来更简洁,实现延迟加载。
  • lambda表达式可以转换成函数式接口,不可以赋值给Object。
  • lambda表达式可以看成是对一个接口中唯一的抽象方法的实现,作为参数传递到用到这个接口的方法中。当这个方法使用接口中的方法时,回调lambda表达式执行其中的代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值