我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java8中Lambda表达式的理解与应用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java8中Lambda表达式的理解与应用

简介

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

下面贴个对比代码:

// Java8之前:旧的写法
Runnable runnable = new Runnable() {
 @Override
 public void run() {
   System.out.println("old run");
}
};
Thread t = new Thread(runnable);

// Java8之后:新的写法
Runnable runnable1 = ()->{
 System.out.println("lambda run");
};
Thread t1 = new Thread(runnable1);

可以看到,有了lambda,代码变得简洁多了

你可以把lambda当作一个语法糖

下面让我们一起来探索lambda的美好世界吧

正文

1. lambda的语法

下面分别说下语法中的三个组成部分

  • 参数: ( Dog dog )

    • 参数类型可省略(当编译器可以自动推导时),比如Comparator<String> comparatorTest = (a, b)->a.length()-b.length();,可以推导出a,b都为String

    • 当参数类型可省略,且只有一个参数时,括弧也可以省略(但是个人习惯保留)

  • 符号:->

  • 主体{ System.out.println("javalover"); }

    • 如果是一条语句,则需要加大括号和分号{;}(比如上图所示)

    • 如果是一个表达式,则直接写,啥也不加(比如a.length()- b.length()

2. 为啥引入lambda

为了简化代码

因为Java是面向对象语言,所以在lambda出现之前,我们需要先构造一个对象,然后在对象的方法中实现具体的内容,再把构造的对象传递给某个对象或方法

但是有了lambda以后,我们可以直接将代码块传递给对象或方法

现在再回头看下开头的例子

可以看到,用了lambda表达式后,少了很多模板代码,只剩下一个代码块(最核心的部分)

3. 什么是函数式接口

就是只定义了一个抽象方法的接口

  • 正例:有多个默认方法,但是如果只有一个抽象方法,那它就是函数式接口,示例代码如下

@FunctionalInterface
public interface FunctionInterfaceDemo {
   void abstractFun();
   default void fun1(){
       System.out.println("fun1");    
  }
   default void fun2(){
       System.out.println("fun2");
  }  
}

这里的注解@FunctionalInterface可以省略,但是建议加上,就是为了告诉编译器,这是一个函数式接口,此时如果该接口有多个抽象方法,那么编译器就会报错

  • 反例:比如A extends B,A和B各有一个抽象方法,那么A就不是函数式接口,示例代码如下

// 编译器会报错,Multiple non-overriding abstract methods found in XXX
@FunctionalInterface
public interface NoFunctionInterfaceDemo extends FunctionInterfaceDemo{
 void abstractFun2();
}

上面的父接口FunctionInterfaceDemo中已经有了一个抽象方法,此时NoFunctionInterfaceDemo又定义了一个抽象方法,结果编译器就提示了:存在多个抽象方法

在Java8之前,其实我们已经接触过函数式接口

比如Runnable 和 Comparable

只是没有注解@FunctionalInterface。

那这个函数式接口要怎么用呢?

配合lambda食用,效果最佳(就是把lambda传递给函数式接口),示例代码如下:

new Thread(() -> System.out.println("run")).start();

其中用到的函数式接口是Runnable

4. 什么是行为参数化

就是把行为定义成参数,行为就是函数式接口

类似泛型中的类型参数化,类型参数化是把类型定义成参数

行为参数化,通俗点来说:

  • 就是用函数式接口形参

  • 然后传入接口的各种实现内容(即lambda表达式)作为实参

  • 最后在lambda内实现各种行为(好像又回到多态的那一节了?这也是为啥多态是Java的三大特性的原因之一,应用太广泛了)

这样来看的话,行为参数化和设计模式中的策略模式有点像了(后面章节会分别讲常用的几种设计模式)

下面我们手写一个函数式接口来加深理解吧

5. 手写一个函数式接口

下面我们循序渐进,先从简单的需求开始

  • 第一步:比如我们想要读取某个文件,那可以有如下方法:

public static String processFile() throws IOException {
   // Java7新增的语法,try(){},可自动关闭资源,减少了代码的臃肿
   try( BufferedReader bufferedReader =
       new BufferedReader(new  FileReader("./test.txt"))){
       return bufferedReader.readLine();
  }
}

可以看到,核心的行为动作就是 return bufferedReader.readLine();,表示读取第一行的数据并返回

那如果我们想要读取两行呢?三行?

  • 第二步:这时就需要用到上面的函数式接口了,下面就是我们自己编写的函数式接口

@FunctionalInterface
interface FileReadInterface{
// 这里接受一个BufferedReader对象,返回一个String对象
   String process(BufferedReader reader) throws IOException;
}

可以看到,只有一个抽象方法process(),它就是用来处理第一步中的核心动作(读取文件内容)

至于想读取多少内容,那就需要我们在lambda表达式中定义了

  • 第三步:接下来我们定义多个lambda表达式,用来传递函数式接口,其中每个lambda表达式就代表了一种不同的行为,代码如下:

// 读取一行
FileReadInterface fileReadInterface = reader -> reader.readLine();
// 读取两行
FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine();
  • 第四步:我们需要修改第一步的processFile(),让其接受一个函数式接口,并调用其中的抽象方法,代码如下:

// 参数为第二步我们自己手写的函数式接口
public static String processFile(FileReadInterface fileReadInterface) throws IOException {
       try( BufferedReader bufferedReader =
                new BufferedReader(new FileReader("./test.txt"))){
// 这里我们不再自己定义行为,而是交给函数式接口的抽象方法来处理,然后通过lambda表达式的传入来实现多个行为
         return fileReadInterface.process(bufferedReader);
      }
  }
  • 第五步:拼接后,完整代码如下:


public class FileReaderDemo {
   public static void main(String[] args) throws IOException {
// 第三步:
    // lambda表达式1 传给 函数式接口:只读取一行
    FileReadInterface fileReadInterface = reader -> reader.readLine();
// lambda表达式2 传给 函数式接口:只读取两行
    FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine();
    // 最后一步: 不同的函数式接口的实现,表现出不同的行为
       String str1 = processFile(fileReadInterface);
       String str2 = processFile(fileReadInterface2);
       System.out.println(str1);
       System.out.println(str2);
  }
// 第四步: 读取文件方法,接受函数式接口作为参数
   public static String processFile(FileReadInterface fileReadInterface) throws IOException {
       try( BufferedReader bufferedReader =
                new BufferedReader(new FileReader("./test.txt"))){
// 调用函数式接口中的抽象方法来处理数据
         return fileReadInterface.process(bufferedReader);
      }
  }
// 第一步:
 public static String processFile() throws IOException {
       try( BufferedReader bufferedReader =
                new BufferedReader(new FileReader("./test.txt"))){
         return bufferReader.readLine();
      }
  }


}

// 第二步: 我们手写的函数式接口
@FunctionalInterface
interface FileReadInterface{
   String process(BufferedReader reader) throws IOException;
}

其实你会发现,我们手写的这个函数式接口,其实就是Function<T>去除泛型化后的接口,如下所示:

@FunctionalInterface
public interface Function<T, R> {
// 都是接受一个参数,返回另一个参数
 R apply(T t);
}

下面我们列出Java中常用的一些函数式接口,你会发现自带的已经够用了,基本不会需要我们自己去写

这里的手写只是为了自己实现一遍,可以加深理解程度

6. 常用的函数式接口

7. 什么是方法引用

我们先看一个例子

前面我们写的lambda表达式,其实还可以简化,比如

// 简化前
Function<Cat, Integer> function = c->c.getAge();
// 简化后
Function<Cat, Integer> function2 = Cat::getAge;

其中简化后的Cat::getAge,我们就叫做方法引用

方法引用就是引用类或对象的方法

下面我们列出方法引用的三种情况:

  • Object::instanceMethod(对象的实例方法)

  • Class::staticMethod(类的静态方法)

  • Class::instanceMethod(类的实例方法)

像我们上面举的例子就是第三种:类的实例方法

下面我们用代码演示上面的三种方法:

public class ReferenceDemo {
   public static void main(String[] args) {
       // 第一种:引用对象的实例方法
       Cat cat = new Cat(1);
       Function<Cat, Integer> methodRef1 = cat::getSum;
       // 第二种:引用类的静态方法
       Supplier<Integer> methodRef2 = Cat::getAverageAge;
       // 第三种:引用类的实例方法
       Function<Cat, Integer> methodRef3 = Cat::getAge;
  }
}
class Cat {
   int age;

   public Cat(int age) {
       this.age = age;
  }

   // 获取猫的平均年龄
   public static int getAverageAge(){
       return 15;
  }
   // 获取两只猫的年龄总和
   public int getSum(Cat cat){
       return cat.getAge() + this.getAge();
  }

   public int getAge() {
       return age;
  }    public void setAge(int age) {
       this.age = age;
  }
}

为啥要用这个方法引用呢?

方法引用好比lambda表达式的语法糖,语法更加简洁,清晰

一看就知道是调用哪个类或对象的哪个方法

8. 什么是构造引用

上面介绍了方法引用,就是直接引用某个方法

这里的构造引用同理可得,就是引用某个类的构造方法

构造引用的表达式为:Class::new,仅此一种

如果你有多个构造函数,那编译器会自己进行推断参数(你看看,多好,多简洁)

比如下面的代码:

// 这里调用 new Cat()
Supplier<Cat> constructRef1 = Cat::new;
// 这里调用 new Cat(Integer)
Function<Integer, Cat> constructRef2 = Cat::new;

9. lambda表达式中引入外部变量的限制

要求引入lambda表达式中的变量,必须是最终变量,即该变量不会再被修改

比如下面的代码:


public static void main(String[] args) {
 String str = "javalover.cc";
 Runnable runnable = ()->{
   str = "1";// 这里会报错,因为修改了str引用的指向
   System.out.println(str);
}
}

可以看到,lambda表达式引用了外面的str引用,但是又在表达式内部做了修改,结果就报错了

为啥要有这个限制呢?

为了线程安全,因为lambda表达式有一个好处就是只在需要的时候才会执行,而不是调用后立马执行

这样就会存在多个线程同时执行的并发问题

所以Java就从根源上解决:不让变量被修改,都是只读的

那你可能好奇,我不把str的修改代码放到表达式内部可以吗?

也不行,道理是一样的,只要lambda有用到这个变量,那这个变量不管是在哪里被修改,都是不允许的

不然的话,我这边先执行了一次lambda表达式,结果你就改了变量值,那我第二次执行lambda,不就乱了吗

10. lambda的组合操作

最后是lambda的必杀技:组合操作

在这里叫组合或者复合都可以

概述:组合操作就是先用一个lambda表达式,然后再在后面组合另一个lambda表达式,然后再在后面组合另另一个lambda表达式,然后。。。有点像是链式操作

学过JS的都知道Promise,里面的链式操作就和这里的组合操作很像

用过Lombok的朋友,应该很熟悉@Builder注解,其实就是构造者模式

下面我们用代码演示下组合操作:


// 重点代码
public class ComposeDemo {
   public static void main(String[] args) {
       List<Dog> list = Arrays.asList(new Dog(1,2), new Dog(1, 1));
       // 1. 先按年龄排序(默认递增)
    // Dog::getAge, 上面介绍的方法引用
    // comparingInt, 是Comparator的一个静态方法,返回Comparator<T>
    Comparator<Dog> comparableAge = Comparator.comparingInt(Dog::getAge);
       // 2. 如果有相同的年龄,则年龄相同的再按体重排序(如果年龄已经比较出大小,则下面的体重就不会再去比较)
       Comparator<Dog> comparableWeight = Comparator.comparingInt(Dog::getWeight);;
       // 3. 调用list对象的sort方法排序,参数是Comparator<? super Dog>
       list.sort(comparableAge.thenComparing(comparableWeight));
       System.out.println(list);
  }
}
// 非重点代码
class Dog{
   private int age;
   private int weight;

   public Dog(int age, int weight) {
       this.age = age;
       this.weight = weight;
  }

   public int getAge() {
       return age;
  }

   public void setAge(int age) {
       this.age = age;
  }

   public int getWeight() {
       return weight;
  }

   public void setWeight(int weight) {
       this.weight = weight;
  }

   @Override
   public String toString() {
       return "Dog{" +
               "age=" + age +
               ", weight=" + weight +
               '}';
  }
}

输出:[Dog{age=1, weight=1}, Dog{age=1, weight=2}]

比较的流程如下所示:

总结

  • lambda的语法:  参数+符合+表达式或语句,比如(a,b)->{System.out.println("javalover.cc");}

  • 函数式接口:只有一个抽象方法,最好加@FunctionalInterface,这样编译器可及时发现错误,javadoc也说明这是一个函数式接口(可读性)

  • 行为参数化:就是函数式接口作为参数,然后再将lambda表达式传给函数式接口,通过不同的lambda内容实现不同的行为

  • 方法引用:lambda的语法糖,总共有三种:

    • Class::instanceMethod(类的实例方法)

    • Object::instanceMethod(对象的实例方法)

    • Class::staticMethod(类的静态方法)

  • 构造引用:就一种,编译器自己可判断是哪个构造函数,语法为Class::new

  • 在lambda中引入外部变量,必须保证这个变量是最终变量,即不再被修改

  • lambda的组合操作,就是链式操作,组合是通过函数式接口的静态方法来组合(静态方法会返回另一个函数式接口的对象)

比如list.sort(comparableAge.thenComparing(comparableWeight));

到此这篇关于Java8中Lambda表达式的理解与应用的文章就介绍到这了,更多相关Java8中Lambda表达式内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java8中Lambda表达式的理解与应用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java8中函数式接口与Lambda表达式的特性

今天就跟大家聊聊有关Java8中函数式接口与Lambda表达式的特性,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。什么是Lambda表达式,java8为什么使用Lambda表达式?“
2023-05-31

lambda表达式怎么在Java8中使用

lambda表达式怎么在Java8中使用?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。lambda 表达式的语法lambda 表达式由参数,->,以及函数体三部分组成。其实函数
2023-05-31

如何在Java8中使用lambda表达式

这篇文章给大家介绍如何在Java8中使用lambda表达式,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。 0. 函数式编程 函数式编程(Functional Programming)属于编程范式(Programm
2023-06-14

怎么在Java8中使用Lambda表达式

怎么在Java8中使用Lambda表达式?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. lambda表达式介绍lambda表达式是Java8提供的新特性之一,也可以称之为闭
2023-06-14

lambda表达式在java8中的使用方法

这篇文章给大家介绍lambda表达式在java8中的使用方法,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。定义 TantanitReader:public class TantanitReader { private
2023-05-31

lambda表达式如何在JAVA8中使用

这期内容当中小编将会给大家带来有关lambda表达式如何在JAVA8中使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一:什么是 Stream?Stream(流)是一个来自数据源的元素队列并支持聚合操作
2023-06-15

深入浅析Java8中的lambda表达式

深入浅析Java8中的lambda表达式?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。函数式接口函数式接口(functional interface 也叫功能性接口,其实是同一
2023-05-31

lambda表达式在Java8中的作用有哪些

本篇文章给大家分享的是有关lambda表达式在Java8中的作用有哪些,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。1.关于JSR335JSR是Java Specificati
2023-05-31

java8中lambda表达式的语法是什么

这篇文章主要介绍“java8中lambda表达式的语法是什么”,在日常操作中,相信很多人在java8中lambda表达式的语法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java8中lambda表达式
2023-07-02

Java8中Lambda表达式与函数式接口的示例分析

这篇文章将为大家详细讲解有关Java8中Lambda表达式与函数式接口的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Java8被称作Java史上变化最大的一个版本。其中包含很多重要的新特性,最核
2023-05-30

如何正确的在Java8中使用lambda表达式

如何正确的在Java8中使用lambda表达式?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1.基本介绍lambda表达式,即带有参数的表达式,为了更清晰地理解lambda
2023-05-31

Java8和Scala中的Lambda表达式有什么不同

本篇内容介绍了“Java8和Scala中的Lambda表达式有什么不同”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、为什么使用Lambd
2023-06-17

lambda表达式如何正确的在Java8项目中使用

这篇文章给大家介绍lambda表达式如何正确的在Java8项目中使用,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。在Java8 里面Lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来了函数式编程的思想,我
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录