Lambda对比匿名内部类,Lambda是什么,Lambda该怎么用,Lambda使用过程中有什么需要注意的?

由匿名内部类到Lambda

看看遍历一个List的不同方法

public class Main {
  public static List<String> myList;
  static {
    myList = new ArrayList<String>();
    myList.add("AAA");
    myList.add("BBB");
    myList.add("CCC");
    myList.add("DDD");
  }
  public static void main(String[] args) {
    
    //1.直接进行遍历
    for (int i = 0; i < myList.size(); ++i) {
      System.out.println("myList: " + myList.get(i));
    }
      
    for (String tmp : myList) {
      System.out.println("myList: " + tmp);
    }
      
	/**************************************************************/
      
    //2.使用匿名内部类
    myList.forEach(new Consumer<String>() {
      @Override
      public void accept(String tmp) {
        System.out.println("myList: " + tmp);
      }
    });
     
    /**************************************************************/

    //3.使用Lambda
    //lambda完整版:(参数) -> {代码块}
    myList.forEach((String s) -> System.out.println("myList: " + s));
    //由于myList里面的参数肯定是String类型的,所以可以不用申明s为String类型的
    myList.forEach((s) -> System.out.println("myList: " + s));
    //由于只有一个参数,所以还可以不用加括号
    myList.forEach(s -> System.out.println("myList: " + s));
    //由于该参数其实是来自于myList,还可以这么写
    myList.forEach(System.out::println);
  }
}

首先,先看看forEach方法。

通过层层继承,List获得了Iterable接口中的forEach方法。

//List接口继承了Collection接口
public interface List<E> extends Collection<E> {
    ......
}

//Collection接口继承了Iterable接口
public interface Collection<E> extends Iterable<E> {
    ......
}

//在Iterable接口中定义了forEach的默认实现
public interface Iterable<T> {
    ......
	default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

分析forEach方法

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

forEach方法传入的是一个Consumer类型的参数,Consumer是一个接口,接口不能直接实例化,但是接口的引用可以指向实现它的某个子类。也就是说,这里传入的参数实际上是一个继承了Consumer接口的子类的实例,并且这个子类一定实现了Consumer中的抽象方法

Consumer是Java1.8新增的一个接口,里面只有一个抽象方法accept()以及一个拥有默认实现的方法。

@FunctionalInterface
public interface Consumer<T> {
    
    void accept(T t);
    
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

再回到forEach方法,在forEach方法中遍历List,并通过action对象来调用accept方法,action是一个实例,该实例对应的类实现了accept方法,而我们所要做的事情其实就是实现accept方法,在accept方法中指定在遍历List的过程中,我们究竟要做些什么操作。

for (T t : this) {
    action.accept(t);
}

回顾一下,这三种遍历myList的不同方法。

  • 方法一是直接进行遍历,在遍历中操作myList对象。相当于是在方法中直接操作对象。

  • 方法二是使用匿名内部类创建一个Consumer类型的实例,然后在实例中实现accept方法,最后再将这个实例作为一个参数传入forEach方法中。

我们习惯的编程思想是:如果我们要操作某个对象,我们会在代码块里直接操作,然后将其封装成一个方法,然后调用该方法即可完成对该对象的操作

第二种遍历的方法则是一种截然不同的思路。如果我们要操作某个对象,首先会在对象内部预留一个“空的方法”,这个“空的方法”就像是一个占位符一样,在该方法内部并没有指定对象具体需要做什么,当我们想对该对象进行操作的时候,就可以将相应的代码块传入,填在这个"空的方法"里,然后对象调用这个方法就可以完成具体的操作了

可以对比这张图来区分这两种不同的编程思想。
在这里插入图片描述

在遍历myList的第二种方法里,我们使用了匿名内部类传入了一个对象,在该对象内实现了accept方法,accept方法内的代码就是我们希望在遍历myList的时候所要完成的操作但是实际上,我们根本不需要这个对象,我们所需要的仅仅只是这个对象里的方法。但是Java是面向对象编程的,类才是第一等公民,任何操作都得依靠类来完成,我们不能直接调用方法,我们必须创建一个对象,然后通过Object.method()的形式来调用方法,如果是静态方法,我们同样也需要类名.method()的形式来进行调用。由于方法不能单独存在,方法必须封装在一个对象里,因此我们要想往forEach方法中传入代码块,就必须得将方法封装在一个对象里。尽管这样看起来有些臃肿,但是在Java里却不得不这么做。

针对这样的问题,我们再来看遍历myList的第三种方法,这便是Lambda,Lambda是一种编程思想——函数式编程。在函数式编程的语言中,函数才是第一等公民,函数可以单独存在,函数可以当作参数传递,也可以当作返回值。

//2.使用匿名内部类
myList.forEach(new Consumer<String>() {
    @Override
    public void accept(String tmp) {
        System.out.println("myList: " + tmp);
    }
});

//3.使用Lambda
//lambda完整版:(参数) -> {代码块}
myList.forEach(s -> System.out.println("myList: " + s));

对比使用匿名内部类和Lambda表达式,Lambda表达式显然更加简洁,我们不需要创建一个对象,然后将方法封装在对象里来进行传递,我们可以直接将要执行的代码块当作参数传入。

Lambda的使用

单个参数的情况

还是以myList的遍历为例

这里的s其实就是从myList中依次取出的值,由于我们定义myList的时候就指定了它里面所存储的数据类型:List<String> myList。所以这个参数s就不需要定义它的类型,它一定和myList中存储的参数的数据类型一样。

String outside = "myList:";
myList.forEach((s) -> System.out.println(outside + s));

进一步,我们可以将 System.out.println("myList: " + s)封装在一个方法里,假如我们封装在了public void process(String str)这个方法里,且该方法与myList.forEach位于同一个类里,则可以这么调用:

String outside = "myList:";
myList.forEach(s -> process(outside + s));

在上面的例子中,我们将外部的变量outside传入了Lambda表达式中,如果我们的process方法中不需要外部的变量,所需要的参数仅仅来自于myList中。我们还可以这么简写

