JavaSE(十三)——函数式编程(Lambda表达式、方法引用、Stream流)

2024-09-08 14:36

本文主要是介绍JavaSE(十三)——函数式编程(Lambda表达式、方法引用、Stream流),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

函数式编程

函数式编程 是 Java 8 引入的一个重要特性,它允许开发者以函数作为一等公民(first-class citizens)的方式编程,即函数可以作为参数传递给其他函数,也可以作为返回值。 这极大地提高了代码的可读性、可维护性和复用性。函数式编程的核心概念包括高阶函数、Lambda 表达式、函数式接口、流(Streams)和 Optional 类等。

函数式编程的核心是Lambda表达式,它是一种简洁的表示匿名方法的方式。Lambda表达式允许你以更简洁的方式传递代码块作为参数给方法,这是Java 8及以后版本中引入的一个重要特性,它主要用在实现只有一个抽象方法的接口(即函数式接口)时。

Lambda表达式只能替代函数式接口的匿名内部类,所以我们先了解函数式接口。

函数式接口

函数式接口是一个具有且仅有一个抽象方法的接口。 它可以包含默认方法和静态方法,但这些方法不会破坏接口作为函数式接口的性质,因为它们不是抽象方法。

函数式接口的特点

  • 单一抽象方法:只含有一个抽象方法
  • Lambda表达式支持:可以使用Lambda表达式简洁地实现该接口
  • @FunctionalInterface注解:推荐使用@FunctionalInterface注解来标记一个接口为函数式接口。这有助于编译器检查接口是否符合函数式接口的规范,并在接口中错误地添加了第二个抽象方法时提供编译时错误。

例如,我们常用的Comparetor接口就是函数式接口

在这里插入图片描述

  • Comparator接口使用到@FunctionalInterface注释
  • compare方法是Comparator接口唯一的抽象方法,尽管存在其他方法,但都是静态方法和默认方法,不会影响函数式接口的性质

Lambda表达式

Lambda表达式的使用

Lambda表达式用于替代函数式接口的匿名内部类对象,从而让程序更简洁,可读性更好。

基本格式如下:

(被重写方法的参数列表) -> {  被重写方法的方法体代码 
}

【举例演示Lambda表达式】

实现函数式接口:

  • 使用匿名内部类:
interface ITest1 {void function();
}public class LambdaDemo1 {public static void main(String[] args) {ITest1 iTest1 = new ITest1() {@Overridepublic void function() {System.out.println("被重写的接口抽象方法...");}};}
}
  • Lambda表达式:
interface ITest1 {void function();
}public class LambdaDemo1 {public static void main(String[] args) {ITest1 iTest1 = ()->{System.out.println("被重写的接口抽象方法...");};}
}

自定义一个学生类,创建一个学生类对象数组,使用comparator比较器 和 Arrays.sort()方法对其排序(要求:使用匿名内部类和Lambda表达式两种方式实现):

