onjava8 函数式编程与流式编程
函数式编程
函数式编程允许用户将代码逻辑封装为函数并且将函数作为参数进行传递
新旧对比代码:
package priv.wzb.javabook.fp;
/**
* 一个接口 接收String类型的参数返回String类型的结果
*/
public interface Strategy {
String approach(String msg);
}
/**
* @program: Design_Pattern
* @description: soft
* @author: yuzuki
* @create: 2020-08-12 16:30
**/
public class Soft implements Strategy {
/**
* 接口的实现 参数/返回值 一致
* @param msg
* @return
*/
@Override
public String approach(String msg) {
return msg.toLowerCase() + "?";
}
}
/**
* @program: Design_Pattern
* @description:
* @author: yuzuki
* @create: 2020-08-12 16:33
**/
public class Unrelated {
/**
* 只是定义一个静态方法 与Strategy的接口参数/返回值 保持一致
* @param msg
* @return
*/
static String twice(String msg){
return msg + " " + msg;
}
}
/**
* @program: Design_Pattern
* @description:
* @author: yuzuki
* @create: 2020-08-12 16:33
**/
public class Strategize {
Strategy strategy;
String msg;
public Strategize(String msg) {
// 在 Strategize 中,你可以看到 Soft 作为默认策略,在构造函数中赋值。
strategy = new Soft(); // [1]
this.msg = msg;
}
void communicate() {
System.out.println(strategy.approach(msg));
}
void changeStrategy(Strategy strategy) {
this.strategy = strategy;
}
public static void main(String[] args) {
Strategy[] strategies = {
new Strategy() { // [2]
// 一种较为简洁且更加自然的方法是创建一个匿名内部类。
// 即便如此,仍有相当数量的冗余代码。
// 你总需要仔细观察后才会发现:“哦,我明白了,原来这里使用了匿名内部类。”
@Override
public String approach(String msg) {
return msg.toUpperCase() + "!";
}
},
// Java 8 的 Lambda 表达式,其参数和函数体被箭头 -> 分隔开。
// 箭头右侧是从 Lambda 返回的表达式。
// 它与单独定义类和采用匿名内部类是等价的,但代码少得多。
msg -> msg.substring(0, 5), // [3]
// Java 8 的方法引用,它以 :: 为特征。
// :: 的左边是类或对象的名称, :: 的右边是方法的名称,但是没有参数列表。
// 这里甚至将非子类的方法传过去了
Unrelated::twice // [4]
};
Strategize s = new Strategize("Hello there");
s.communicate();
for (Strategy newStrategy : strategies) {
s.changeStrategy(newStrategy); // [5]
s.communicate(); // [6]
}
}
}
输出:
hello there?
HELLO THERE!
Hello
Hello there Hello there
lambda+方法引用并没将Java转为函数式语言而是提供函数式编程的支持。
附录:jdk自带的函数式接口
用于确定传参的类型
这种接口可以理解为仅代表某个方法(static关键字应该也能完成此种作用)
实际上Java会自动适配赋值到接口然后编译器在后台把方法引用或lambda表达式包装进目标接口的类的实例
特征 | 函数式方法名 | 示例 |
---|---|---|
无参数; 无返回值 | Runnable (java.lang) run() | Runnable |
无参数; 返回类型任意 | Supplier get() getAs类型() | SupplierBooleanSupplierIntSupplierLongSupplierDoubleSupplier |
无参数; 返回类型任意 | Callable (java.util.concurrent) call() | Callable |
1 参数; 无返回值 | Consumer accept() | ConsumerIntConsumerLongConsumerDoubleConsumer |
2 参数 Consumer | BiConsumer accept() | BiConsumer<T,U> |
2 参数 Consumer; 1 引用; 1 基本类型 | Obj类型Consumer accept() | ObjIntConsumerObjLongConsumerObjDoubleConsumer |
1 参数; 返回类型不同 | Function apply() To类型 和 类型To类型 applyAs类型() | Function<T,R>IntFunctionLongFunctionDoubleFunctionToIntFunctionToLongFunctionToDoubleFunctionIntToLongFunctionIntToDoubleFunctionLongToIntFunctionLongToDoubleFunctionDoubleToIntFunctionDoubleToLongFunction |
1 参数; 返回类型相同 | UnaryOperator apply() | UnaryOperatorIntUnaryOperatorLongUnaryOperatorDoubleUnaryOperator |
2 参数类型相同; 返回类型相同 | BinaryOperator apply() | BinaryOperatorIntBinaryOperatorLongBinaryOperatorDoubleBinaryOperator |
2 参数类型相同; 返回整型 | Comparator (java.util) compare() | Comparator |
2 参数; 返回布尔型 | Predicate test() | PredicateBiPredicate<T,U>IntPredicateLongPredicateDoublePredicate |
参数基本类型; 返回基本类型 | 类型To类型Function applyAs类型() | IntToLongFunctionIntToDoubleFunctionLongToIntFunctionLongToDoubleFunctionDoubleToIntFunctionDoubleToLongFunction |
2 参数类型不同 | Bi操作 (不同方法名) | BiFunction<T,U,R>BiConsumer<T,U>BiPredicate<T,U>ToIntBiFunction<T,U>ToLongBiFunction<T,U>ToDoubleBiFunction |
这些接口会在下面的流式编程中大量使用用于传递编程者自定义的实现
流式编程
流式编程是对集合操作的简化,带来代码难理解的问题?可能是对流式i编程的不理解,可添加注释表明流式编程语句段用于实现什么功能。
流式编程的出现可配合函数式编程+builder设计模式,每次的调用都返回集合对象,方法中传入的参数是Java预先定义的函数式接口(上面附录中出现的接口)可使用lambda表达式进行快速实现,简化大量代码。
流式编程代码举例:
package priv.wzb.javabook.streams;
import java.util.Random;
/**
* @program: Design_Pattern
* @description:
* @author: yuzuki
* @create: 2020-08-6
**/
public class Randoms {
public static void main(String[] args) {
new Random(47)
// 创建随机数的流,
.ints(5,20)
// 唯一性
.distinct()
// 限制7个
.limit(7)
// 排序
.sorted()
// 遍历操作
.forEach(System.out::println);
}
}
6
10
13
16
17
18
19
流操作包括:
- 创建流
- 修改流元素(中间操作,Intermidate Operations)
- 消费流元素(终端操作,Terminal Operations)
流创建
-
Stream.of
Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!") .forEach(System.out::print);
-
.stream
Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split(" "))); w.stream() .map(x -> x + " ") .forEach(System.out::print);
中间操作
跟踪和调试
使用peek遍历元素的时候输出
使用map传入函数对每个数据进行遍历操作
package priv.wzb.javabook.streams;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import java.util.stream.Stream;
/**
* @program: Design_Pattern
* @author: yuzuki
* @create: 2020-10-02 11:02
* @description:
**/
public class FileToWords {
public static Stream<String> stream(String filePath)
throws Exception {
return Files.lines(Paths.get(filePath))
.skip(1) // First (comment) line
.flatMap(line ->
Pattern.compile("\\W+").splitAsStream(line));
}
}
// streams/Peeking.java
class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.limit(4)
.map(w -> w + " ")
.peek(System.out::print)
.map(String::toUpperCase)
.peek(System.out::print)
.map(String::toLowerCase)
.forEach(System.out::print);
}
}
Well WELL well it IT it s S s so SO so
文件内容:
first
well
it
s
so
map接口:
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
显然用到了函数接口Function 1参数不同返回类型
使用函数式接口代表可传入函数式的逻辑代码,在流中进行自定义的函数式操作
peek接口
/**
* Returns a stream consisting of the elements of this stream, additionally
* performing the provided action on each element as elements are consumed
* from the resulting stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* <p>For parallel stream pipelines, the action may be called at
* whatever time and in whatever thread the element is made available by the
* upstream operation. If the action modifies shared state,
* it is responsible for providing the required synchronization.
*
* @apiNote This method exists mainly to support debugging, where you want
* to see the elements as they flow past a certain point in a pipeline:
* <pre>{@code
* Stream.of("one", "two", "three", "four")
* .filter(e -> e.length() > 3)
* .peek(e -> System.out.println("Filtered value: " + e))
* .map(String::toUpperCase)
* .peek(e -> System.out.println("Mapped value: " + e))
* .collect(Collectors.toList());
* }</pre>
*
* @param action a <a href="package-summary.html#NonInterference">
* non-interfering</a> action to perform on the elements as
* they are consumed from the stream
* @return the new stream
*/
Stream<T> peek(Consumer<? super T> action);
1参数无返回值的Consumer接口
返回Stream可进行后续操作
排序
// streams/SortedComparator.java
import java.util.*;
public class SortedComparator {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(10)
.limit(10)
// sorted排序Comparaotr去逆序
.sorted(Comparator.reverseOrder())
// map代表函数式编程,把流元素传到函数中操作
.map(w -> w + " ")
.forEach(System.out::print);
}
}
还有元素移除等操作
终端操作
以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作(Terminal Operations)总是我们在流管道中所做的最后一件事。
数组
toArray()
:将流转换成适当类型的数组。toArray(generator)
:在特殊情况下,生成自定义类型的数组。
循环
forEach(Consumer)
常见如System.out::println
作为 Consumer 函数。forEachOrdered(Consumer)
: 保证forEach
按照原始流顺序操作。
集合
collect(Collector)
:使用 Collector 收集流元素到结果集合中。collect(Supplier, BiConsumer, BiConsumer)
:同上,第一个参数 Supplier 创建了一个新的结果集合,第二个参数 BiConsumer 将下一个元素收集到结果集合中,第三个参数 BiConsumer 用于将两个结果集合合并起来。
组合
reduce(BinaryOperator)
:使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional。reduce(identity, BinaryOperator)
:功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果。reduce(identity, BiFunction, BinaryOperator)
:更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合map()
和reduce()
来更简单的表达它。
匹配
allMatch(Predicate)
:如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。anyMatch(Predicate)
:如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 是停止执行计算。noneMatch(Predicate)
:如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。
查找
findFirst()
:返回第一个流元素的 Optional,如果流为空返回 Optional.empty。findAny(
:返回含有任意流元素的 Optional,如果流为空返回 Optional.empty。
信息
count()
:流中的元素个数。max(Comparator)
:根据所传入的 Comparator 所决定的“最大”元素。min(Comparator)
:根据所传入的 Comparator 所决定的“最小”元素。
数字流信息
average()
:求取流元素平均值。max()
和min()
:数值流操作无需 Comparator。sum()
:对所有流元素进行求和。summaryStatistics()
:生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。
小结
流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。
函数式编程配合流式编程简化开发,方便代码复用。