JAVA - Lambda表达式

本文详细介绍了Java接口的定义与实现方式,包括传统的接口定义和Java 8以后的新特性。接着讲解了Lambda表达式的基本概念,作为匿名内部类的简洁表示,用于函数式编程。文中通过实例展示了如何使用Lambda表达式实现函数式接口,如Consumer、Function、Predicate,并介绍了方法引用的使用,使得代码更加简洁高效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Lambda 表达式

在讨论Lambda的相关内容前,我们先回顾一下有关Java(Interface)接口的内容。

一、Java接口的定义方式

传统的Java接口定义

Java接口是一个抽象类型,是抽象方法的集合

  • 抽象方法:只有定义而没有主体的方法。
  • 无属性:不能在接口中定义变量。
public interface MyInterface{
    String getName();
    int getAge();
    void hello();
}

新型的Java接口定义

从Java8开始,接中的定义有了更加丰富的内容

  • 允许在接口内声明静态变量。
  • 可以定义静态(static)方法和默认实现(default)方法。
public interface MyInterface{
    //获得名字
    default String getName(){
        return "Default Name";
    }
    //打招呼
    void hello();
}

二、接口的实现方式

以声明类的方式实现

public class MyImpl implements MyInterface{
    @Override
    public void hello(){
        System.out.println("Hello world");
    }
}
public class MyService{
    /**自我介绍*/    
    public void introduce(MyInterface imy){
        imy.hello();
    }
    
    public void myMethod(){
        //某人介绍自己
        MyInterface someOne = new MyImpl();
        this.introduce(someOne);
    }
}

以匿名内部类的方式实现

public class MyService{
    /**自我介绍*/    
    public void introduce(MyInterface imy){
        imy.hello();
    }
    
    public void myMethod(){
        //张三介绍自己
        MyInterface zhangSan = new MyInterface(){
            @Override
            public void hello(){
                System.out.println("Hello,I'm ZhangSan");
            }
        };
        this.introduce(zhangSan);
        //李四介绍自己
        this.introduce(new MyInterface(){
           @Override
            public void hello(){
                System.out.println("Hi,I'm LiSi");
            }
        });
    }
}

三、Java的Lambda表达式

() -> {}

  • Lambda表达式并不是Java独有的,C#、Ruby、Python等语言都使用了Lambda表达式。
  • 在Java中Lambda表达式也是一种对象,是匿名内部类的快速表达方式,使用时忽略接口名和方法名,只关注方法实现
  • 是Java的函数式编程方式。

面向对象(OOP)编辑和函数式编程(FP)

面向对象编程(关注谁来做)

​ 解决问题的途径是找到一个具有方法的对象,将对象在流程中进行传递(传入或返回),比如上面的例子,想要进行介绍(introduce)必须找到一个具有hello()能力的对象传入。

//传入接口的new实例
this.introduce(new MyInterface(){
        @Override
         public void hello(){
                System.out.println("Hi,I'm LiSi");
            }
        });

函数式编程(关注怎么做)

解决问题的途径是找到可用的方法,把方法(函数)在流程中进行传递(传入或返回),比如上面的例子中,不需要某个特定的实现对象,只要传入方法的实现就行。

//传入方法实现
this.introduce(()->System.out.println("Hello,I'm ZhangSan"));

Lambda表达式的写法

格式:(类型1 变量名2)3 ->4 {函数体}5

  • 类型声明1可省略,编译器会自动推断参数类型
  • ->4是不可省略的
  • 当()3只有一个参数,()3可以省略;如果是无参数的表达式,()3不可省略
  • 当函数体只有一行,{}5可以省略
  • 如果是有返回值的函数,当函数体只有一行,return也可以省略

比如(int a)->{return a*10;}可以写成 a->a*10

以Lambda表达式的方式实现函数式接口

public class MyService{
    /**自我介绍*/    
    public void introduce(MyInterface imy){
        imy.hello();
    }
    
    public void myMethod(){
        //张三介绍自己
        this.introduce(()->System.out.println("Hello,I'm ZhangSan"));
        //李四介绍自己
        this.introduce(()->System.out.println("Hi,I'm LiSi"));
        //啥也不说
        this.introduce(()->{}); //对于空函数体,{}不能省略
    }
}

如何定义一个函数式接口

  • 有且只有一个抽象方法的接口就符合函数式接口的定义

    这并不是说接口只能有一个方法,在第一节提到新型Java接口定义中接口,方法可以通过default或static在接口内进行实现,这样可以使接口内可以有多个方法而只有唯一的抽象方法。

  • 可以使用*@FunctionalInterface*注解验证是否是函数式接口

    当一个接口不符合函数式接口的定义规范时,加上@FunctionalInterface注解编译器会报错,比如下面的情况。

    @FunctionalInterface
    public interface MyFunctionalInterface{
        void toDoSomething();
        String getSomethine();
    }
    