  1. 假设public void process(String str)位于LambdaTest这个类中,则可以这么调用。
LambdaTest test = new LambdaTest();
myList.forEach(test::process);
  1. 若process是静态方法,还能这么调用
myList.forEach(LambdaTest::process);

两个参数的情况

Map<String, String> myMap = new HashMap<>();
myMap.put("k1", "v1");
myMap.put("k2", "v2");
myMap.put("k3", "v3");
// 两个参数也没问题,把参数用括号扩起来,用逗号分开
myMap.forEach((k, v) -> processTwo(k, v));
// 省略也没问题,这里假设processTwo为LambdaTest中的一个静态方法
myMap.forEach(LambdaTest::processTwo);

使用Stream进行流式处理

以上面的myList为例。

myList.stream().filter(s -> s.length() > 4).map(String::toUpperCase).forEach(System.out::println);

上面这段代码的意思就是:取出myList中的每一个元素,过滤掉length > 4的元素,然后将过滤后的元素转化为全大写,然后依次输出每一个。这一气呵成的处理便是Stream,就像是水流动一样。

collect的作用则是将元素又收集起来,转化为一个Collection集合。

List<String> longgerStrList = myList.stream().filter(s -> s.length() > 4)
    .map(String::toUpperCase).collect(Collectors.toList());

使用Lambda需要注意的点

  • Lambda可以有返回值和异常,得具体看对应的接口中的抽象方法有没有返回值或者是有没有抛出异常。对应到myList.forEach,里面所需要实现的方法其实是Consumer<T>接口中的void accept(T t);方法,该方法并没有返回值,因此Lambda的表达式中就不能有表达式。该方法的签名中也没抛出异常,因此不需要抛出异常。
  • lambda 可以取代只有一个抽象方法的接口,因为在使用Lambda的时候,我们只管往里面传入要执行的代码,并没有指定这段代码是要覆盖接口中的哪个方法,如果接口中有多个抽象方法,则究竟覆盖谁就无法抉择了,因此只能在有一个抽象方法的接口中使用。
Lambda 表达式和匿名内部类都是 Java 中用于创建短小、简洁的代码块,通常用作函数式接口(只有一个抽象方法)的实现。它们的主要区别在于: 1. **语法简洁性**: - **Lambda 表达式**:使用 `->` 运算符将参数列表和方法体直接连接,使得代码更加紧凑。例如,`x -> x * x` 表示接受一个参数并返回其平方的函数。 - **匿名内部类**:需要定义一个完整的类,尽管这个类可能只包含一个方法。例如,`new Runnable() { public void run() { ... } }`。 2. **类型推断**: - **Lambda 表达式**:编译器通常能够推断出 lambda 表达式的类型,特别是对于单方法接口,这提供了更灵活的使用。 - **匿名内部类**:如果方法体中有明确的类型声明,或者方法返回值不是 final,需要显式指定类型。 3. **可读性**: - **Lambda 表达式**:由于其简洁的语法,可能更容易理解。 - **匿名内部类**:对于复杂的类结构或多个方法,匿名内部类可能显得冗长。 4. **作用域和生命周期**: - **Lambda 表达式**:在方法调用结束时自动销毁,除非存储在引用中。 - **匿名内部类**:具有独立的生命周期,可以作为对象实例存在,直到垃圾回收。 5. **应用场景**: - **Lambda 表达式**:适合用于函数式编程风格,如 Java 8 中的 Stream API 和集合框架的操作。 - **匿名内部类**:更适合需要对象实例的场景,比如事件监听或作为构造函数的参数传递。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值