Java 8 函数式编程
2021-06-21 21:34:30 37 举报
AI智能生成
Java8函数式编程(未完待续)
作者其他创作
大纲/内容
Lambda表达式<br>
去除大量模板代码,使代码变得紧凑,增强可读性
表现形式<br>
<font color="#f1753f">(类型 参数) -> {方法体}</font><br>
例如<br>BinaryOperator<Long> add = (x, y) -> x + y;<br>BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;<br>Runnable task = () -> {System.out.println("hello lambda");};<br>ActionListener oneArgument = event -> System.out.println("button clicked");<br>
类型可选,一般javac会自动推断出参数类型(也存在无法推断的情况)<br>没有参数时,省略类型和参数<br>有且仅有一个参数时,参数括号可省略<br>方法体如果只有一行语句,可省略花括号<br>
方法引用
<font color="#f1753f">类名</font>::<font color="#f1753f">方法名</font><br>
Function<integer, string=""><Integer, String> intToStr = String::valueOf; // 等同于 e -> String.valueOf(e)<br></integer,>
当参数仅有一个,且返回值为类的一个方法(静态或非静态均可)返回值时,可省略参数,使用 类名::方法名 的方式描述<br>
使用 类名::new 来引用类的构造方法<br>
Function<char[], String> newStrFromCharArray = String::new; // 调用的是 new String(char[] cs)构造方法<br>String[]::new 来引用数组的构造方法<br>
值引用
同<font color="#f1753f">匿名内部类</font>类似,在Lambda表达式中引用的变量,需要<font color="#f1753f">声明为final</font>或在<font color="#f1753f">既成事实上为final</font><br>即<font color="#f1753f">终态变量</font>
String name = "Alex";<br>// <font color="#8a8b8f"><i>name自声明以来仅赋值一次,</i></font><br>// <font color="#8a8b8f"><i> 虽然没有显式声明为final,但为既成事实上的final变量</i></font><br>button.addActionListener(event -> System.out.println("hi " + name));<br>name = "Jack"; // <font color="#8a8b8f"><i>再次赋值会导致上一行lambda表达式报错</i></font><br>
此特性表明lambda表达式中变量实际上为<font color="#f1753f">值引用</font>
函数式接口
函数式接口是<font color="#f1753f">只有一个抽象方法</font>的接口,用作Lambda表达式的类型<br>允许存在其他非抽象方法(如静态非抽象方法、default默认方法)<br>
只有一个抽象方法的限制保证了javac可以正常的推断Lambda表达式所实现的接口方法
<font color="#8a8b8f"><i>(可选)可以使用@FunctionalInterface标注函数式接口,该注解会强制javac检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac就会报错。重构代码时,使用它能很容易发现问题</i></font>
常用的函数式接口<br>java.util.function<br>
一元接口
Predicate<T><t></t>:<br>
boolean test(T t)<br>谓词函数,传入一个变量,根据一定规则进行布尔判断
Function<t><T, R></t>
R apply(T t)<br>一般函数,传入一个变量,输出另一个变量<br>
UnaryOperator<T>
T apply(T t)<br>算子函数,输入输出同类型<br>形如XXOperator的接口均为算子函数式接口,入参出参类型相同<br>
Consumer<T><t>:</t>
void accept(T t)<br>消费函数,传入一个变量,执行无返回值的操作
Supplier<T>
T get()<br>生产函数,没有输入,输出一个变量<br>
二元接口
BiPredicate<T, U>
boolean test(T t, U u)<br>谓词函数的二元版本,接受两个参数<br>
BiFunction<T, U, R>
R apply(T t, U u)<br>一般函数的二元版本,接受两个参数<br>
BinaryOperator<T>
T apply(T t1, T t2)<br>算子函数的二元版本,接受两个参数<br>
BiConsumer<T, U><br>
void accept(T t, U u)<br>消费函数的二元版本,接受两个参数<br>
Function, Predicate, Consumer等及其二元版本还有一些default默认方法也较为常用,待补充<br>
Predicate, Fucntion, Consumer, Supplier, UnaryOperator等及其二元版本均有int, double, long版本的函数式接口<br>详见java.util.function包<br>
类型推断<br>
javac会根据应用上下文自动推断变量类型<br>
final String[] ary = {"Hello", "World"};<br>Map<string, integer=""><String, Integer> diamondWordCounts = new HashMap<>();</string,>
Predicate<integer><Integer> atLeast5 = x -> x > 5;</integer>
依据Predicate<Integer>自动推断x的类型为Integer,并且表达式的返回值为boolean<br>
反例
BinaryOperator add = (x, y) -> x + y;
编译报错,因为无法推断xy的类型,则认定xy类型为Object,而Object类型不支持+操作符<br>
流(Stream)<br>
外部迭代到内部迭代<br>
外部迭代:针对集合的普通for循环,由应用代码控制迭代的方式<br> 本质上为串行化操作,不易修改为并行化迭代
内部迭代:通过对集合的stream操作,应用程序仅仅关心迭代需要进行的操作和迭代结果<br> 迭代的实现方式由集合自行控制
实现方式
Collection的stream方法返回集合的一个可操作流对象(并非创建了一个新的集合)
针对stream有一系列的<font color="#f1753f">流操作</font>方法,分为两大类
惰性求值<br>
仅仅是对流需要执行的操作的描述,并不会立即执行<br>返回值是stream<br>
例如过滤操作filter
及早求值
从stream产生新值的方法,会导致stream的迭代<br>返回值是非stream或空<br>
例如计数操作count
针对如下例子:<br>List<String> strings = ...;<br>long emptyStrCount = <font color="#8a8b8f">strings</font><br> .<font color="#f15a23">stream()</font><font color="#0076b3"><br> .filter(<font color="#662c90">str -> !str.isEmpty()</font>)<br> </font>.<font color="#16884a">count()</font>;
strings为一个字符串列表,对其中为空的字符串进行计数
通过stream()方法获取集合的可操作stream流
filter方法接受一个一元谓词函数(Predicate<String>),并依据返回的boolean值决定断言的元素是否保留到过滤后的stream中<br>filter操作并不会立即执行,它仅仅描述需要对strings.stream()这个流对象需要执行的操作<br>
最后count方法对过滤后的stream进行元素计数,获得最终结果
如果修改为<br>strings.stream().filter(str -> {<br> System.out.println(str);<br> return str.isEmpty();<br> });<br>此代码执行后并不会有任何输出,因为filter方法不会构造一个新的列表,不会立即迭代列表,<br>并且此stream没有及早求值方法,因此不会进行迭代,即filter方法不会被执行<br>
常用的流操作
惰性求值操作<br>
filter
接收一个Predicate<T>,并据此对stream执行过滤,仅保留test(T t)结果为true的元素<br>
map
接收一个Function<T, R>,并据此对stream中每个元素T进行处理,将返回结果R组成新的stream<br>
例如 <br>List<String> intStr =<br> integers.stream().map(String::valueOf).collect(Collectors.toList());<br>
flatMap
将多个stream流中的元素综合成一个stream<br>
例如<br>List<Integer> intList =<br> Stream.of(Arrays.asList(1,2), Arrays.asList(3,4))<br> .flatMap(e -> e.stream()).collect(Collectors.toList());<br>将[1,2]和[3,4]两个列表合并成一个列表[1,2,3,4],e -> e.stream()可替换为Collection::stream<br>
distinct
从stream元素中去除重复元素,判断依据equals方法的返回值<br>
例如<br>int distinctValCount = integers.stream().distinct().count(); // 求取整数列表中不重复元素的个数<br>
sorted
依据自然顺序(natural order)对stream元素排序,返回排序后的stream
例如<br>List<Integer> sortedInt = integers.stream().sorted().collect(Collectors.toList());<br>求取整数列表排序后的列表<br>
元素需要实现Comparable<T>接口,或者给sorted传入自定义的Comparable<T>
peek
接受一个Consumer<T>,对stream中的每个元素执行Consumer操作,并返回原stream<br>
例如<br>List<integer> sortedInt = integers.stream()<br> .peek(System.out::println)<br> .sorted()<br> .peek(System.out::println)<br> .collect(Collectors.toList());<br>求取整数列表排序后的列表,并将排序前后的元素打印出来</integer>
通常用于对数据进行监控,日志记录等
limit/skip
接受一个long类型参数,限制保留/跳过指定数量的元素<br>
例如<br>// 求取整数数列中前三小的数<br>List min3int = integers.stream().sorted().limit(3).collect(Collectors.toList());<br>// 去除整数数列中前三小的数<br>List min3int = integers.stream().sorted().skip(3).collect(Collectors.toList());
及早求值操作
collect
对stream执行收集操作,产生一个新的对象(集合)<br>接受一个实现了Collector接口的对象作为参数,用来描述收集元素形成集合的方式<br>
常用stream.collect(Collectors.toList())来从stream中获取列表<br>Collectors类中预定义了许多常用的集合收集方法<br>
例如<br>List<String> simpleStrings = <br> Stream.of("1", "2", "3").collect(Collectors.toList());<br>一行代码快速构造简单列表<br>与Arrays.asList("1", "2", "3")结果相同(equal)<br>
reduce
从一个stream的一组元素中生成一个值
例如<br>int sum = integers.stream().reduce(0, (m, n) -> m + n); // 对整数列表求和<br>int product = integers.stream().reduce(1, (m, n) -> m * n); // 对整数列表求积<br>int result = integers.stream().reduce(0, (m, n) -> m + n * n); // 求平方和<br>String bigStr = strings.stream().reduce("", (m, n) -> m + n); // 将字符串列表合并成一个字符串<br>BinaryOperator<T> 中第一个参数(上例中m)为累积结果,第二个参数(上例中n)为stream中的遍历元素<br>
接受一个初始值T,和一个BinaryOperator<T>,随后从初始值开始,对stream中的元素逐个执行二元操作
常用reduce操作
max/min
求stream中“最大”/“最小”的元素,接受一个Comparator<T><br>
例如<br>Integer maxVal = integers.stream().max((m, n) -> m.compareTo(n)).get();<br>Integer maxVal = integers.stream().max(Integer::compareTo).get();<br>求取最大的整数
返回一个Optional<T>,详见下文<br>
类Comparator提供了许多辅助方法
例如<br>String longestStr = strings.stream().max(Comparator.comparing(ste -> str.length())).get();<br>获取列表中最长的字符串<br>
count
对stream中的元素计数
anyMatch/allMatch/noneMatch
接受一个Predicate<T>,依据Predicate判断stream元素中是否 <font color="#f1753f">存在任何</font>满足/<font color="#f1753f">所有</font>都满足/<font color="#f1753f">没有</font>满足 指定断言的元素<br>返回布尔值<br>
例如<br>boolean hasEmptyStr = strings.stream().anyMatch(String::isEmpty);<br>判断列表中是否存在空字符串<br>
findFirst/findAny
获取stream中第一个/任意一个元素,返回Optional<T><br>
forEach
接受一个Consumer<T>,对stream中的每个元素执行制定操作,返回void,此stream终止<br>
forEachOrder
此操作会按照stream中的原顺序进行Consumer操作,适用于使用了并行流(parallel)的stream<br>
并行流 parallel stream<br>
集合的stream()方法返回的是串行的流,对其的操作一般都是串行化的<br>
对集合调用parallelStream()方法,或者对steram调用parallel()方法,可以获取并行化的流<br>
并行化的流使用ForkJoinPool.commonPool()提供的pool来执行并行化任务<br>
pool的大小默认为处理器核心数量,可以通过-Djava.util.concurrent.ForkJoinPool.common.parallelism=N来控制
对<b><font color="#f1753f">任务简单</font></b>,可并行的操作,可以通过并行流来提高执行效率,但是对于重型任务,<font color="#c41230"><b>耗时较久</b></font>的任务,需要<font color="#c41230"><b>慎重考虑</b></font>;<br>因为java默认使用ForkJoinPool.commonPool()来执行parallel stream中的操作任务,并且后来的任务不会取消前置的耗时任务,<br>这会导致,如果之前正在对并行流执行重度任务,后来的并行流任务将会等待(当pool已经满载时)<br>
链式调用
使用一个例子来说明stream的链式调用<br>
TODO
高阶函数
高阶函数是指接受另外一个函数作为参数,或返回一个函数的函数
要点解析
基本类型
java中的集中基本类型都有其对应的对象类型,基本类型转换为对象类型称之为<font color="#f1753f">装箱</font>,反之为<font color="#f1753f">拆箱</font>
当在集合中处理基本类型时,都需要引用其对应的装箱类型,例如一个整数列表 List<Integer>,<br>然而在对装箱类型做大量的操作时(比如+)会频繁的装箱和拆箱,降低效率,<br>可以将此种对象类型的stream转换成基本类型流之后再操作<br>
对于int、long和double这三个基本类型,标准库已经提供了对应的高阶映射方法和对应的stream
IntStream, LongStream, DoubleStream<br>
经过特殊处理的流,专用做处理int、long、double等常用数据流,比装箱类型的流更加高效<br>除了基本的流操作之外,提供了例如sum、max、min、average、summaryStatistics等数值计算方法<br>
mapToInt, mapToLong, mapToDouble<br>
分别接受 ToIntFunction<T>, ToLongFunction<T>, ToDoubleFunction<T><br>将一个流转换为IntStream, LongStream, DoubleStream<br>
重载解析
Lambda表达式作为参数时,其类型由它的目标类型推导得出,推导过程遵循如下规则
如果只有一个可能的目标类型,由相应函数接口里的参数类型推导得出
如果有多个可能的目标类型,由最具体的类型推导得出
如果有多个可能的目标类型且最具体的类型不明确,则需人为指定类型
默认方法
类胜于接口 如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法
保证代码向后兼容
子类胜于父类 如果一个接口继承了另一个接口,且两个接口都定义了一个默认方法,那么子类中定义的方法胜出
如果上面两条规则不适用,子类要么需要实现该方法,要么将该方法声明为抽象方法
Optional<br>
Optional<T>可以看做一个容器,用来代替null值<br>null值通常会引发NullPointerException,Optional就是为了更加安全的处理null值<br>
构造
通过Optional.of(T t)工厂方法来获取一个包装了t的Optional对象,要求t非空<br>
通过Optional.ofNullable(T t)工厂方法来获取一个允许t为空的对象
Optional.empty()返回一个空的Optional对象
使用
isPresent()方法判断容器是否为空
ifPresent(Consumer<T> consumer)方法判断容器是否为空,如果不为空,则使用Consumer处理对象,否则不处理<br>
get()方法获取容器中的对象,需要首先判断对象是否存在,如果对象为null,会抛出异常
orElse(T other)方法获取容器中的对象,如果对象为null,则返回提供的默认值other<br>
orElseGet(Supplier<T> other)方法获取容器中的对象,如果对象为null,则通过调用Supplier的get方法获取默认值
filter(Predicate<T> predicate)方法使用提供的断言来检测容器中的对象,<br>如果检测为true,则返回原Optional对象,否则返回空的Optional对象<br>
map(Function<T, U> mapper)方法依据mapper将对象转换为另一个对象<br>
stream元素顺序
如果入流是有序的,则出流也是有序的
如从List得到的流
如果入流是无序的,则出流也是无序的
如从Set得到的流
对于forEach来说,无法保证并行流的顺序是有序的,可以使用forEachOrder来保证按照入流顺序处理<br>
收集器
转换成其他集合
转换为值
参考
https://blog.csdn.net/f641385712/article/details/81506090
0 条评论
下一页