如何定义一个可以接收Lambda表达式的方法

很简单,只要方法的参数是一个函数式接口,这个方法就可以接受Lambda表达式。那么是不是想用Lambda表达式就必需要自定义函数式接口呢,并不用,Java已经给我们准备好了很多的函数式接口供我们直接使用。

四、Java的函数式接口

下面列举几个常用的Java函数式接口

接口名称接口方法说明
Consumer<T>void accept(T t)接受一个输入参数,操作后无返回结果
Function<T,R>R apply(T t)接受一个输入参数,操作后返回一个结果
Predicate<T>boolean test(T t)接受一个输入参数,操作后返回一个布尔值结果
Supplier<T>T get()无参数,操作后返回一个结果

更多的资料点击这里查看

下面看一下这几个接口的应用场景

Consumer接口

Consume<T>接口是一个消费者接口,它接受一个输入对象,但不产生返回对象。

场景:

  • 用户信息类UserInfo保存了用户的个人信息

  • 按需求将List中的元素进行格式化

class UserInfo {
    private String name;
    private String nickname;
    private int age;
    // ...更多属性
}
public void testFunction(){
    List<UserInfo> list = Arrays.asList(
        new UserInfo("张三","Zhang",23),
        new UserInfo("李四","Lee",24),
        new UserInfo("王五","King",25)
    );
    //姓名之后加昵称
    
    //昵称英文大写
}

姓名之后加昵称

//姓名加昵称
void mixName(){
    for(UserInfo u: list){
        u.setName(u.getName()+" "+u.getNickname());
    }
}

昵称英文大写

//昵称英文大写
void upperCaseNickname(){
    for(UserInfo u: list){
      u.setNickname(u.getNickname().toUpperCase());
    }
}

如果还有更多排序需求呢…

下面我们采用Consumer函数接口的方式,List接口的forEach方法是一个参数为Consumer的函数接口

//姓名加昵称
list.forEach(u->u.getName()+" "+u.getNickname());
//昵称英文大写
list.forEach(u->u.getNickname().toUpperCase());

Function接口

Function<T,R>接口是一个类型转换接口,它接受一个输入对象并返回不同类型的对象。

场景

  • 用户信息类UserInfo保存了用户的个人信息
  • 按需求把一个列表(List)中用户的信息提取成另一个单独的列表
class UserInfo {
    private String name;
    private String nickname;
    private int age;
    // ...更多属性
}
public void testFunction(){
    List<UserInfo> list = Arrays.asList(
        new UserInfo("张三","Zhang",23),
        new UserInfo("李四","Lee",24),
        new UserInfo("王五","King",25)
    );
    //提取姓名
    
    //提取昵称
    
    //提取年龄
}

以往的处理方法

如果需要从用户列表中提取所有姓名,那么就要定义方法

private List<String> extractNameList(){
 List<String> strList = new ArrayList<>();
 //查询用户列表 List<UserInfo> list = ...
 for (UserInfo userInfo : list) {
     strList.add(userInfo.getName());
 }
 return strList;
}
List<String> nameList = extractNameList();

如果需要从用户列表中提取所有昵称,那么就再要定义方法

private List<String> extractNicknameList(){
 List<String> strList = new ArrayList<>();
 //查询用户列表 List<UserInfo> list = ...
 for (UserInfo userInfo : list) {
     strList.add(userInfo.getNickname());
 }
 return strList;
}
List<String> nameList = extractNicknameList();

如果需要从用户列表中提取所有年龄,那么就还要定义方法

private List<Integer> extractAgeList(){
 List<String> strList = new ArrayList<>();
 //查询用户列表 List<UserInfo> list = ...
 for (UserInfo userInfo : list) {
     strList.add(userInfo.getAge());
 }
 return strList;
}
List<String> nameList = extractAgeList();

如果还有更多属性需求呢…

当我们采用Function函数接口的解决方式,结合泛型只需要定义一个方法就可以解决

private <T> List<T> extractUserProp(Function<UserInfo,T> func){
    List<T> strList = new ArrayList<>();
 	//查询用户列表 List<UserInfo> list = ...
    for (UserInfo userInfo : list) {
        strList.add(func.apply(userInfo));
    }
    return strList;
}
//提取姓名
List<String> nameList = extractUserProp(u->u.getName());
//提取昵称
List<String> nicknameList = extractUserProp(u->u.getNickname());
//提取年龄
List<Integer> ageList = extractUserProp(u->u.getAge());
//更多的属性也能处理

Predicate接口

Predicate<T>接口是一个判断接口,它接受一个输入对象在执行后返回布尔值(boolean)。

