函数式编程
Lambda表达式
核心原则
可推导可省略
基本格式
(参数)->{代码块}
匿名内部类实现Thread对象的run方法的重写
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中run方法被执行了");
}
}).start();
Lambda表达式重写
new Thread(()->{
System.out.println("666");
}).start();
省略了函数名称的定义
条件:如果要重写的方法是一个接口而且接口中只有一个方法(因为只有一个方法所以不需要做区分)
省略规则
1.参数类型可以忽略
2.方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
3.方法只有一个参数时,小括号可以省略
4.以上这些规则都记不住也可以省略不记?这么省大大降低了代码可读性
Stream
一、定义与特点
Stream 是对集合(Collection)数据的高级抽象,它提供了一种声明式的方式来处理数据集合,就像对数据集合的操作流水线。
具有延迟执行的特点,只有在终端操作被调用时,整个流的操作才会真正执行。这使得可以将多个操作链接在一起,形成一个高效的操作序列。
二、常见操作类型
中间操作(Intermediate Operations):
这些操作会返回一个新的 Stream,允许对数据进行一系列的转换和过滤。
例如:filter、map、sorted等。
filter(Predicate<? super T> predicate):根据给定的谓词(一个返回 boolean 值的函数)过滤 Stream 中的元素。
map(Function<? super T,? extends R> mapper):将 Stream 中的每个元素通过给定的函数进行转换,生成一个新的元素。
sorted():对 Stream 中的元素进行自然排序,如果元素是自定义类型,需要实现 Comparable 接口;也可以传入一个 Comparator 进行自定义排序。
终端操作(Terminal Operations):
触发 Stream 流的实际执行,并产生一个结果或副作用。
例如:forEach、collect、reduce等。
forEach(Consumer<? super T> action):对 Stream 中的每个元素执行给定的操作,通常用于输出或修改元素。
collect(Collector<? super T,A,R> collector):将 Stream 中的元素收集到一个结果容器中,如收集到一个集合、映射或其他数据结构中。
reduce(BinaryOperator accumulator):将 Stream 中的元素进行归约操作,通过给定的函数将元素逐步合并成一个结果。
三、使用示例
以下是一个使用 Stream 流编程处理集合数据的示例:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 过滤出偶数并乘以 2
List<Integer> doubledEvenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println(doubledEvenNumbers);
// 计算所有数字的总和
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
}
}
在这个例子中,首先使用 filter 方法过滤出偶数,然后使用 map 方法将偶数乘以 2,最后使用 collect 方法将结果收集到一个新的列表中。接着,使用 reduce 方法计算了列表中所有数字的总和。
首先获取集合对象
如上图,.stream()将对象转化为流对象,.distinct()完成去重,.filter()完成对括号中条件的判断,.forEach()表示遍历
数组转换为流对象方法如下:
双列集合转换的方法(也是先转化为Set):
entrySet()是Map接口的一个方法,它返回一个包含Map中所有键值对的Set集合
中间操作和终结操作
中间操作
定义与特点:
中间操作是对 Stream 进行处理,返回一个新的 Stream 的操作。
中间操作是惰性的,即只有在遇到终结操作时才会真正执行。这意味着可以将多个中间操作链接在一起,形成一个操作流水线,而不会立即执行任何实际的处理。
终结操作
定义与特点:
终结操作是触发 Stream 操作链执行并产生最终结果的操作。
一旦调用了终结操作,Stream 就不能再被使用。
map方法
进行流当中对象的类型转换。(将一个类型映射为另一个类型)
这里就是把流对象,原本是Author对象,映射为author.getname所对应的字符串
distinct方法
去除流当中的重复的元素,实际上使用Object当中的equals方法。如果对象直接比较,需要内存地址相同才为真。如果需要实现去除重复的对象,需要重写equals方法
sorted方法
将流当中的元素进行排序,如果没有实现Comparable接口则无法进行比较
实际Lambda表达式实现
limit方法
限制流的最大长度
skip方法
跳过流中的前n的元素,并返回后面的元素
flatmap方法
示例一:
将流元素展平并完成映射
由于类中的book属性是一个集合,这样获得的流元素,每一个都是一个List<book>
对象,直接输出String类型的书名会存在问题
用匿名函数类,可以看到flatmap实际上是将每一个集合对象,展平为book类型的对象
最后完成展平并映射道每一本书
用Lambda表达式重写
注意flatMap函数的返回值是一个流对象
示例二:
forEach方法
对流元素进行遍历,并决定要进行的操作
代码:输出所有作家的名字
count方法
获取流当中的元素的数量
最终的返回值是一个整型?
min&max方法
获取流元素的最值
collect方法
把当前流转换为一个集合
查找与匹配 anyMatch方法
只要存在满足条件的元素,就返回true
返回的是一个布尔类型
allMatch方法
所有元素都符合判断条件才返回true
同样的还有 noneMatch方法,顾名思义
findAny方法
查找任意一个满足条件的元素
Optional类型是为了防止空指针异常(如果没有查找对象,则authors指针不会被赋值)
findFirst方法
查找第一个满足条件的元素 顾名思义和上面的方法用法类似
reduce方法
result = identity 为定义的初始值,而apply则根据定义的方法参数,选择对应的计算方法
//获取所有的作家的年龄之和
Integer sum = authors.stream
.distinct()
.map(author -> author.getage()) //集合转换了才能进行运算
.reduce((result,element)->result + element); //表示返回的是累加的值
Sream流注意事项:
注意终结之后, 流不可再被使用
Optional
如果没有Optional对象,需要进行空指针判断,会导致代码臃肿且难以维护
养成使用Optional的习惯可以写出更加优雅的代码来避免空指针异常。
定义为函数,如果不为空则直接返回
实际开发过程中很多数据是通过数据库来获取的,Mybatis从3.5版本以后也支持Optional了。可以直接把dao方法的返回值类型定义为Optional类型,Mybatis会自己把数据封装为Optional对象返回。封装的过程也不需要我们自己去操作。
很多函数式编程相关的API都用了Optional。
Optional.of() 传入的参数必须不能为null。(不建议使用)
Optional.empty() 返回一个空的Optional对象。
安全的消费
Optional.ifPresent() 该方法会判断其内部封装的数据是否为空,不为空的时候才能执行具体的消费代码。
Optional.isPresent() 该方法会判断其内部封装的数据是否为空,为空返回false,不为空返回true.
获取Optional值
如果想要安全的获取Optional对象中的值,不推荐使用get()方法。推荐使用以下几种方法。
Optional.ofNullable() 将对象封装为Optional对象。无论传入的参数是否为null都不会出现问题。(建议使用 )
Optional.orElseGet() 如果Optional中的值为null,可以自定义返回一个对象。
上图表示,如果值为null,则返回设置的默认值为new Author();
Optional.orElseThrow() 如果Optional中的值为null,可以手动抛出异常。
过滤值
Optional.filter() 在方法中进行逻辑判断,如果满足会返回Optional对象;不满足则返回null.
这样流中返回的是一个optional对象,值为null
转换值
Optional.map() 将对象中的值转为Optional<List<T>>对象.
最终被转换为了 Optional
函数式接口
JDK的函数式接口中都加上了
@FunctionalInterface注解进行标识,无论是否加上该注解只要接口中只有一个抽象方法(未实现的方法),都是函数式接口。
JDK自带的常用函数式接口
Consumer 消费接口
Comparator 比较接口
Function 计算转换接口
根据重写的方法转换或者计算
Predicate 判断接口
Supplier 生产型接口
函数式接口中的默认方法
方法引用
and方法实现多重条件判断
直接写 author.getAge() > 17 && author.getAge() > 1 好像一样?
方法引用的基本格式
类名或对象名::方法名
应用场景:写完Lambda表达式以后,发现方法体只有一行代码,并且方法调用时候使用Idea快捷键能够转换成方法引用即可。
构造器引用
转换为匿名内部类可以看到,重写了构造函数的接口,而且代码体只有一行
引用格式:
类名:: new
装箱和拆箱
一、装箱(Boxing)
概念:装箱是将基本数据类型转换为对应的包装类对象的过程。例如,将int类型转换为Integer对象。
自动装箱:Java 5 引入了自动装箱功能,使得基本数据类型可以自动转换为包装类对象。例如,以下代码中,将int类型的变量i自动装箱为Integer对象:
int i = 10;
Integer integer = i;
手动装箱:也可以通过显式调用包装类的构造方法来进行手动装箱。例如:
int j = 20;
Integer integer2 = new Integer(j);
二、拆箱(Unboxing)
概念:拆箱是将包装类对象转换为基本数据类型的过程。例如,将Integer对象转换为int类型。
自动拆箱:Java 5 也引入了自动拆箱功能,使得包装类对象可以自动转换为基本数据类型。例如,以下代码中,将Integer对象integer自动拆箱为int类型:
Integer integer = 30;
int k = integer;
手动拆箱:可以通过调用包装类的xxxValue()方法来进行手动拆箱。例如:
Integer integer3 = new Integer(40);
int l = integer3.intValue();
三、注意事项
空指针异常:在进行拆箱操作时,如果包装类对象为null,会抛出NullPointerException。例如:
Integer integer4 = null;
int m = integer4; // 会抛出空指针异常
性能影响:虽然自动装箱和拆箱使得代码更加简洁,但在频繁进行装箱和拆箱操作时,可能会对性能产生一定的影响。因为每次装箱和拆箱都涉及到对象的创建和销毁。
高级用法
在Stream流中,上面的代码块每次都是用的Integer就行运算,但每一次运算时都需要将Integer拆箱为int,计算完后包装为Integer,这样在运算中会有较大的开销。
map()方法中的第一个泛型不能改(第一个类型就是对象的元素,已经是定义好的,不能修改不然会报错),但是第二个泛型是可以修改的(可以修改为想要的数据类型),简单理解为就是把流当中的元素转换为另外一种元素类型然后再放到流中。
mapToInt() 高级用法…还有mapTOLong()等等,针对基本数据类型进行优化。
并行流
当流中有大量元素,可以使用并行流提高操作效率。其实并行流就是把任务分配给多个线程去完成。如果使用Stream的话,只需要修改一个方法的调用就可以使用并行流来提高效率
Stream.parallel()方法
parallelStream() 直接获取并行流
.parallel() 控制多线程进行工作,把串行流转换为并行流