//自定义学生类
class Student {public String name;public int age;public double score;public Student(String name, int age, double score) {this.name = name;this.age = age;this.score = score;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", score=" + score +'}';}
}public class LambdaDemo1 {//Lambda表达式public static void byLambda(Student[] student) {Student[] students = new Student[3];for (int i = 0; i < students.length; i++) {students[i] = student[i];}System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students, (Student o1, Student o2)->{return o1.age - o2.age;});System.out.println("排序后:" + Arrays.toString(students));}//匿名内部类public static void byInnerclass(Student[] student) {Student[] students = new Student[3];for (int i = 0; i < students.length; i++) {students[i] = student[i];}System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}});System.out.println("排序后:" + Arrays.toString(students));}public static void main(String[] args) {Student[] students = new Student[3];students[0] = new Student("张三", 19, 98.2);students[1] = new Student("李四", 21, 88.5);students[2] = new Student("王五", 18, 99.9);byInnerclass(students);System.out.println("=====================");byLambda(students);}

在这里插入图片描述

  • 匿名内部类中重写的方法的 参数列表 对应 Lambda表达式->的左边; 方法体 对应 Lambda表达式->的右边

Lambda表达式的省略

Lambda表达式可以进行化简,其规则如下:

  • 参数类型可以省略
  • 如果只有一个参数,参数类型省略的同时 “()” 也可以省略,但多个参数不能省略
  • 如果Lambda表达式中只有一行代码,大括号可以不写,同时分号 “;” 也要省略;如果这行代码是 return语句,也必须去掉return

例如

//化简前
Arrays.sort(students, (Student o1, Student o2)->{return o1.age - o2.age;});//化简后
Arrays.sort(students, (o1, o2)->o1.age - o2.age);

对于原Lambda表达式:(Student o1, Student o2)->{return o1.age - o2.age;},首先省略了参数类型,但由于不止一个参数所以小括号不能省略,方法体中只包含一条语句,可以省略大括号并同时省略分号,由于是return语句,省略return。


方法引用

Java中的方法引用也是Java 8引入的一个特性,它 提供了一种更简洁的方式来引用方法或Lambda表达式。 方法引用可以看作是Lambda表达式的一种特殊形式,其中Lambda表达式仅仅是调用了已存在的方法。使用方法引用可以使代码更加简洁易读。

所以,可以将方法引用理解为对Lambda表达式的再进一步化简,主要分为:

  • 静态方法引用
  • 实例方法引用
  • 特定类型方法引用
  • 构造方法引用

静态方法引用

静态方法引用:通过类名直接引用静态方法。

格式为:类名::静态方法

应用场景:如果某个Lambda表达式中只是通过类名调用了一个静态方法,并且->前后参数的形式一致,就可以使用静态方法引用

【示例】

创建一个场景,一个学生类,实现一个静态方法,实现了根据年龄的排序规则,利用Arrays.sort(T[] a, Comparator<? super T> c)方法排序时,Lambda表达式的方法体中仅调用这个静态方法,使用静态方法引用化简:

class Student {public String name;public int age;public double score;public Student(String name, int age, double score) {this.name = name;this.age = age;this.score = score;}//静态比较方法public static int compareByAge(Student s1, Student s2) {return s1.age - s2.age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", score=" + score +'}';}
}public class LambdaDemo1 {public static void byLambda(Student[] student) {Student[] students = new Student[3];for (int i = 0; i < students.length; i++) {students[i] = student[i];}System.out.println("===== Lambda表达式 =====");System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students, (o1, o2)->Student.compareByAge(o1, o2));System.out.println("排序后:" + Arrays.toString(students));}public static void byInnerclass(Student[] student) {Student[] students = new Student[3];for (int i = 0; i < students.length; i++) {students[i] = student[i];}System.out.println("===== 匿名内部类 =====");System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return Student.compareByAge(o1, o2);}});System.out.println("排序后:" + Arrays.toString(students));}public static void byMethodReference(Student[] student) {Student[] students = new Student[3];for (int i = 0; i < students.length; i++) {students[i] = student[i];}System.out.println("===== 静态方法引用 =====");System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students, Student::compareByAge);System.out.println("排序后:" + Arrays.toString(students));}public static void main(String[] args) {Student[] students = new Student[3];students[0] = new Student("张三", 19, 98.2);students[1] = new Student("李四", 21, 88.5);students[2] = new Student("王五", 18, 99.9);byInnerclass(students);byLambda(students);byMethodReference(students);}
}

在这里插入图片描述


实例方法引用

静态方法引用:通过实例对象直接引用实例方法。

格式为:对象名::实例方法

应用场景:如果某个Lambda表达式中只是通过对象名调用了一个实例方法,并且->前后参数的形式一致,就可以使用实例方法引用

【示例】

相似的业务场景,只不过将静态方法引用中调用静态方法改为调用一个实例方法,使之成为实例方法引用的场景。

在这里插入图片描述


特定类型方法引用

特定类型的方法引用:使用类名来引用该类中任意对象的实例方法,此时Lambda表达式的第一个参数会是该方法的调用者。

格式为:特定类的名称::方法

应用场景:如果某个Lambda表达式里只是调用一个特定类型的实例方法,并且前面的参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,此时就可以使用特定类型的方法引用。

【示例】

现有一个字符串数组,要对该数组按字母顺序进行排序,排序时忽略大小写,即"Zh"应该在"ang"的后面:

public static void main(String[] args) {String[] strings = new String[]{"Zh","ang","Job","Aa","bb","Oo"};//正常排序,不能实现需求String[] strings1 = Arrays.copyOf(strings,strings.length);System.out.println("排序前:");System.out.println(Arrays.toString(strings1));Arrays.sort(strings1);System.out.println("排序后:");System.out.println(Arrays.toString(strings1));//忽略大小写排序String[] strings2 = Arrays.copyOf(strings,strings.length);System.out.println("排序前:");System.out.println(Arrays.toString(strings2));Arrays.sort(strings2, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o1.compareToIgnoreCase(o2);}});System.out.println("排序后:");System.out.println(Arrays.toString(strings2));//Lambda表达式String[] strings3 = Arrays.copyOf(strings,strings.length);System.out.println("排序前:");System.out.println(Arrays.toString(strings3));Arrays.sort(strings3, (o1, o2)->o1.compareToIgnoreCase(o2));System.out.println("排序后:");System.out.println(Arrays.toString(strings3));//特定方法引用String[] strings4 = Arrays.copyOf(strings,strings.length);System.out.println("排序前:");System.out.println(Arrays.toString(strings4));Arrays.sort(strings4, String::compareToIgnoreCase);System.out.println("排序后:");System.out.println(Arrays.toString(strings4));}

在这里插入图片描述


构造方法引用

构造方法引用:使用类名来引用构造方法

格式为:类名::new

应用场景:如果某个Lambda表达式里只是在创建对象,并且->前后参数情况一致,此时就可以使用构造方法引用

【示例】

以下三种方法等价:

    public static void main(String[] args) {//匿名内部类ICarFactory carFactory1 = new ICarFactory() {@Overridepublic Car createOb(String name, double price, String brand) {return new Car(name, price, brand);}};//Lambda表达式ICarFactory carFactory2 = (name, price, brand) -> new Car(name,price,brand);//构造方法引用ICarFactory carFactory3 = Car::new;}

在这里插入图片描述


Stream流

Stream是JDK8也是引入的新特性,是一套全新的API(java.util.stream.*),是函数式编程的重要部分,可以用于操作集合或者数组的数据。Stream流大量结合了Lambda表达式的语法风格,能够简化代码,增强代码可读性。

Stream流的使用步骤:

  1. 获取Stream流
  2. 调用中间方法,处理流上的数据
  3. 调用终止方法

在这里插入图片描述


获取Stream流

Stream 本身是一个接口,但集合(如 ListSet 等)和数组可以通过特定的方法或操作来“使用”它,即生成 Stream 实例。

  • 获取集合(Collection下) 的Stream流
Collection提供解释
default Stream stream()获取当前集合对象的Stream流

Collection底下的集合类可以直接通过对象实例调用stream方法,但Map底下的不可以。

Map相关集合类需要先调用values()keySet()entrySet()三种方法,再调用stream()方法分别获取值流、键流、键值对流。

示例:

    public static void main(String[] args) {List<Integer> list = new ArrayList<>();list.stream();Set<Integer> set = new HashSet<>();set.stream();Map<String,Integer> map = new HashMap<>();map.values().stream();map.keySet().stream();map.entrySet().stream();}

  • 获取数组的Stream流

对于数组,Java 8 并没有在数组类型上直接添加 stream() 方法,因为数组是基本类型或者是固定大小的对象集合,并不直接实现 Collection 接口,但可以通过以下两种方法获取数组的Stream流:

Arrays类提供的方法解释
public static Stream stream(T[] array)获取当前数组的Stream流
Stream提供的方法解释
public static Stream of(T… values)获取当前接收数据的Stream流
  • of方法的参数T...values是可变参数

示例:

    public static void main(String[] args) {String[] strings = new String[10];Arrays.stream(strings);Stream.of(strings);}

补充:可变参数

可变参数(也称为可变长参数或不定参数)是一种允许你传递任意数量参数的方法。这个功能通过 ... 语法在方法定义中指定。使用可变参数,你可以调用方法时传递零个或多个指定类型的参数,而不需要使用数组。这些参数在方法内部被当作一个数组处理。

格式数据类型...参数名称

可变参数可以接收一个或多个数据,也可以直接接收数组,甚至可以不传入任何数据。

注意

  • 一个参数列表只能有一个可变参数
  • 可变参数必须在参数列表的最后面
  • 可变参数在方法内部就是一个数组
  • 如果方法之间存在参数数量不同的重载版本,并且其中一个版本使用了可变参数,那么编译器可能会报告冲突。这是因为编译器可能会将具有固定数量参数的方法调用解释为对可变参数方法的调用。

示例

    public static void varargs(int a, int...b) {System.out.println("可变参数测试:");System.out.println(a);System.out.println(Arrays.toString(b));}public static void main(String[] args) {int[] array = new int[]{1, 2, 3, 4, 5};//不向可变参数传参varargs(1);//向可变参数传入一个数据varargs(1, 2);//向可变参数传入多个数据varargs(1, 2,3,4,5);//向可变参数传入数组varargs(1, array);}

在这里插入图片描述


中间方法

中间方法指的是调用完成后会返回新的Stream流,可以继续使用,即支持链式编程,可以连续使用多个中间方法。

可以将Stream流看作是一个传送带,集合或数组的数据都倒到传送带上,再由中间方法对数据进行筛选。

常用方法:

Stream提供的方法说明
Stream filter(Predicate<? super T> predicate)对流中数据进行过滤
Stream sorted()对元素进行升序排序,T 必须可比较
Stream sorted(Comparator<? super T> comparator)按照指定规则排序
Stream limit(long maxSize)获取前maxSize个元素
Stream skip(long n)跳过前n个元素
Stream distinct();去重
Stream map(Function<? super T, ? extends R> mapper)加工/映射,将流上数据加工后变成新数据再放回流中
public static Stream concat(Stream<? extends T> a, Stream<? extends T> b)将a流和b流合并为一个流

示例:

    public static void main(String[] args) {List<Integer> list = new ArrayList<>();Collections.addAll(list, 78,36,88,98,39,56,41,66,78);//filter-过滤出60+的数据list.stream().filter(s -> s >=60).forEach(System.out::println);//sorted-升序排序list.stream().sorted().forEach(System.out::println);//sorted-指定降序排序list.stream().sorted((o1, o2) -> o2 - o1).forEach(System.out::println);//limit-获取前3大的数据list.stream().sorted((o1, o2) -> o2 - o1).limit(3).forEach(System.out::println);//skip-跳过前3大的元素list.stream().sorted((o1, o2) -> o2 - o1).skip(3).forEach(System.out::println);//distinct-去重list.stream().distinct().forEach(System.out::println);//map-将所有数据-10list.stream().map(s -> s - 10).forEach(System.out::println);//concat-合并流List<String> list1 = new ArrayList<>();Collections.addAll(list1, "a","b","c");Stream.concat(list.stream(), list1.stream()).forEach(System.out::println);}

终止方法

终止方法指的是调用完成后,不返回新的Stream流,即无法再继续使用流。

当一个流调用了终止方法后,就不能再使用该流了,否则会抛出IllegalStateException异常!

常用方法:

Stream提供的方法解释
void forEach(Consumer<? super T> action)对流运算后的元素执行遍历操作
long count()统计流运算后的元素个数
Optional max(Comparator<? super T> comparator)获取流运算后的最大值元素
Optional min(Comparator<? super T> comparator)获取流运算后的最小值元素

Optional 类是 Java 8 引入的一个容器类,用于包含非空值。Optional 类的引入主要是为了解决空指针异常(NullPointerException)的问题,提供了一种更好的方式来处理可能为 null 的情况。

Optional 类是一个可以包含也可以不包含非 null 值的容器对象。如果值存在,isPresent() 方法将返回 true,调用 get() 方法将返回该对象。


终止方法中有一种收集方法,能将Stream流操作后的结果转回到集合或者数组中。

开发中将数据收集到集合或数组中才会进一步使用,Stream流只是方便操作集合和数组的手段。

方法解释
<R, A> R collect(Collector<? super T, A, R> collector)将流处理后的结果集收集到一个指定的集合中
Object[] toArray()将流处理后的结果集收集到一个数组中

关于collect方法的参数,要关注Collectors工具类提供的具体的收集方式

方法解释
public static Collector<T, ?, List> toList()把元素收集到List集合
public static Collector<T, ?, Set> toSet()把元素收集到Set集合
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)把元素收集到Map集合

这三个方法的返回值和参数较为复杂,搭配collect方法使用:

    public static void main(String[] args) {List<Integer> list1 = new ArrayList<>();Collections.addAll(list1, 78,36,88,98,39,56,41,66,78);List<Student> list2 = new ArrayList<>();Collections.addAll(list2, new Student("张三",12), new Student("李四",24 ),new Student("王五",18));//toListList<Integer> newList = list1.stream().sorted().collect(Collectors.toList());System.out.println(newList);//toSetSet<Integer> set = list1.stream().collect(Collectors.toSet());System.out.println(set);//toMapMap<String,Integer> map = list2.stream().collect(Collectors.toMap(s -> s.name, s -> s.age));System.out.println(map);//toArrayObject[] array = list1.stream().sorted().toArray();System.out.println(Arrays.toString(array));}

最后小结一下函数式编程的优势

  • 代码简洁:Lambda 表达式和 Stream API 可以让代码更简洁、更易读。
  • 并行处理:Stream API 支持并行操作,可以自动利用多核处理器进行并行计算。
  • 易于测试:函数式编程倾向于使用小的、独立的函数,这使得测试更加简单。
  • 无副作用:函数式编程鼓励编写没有副作用(即不修改外部状态)的函数,这有助于保持代码的清晰和可预测性。

这篇关于JavaSE(十三)——函数式编程(Lambda表达式、方法引用、Stream流)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1148376

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