场景

  • 用户信息类UserInfo保存了用户的个人信息
  • 按需求过滤列表中的记录并返回
class UserInfo {
    private String name;
    private String nickname;
    private int age;
    // ...更多属性
}

以往的处理方法

如果我们要按年龄过滤

public List<UserInfo> filterByAge(int min,int max){
    List<UserInfo> list = Arrays.asList(
        new UserInfo("张三","Zhang",23),
        new UserInfo("李四","Lee",24),
        new UserInfo("王五","King",25)
    );
    List<UserInfo> flist = new ArrayList()<>;
    for(UserInfo userinfo: list){
        // 比较年龄是否符合条件
        if(userinfo.getAge()>=min&&userinfo.getAge()<=max){
            flist.add(userinfo);
        }
    }
    return flist;
}

通过传入年龄范围来实现过滤,这种方式是最"偷懒"的实现了

//假如我只想要23岁以上的呢,接口必需要两个值,最大年龄传999吧
List<UserInfo> list = filterByAge(23,999);
//假如我只想要60岁以下的呢,最小年龄传0吧,18似乎也行
List<UserInfo> list = filterByAge(0,60);

能用,但不优雅

下面我们使用Predicate接口的解决方案

private List<UserInfo> filterBy(Predicate<UserInfo> p){
    List<UserInfo> list = Arrays.asList(
        new UserInfo("张三","Zhang",23),
        new UserInfo("李四","Lee",24),
        new UserInfo("王五","King",25)
    );
    List<UserInfo> flist = new ArrayList<>();
    for (UserInfo userInfo : list) {
        if(p.test(userInfo)){
            flist.add(userInfo);
        }
    }
    return flist;
}
//23岁以上的人
List<UserInfo> list = filterBy((u)->u.getAge()>23);
//60岁以下的
List<UserInfo> list = filterBy((u)->u.getAge()<60);
//姓张的
List<UserInfo> list = filterBy((u)->u.getName().startsWith("张"));
//更多你想要的条件都可以
....

五、方法引用(::)

作用

将已定义好的方法引用为Lambda表达式。

用法

前面用到的Lambda表达式都是定义匿名内部类,Java对于类中已经声明的方法也可以引用为Lambda表达式,方法是通过双冒号(::)操作符。双冒号操作符的用法有以下几种

  • 静态方法引用(类名::方法名)

比如前面Predicate接口的例子中,如果我们之前已经有一个业务方法来判断是否大于60岁

public class UserInfoUtil{
   public static boolean isLargeThan60(UserInfo u){
        return u.getAge()>60;
    } 
}

那么我们就可以直接使用这个方法

List<UserInfo> list = filterBy(UserInfoUtil::isLargeThan60);
  • 实例方法引用(变量名::方法名)
public class UserInfoServiceImpl{
   private boolean isLargeThan60(UserInfo u){
       return u.getAge()>60;
   }
    
    private List<UserInfo> filterBy(Predicate<UserInfo> p){
        List<UserInfo> list = Arrays.asList(
                new UserInfo("张三","Zhang",23),
                new UserInfo("李四","Lee",24),
                new UserInfo("王五","King",25)
        );
        List<UserInfo> strList = new ArrayList<>();
        for (UserInfo userInfo : list) {
            if(p.test(userInfo)){
                strList.add(userInfo);
            }
        }
        return strList;
    }
    
   public List<UserInfo> findUser(){
       List<UserInfo> list = filterBy(this::isLargeThan60);
       //List<UserInfo> list = filterBy(new UserInfoServiceImpl()::isLargeThan60);
       return list;
   } 
}
  • 特定类型任意对象实例方法引用

解释:

  1. 函数接口抽象方法的参数比引用的方法参数多一个
  2. 函数接口抽象方法的第一个参数是引用方法的对象实例

Function接口的例子

private <T> List<T> extractUserProp(Function<UserInfo,T> func){
    //实现代码
}
//原来的写法
List<String> nameList = extractUserProp(u->u.getName()); 
/**改用双冒号的写法*/
List<String> nameList = extractUserProp(UserInfo::getName);

用到了UserInfo.getName(),getName()方法并不是一个静态方法,为什么可以用 类名::方法名 的方式引用

/**
*引用方法UserInfo.getName()是一个Supplier接口,方法:T get() 无参数
*extractUserProp的参数是一个Function接口,方法:R apply(T t) 比get方法多一个参数
*函数定义u->u.getName() 第一个参数u是UserInfo的实例
*/
//UserInfo::getName写作Funciton类型与u->u.getName()等价
Function<UserInfo, String> getName = UserInfo::getName;
Function<UserInfo, String> getName2 = u->u.getName();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值