1. 函数式编程
函数式编程语言操纵代码片段就像操作数据一样容易。虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程
1.0 函数式接口
定义:接口中有且只有一个抽象方法,便称为函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
复制代码
@FunctionalInterface:用于一个接口的定义上,一旦使用该注解定义接口,则编译器将会强制检查该接口是否确实有且只有一个抽象方法,否则报错。
1.1 Lambda表达式
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
特征
- 可选类型声明:可以不声明参数类型,编译器将统一识别参数值
- 可选参数圆括号():一个参数可以不定义圆括号,但没有参数或多个参数需要定义圆括号
- 可选大括号{}:若 Lambda 体只有一个语句,可以不写 {}
- 可选返回关键字 return:若 Lambda 体只有一个表达式返回值,则可以省略 return 和 {}
(params) -> expression
或
(params) ->{ statements; }
//参数 -> { 方法体 }
//1. 参数。
//2. 接着` ->`,可视为“产出”。
//3. `->` 之后的内容都是方法体。
例:
// 1. 不需要参数,返回值为 5
() -> 5
//相当于 () -> return 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
常见函数式接口
位于java.util.function包下
1.2 方法引用
所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。
定义:"::"引用运算符,包含引用运算符的表达式称为方法引用
语法
- 对象引用::实例方法名
System.out::println
- 类名::静态方法名
Math::random
- 类名::实例方法名
Product::getName
- 类名::new
Product::new
2. 流式编程
流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。
众所周知,集合操作非常麻烦,若要对集合进行筛选、投影,需要写大量的代码,而流是以声明的形式操作集合,它就像SQL语句,我们只需告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你,无需我们自己手写代码。
因此,流的集合操作对我们来说是透明的,我们只需向流下达命令,它就会自动把我们想要的结果给我们。由于操作过程完全由Java处理,因此它可以根据当前硬件环境选择最优的方法处理,我们也无需编写复杂又容易出错的多线程代码了。
原文链接:https://blog.csdn.net/u010425776/article/details/52344425
Stream 流式编程的特点:
-
只能遍历一次
我们可以把流想象成一条流水线,流水线的源头是我们的数据源(一个集合),数据源中的元素依次被输送到流水线上,我们可以在流水线上对元素进行各种操作。一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然,我们可以从数据源那里再获得一个新的流重新遍历一遍。
-
采用内部迭代方式
若要对集合进行处理,则需我们手写处理代码,这就叫做外部迭代。而要对流进行处理,我们只需告诉流我们需要什么结果,处理过程由流自行完成,这就称为内部迭代。
-
支持函数式编程和链式操作
Stream流操作流程
- 准备一个数据源
- 执行中间操作(Intermediate Operations):中间操作会返回一个新的流,一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。而是在终端操作开始的时候才真正开始执行。
- 执行终端操作(Terminal Operations):是指返回最终的结果。一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
2.1 创建Stream
要使用Stream
,就必须先创建Stream
对象,创建Stream
对象有以下几种方式。
(1) 直接将几个值变成流对象 – Stream.of()
Stream<String> stream = Stream.of("chaimm","peter","john");
(2) 通过集合创建 – collection.stream()
List<Person> list = new ArrayList<Person>();
Stream<Person> stream = list.stream();
(3) 通过数组创建 – Arrays.stream(arr)
通过Arrays类提供的静态函数stream()获取数组的流对象:
String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
(4) 通过文件创建流
Java7中引入了处理文件I/O操作的NIO API(非阻塞I/o),便于使用Stream API。
java.nio.file.Files
中的很多静态方法都会返回一个流。如, Files.lines()
方法
try(Stream lines = Files.lines(Paths.get(“文件路径名”),Charset.defaultCharset())){
//可对lines做一些操作
}catch(IOException e){
}
(5) 基于Supplier
创建Stream
还可以通过Stream.generate()
方法,它需要传入一个Supplier
对象:
Stream<String> s = Stream.generate(Supplier<String> sp);
基于Supplier
创建的Stream
会不断调用Supplier.get()
方法来不断产生下一个元素,这种Stream
保存的不是元素,而是算法,它可以用来表示无限序列。
例如,我们编写一个能不断生成自然数的Supplier
,它的代码非常简单,每次调用get()
方法,就生成下一个自然数:
public class Main {
public static void main(String[] args) {
Stream<Integer> natual = Stream.generate(new NatualSupplier());
// 注意:无限序列必须先变成有限序列再打印:
natual.limit(20).forEach(System.out::println);
}
}
class NatualSupplier implements Supplier<Integer> {
int n = 0;
public Integer get() {
n++;
return n;
}
}
2.2 常用中间操作
中间操作不会消耗流,只是将一个流转换成另外一个流,类似于流水线。
(1) 筛选filter()
filter函数接收一个Lambda表达式作为参数,返回值为boolean类型,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。
Collection<Integer> collection = new LinkedList();
collection.add(1);
collection.add(2);
collection.add(3);
collection.add(3);
System.out.println("过滤大于2的整数:"+collection.stream()
.filter(n -> n > 2)
.collect(Collectors.toList()));
//过滤大于2的整数:[3, 3]
(2) 去重distinct()
System.out.println("去重:" + collection.stream()
.distinct().
collect(Collectors.toList()));
//去重:[1, 2, 3]
(3) 截取limit()
截取流的前三个元素:
System.out.println("截取流的前两个:" + collection.stream()
.limit(2)
.collect(Collectors.toList()));
//截取流的前两个:[1, 2]
(4) 跳过skip()
跳过流的前3个元素:
System.out.println("跳过流的前3个元素:" +
collection.stream()
.skip(3)
.collect(Collectors.toList()));
//跳过流的前3个元素:[3]
(5) 排序sorted()
将流中的元素按照自然排序方式进行排序。
List<Integer> nums = Arrays.asList(1, 2, 5, 6, 8, 3);
System.out.println(nums.stream()
.sorted()
.collect(Collectors.toList())
);
System.out.println("降序:"+nums.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList())
);
(6) map()
将流中的元素进行再次加工形成一个新流,流中的每一个元素映射为另外的元素。
flatMap()
扁平化映射,它具体的操作是将多个stream连接成一个stream,这个操作是针对类似多维数组的,比如集合里面包含集合,相当于降维作用。
peek()
对流中每个元素执行操作,并返回一个新的流,返回的流还是包含原来流中的元素。
- map()
- flatMap()
- distinct()
- sorted()
- peek()
- limit()
- skip()
2.3 终端操作
终端操作会消耗流,以产生一个最终结果,终端操作完成后,流就被消耗了,不可再调用相关操作流的方法。
方法 | 说明 |
---|---|
forEach(Consumer action) | 遍历流中所有元素,对每个元素执行action |
toArray() | 将流中所有元素转换为一个数组 |
reduce() | 该方法有三个重载的版本,都用于通过某种操作来合并流中的元素 |
min() | 返回流中所有元素的最小值 |
max() | 返回流中所有元素的最大值 |
count() | 返回流中所有元素的数量 |
anyMatch(Predicate predicate) | 判断流中是否至少包含一个元素符合 Predicate 条件。 |
allMatch(Predicate predicate) | 判断流中是否每个元素都符合 Predicate 条件 |
noneMatch(Predicate predicate) | 判断流中是否所有元素都不符合 Predicate 条件 |
findFirst() | 返回流中的第一个元素 |
findAny() | 返回流中的任意一个元素 |
- forEach()
- forEachOrdered()
- toArray()
- reduce()
- collect()
- min()
- max()
- count()
- anyMatch()
- allMatch()
- noneMatch()
- findFirst()
- findAny()
3. Optional<T>
Optional<T>
是 Java 8 新加的容器,只存放0个或1个元素,用于防止出现 NullpointException
boolean isPresent()
:判断容器是否有值
T get()
:获取容器中的元素,若容器为空则抛出 NoSuchElement 异常。
List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
Optional<Integer> first = list.stream().findFirst();
if (first.isPresent()) {
System.out.println("返回第一个元素:" + first.get());
}
// 更优雅的方式
first.ifPresent(integer -> System.out.println("返回第一个元素:" + integer));
Optional<Integer> anyEle = list.stream().findAny();
System.out.println(anyEle.get());
参考:
评论区