java 8 新特性功能和用法介绍---Java Stream API
Java Stream API
Stream 是java 8最重要的亮点,它跟java.io包中inputStream和outStream是不同的概念。 它是对集合对象功能的增强,
它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。
同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。
下面从以下几个进行讨论:
- 函数式接口
- Stream流操作
- 聚合操作
- Optional使用介绍
函数式接口
函数式接口是java 8对一类特殊类型接口的称呼。该类接口定义了唯一抽象方法的接口,并使用注解@FunctionalInterface注释该接口。
jdk8 最直接的支持就是java.util.function包,并且定义四个最基础的函数接口:
接口 | 参数 | 返回值 | 类别 |
---|---|---|---|
Consumer | T | void | 消费性接口 |
Supplier | None | T | 供给型接口 |
Function | T | R | 函数型接口 |
Predicate | T | boolean | 断言型接口 |
消费型接口
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
......
......
}
该接口表示一个接受单个输入参数并且没有返回值的操作。不像其它函数式接口,Consumer接口期望执行带有副作用的操作(Consumer的操作可能会更改输入参数的内部状态)。
具体使用示例
public void testConsumer(){
consumer.accept("今天天气真好");
}
Consumer<String> consumer = message -> System.out.println(message);
供给型接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T
具体使用示例
public void testSupplier(){
Supplier<Cat> catSupplier = Cat::new;
Cat cat = catSupplier.get();
cat.setName("test");
cat.setColour("black");
System.out.println(cat.getName());
}
函数型接口
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
......
}
数据转换器,接收一个 T 类型的对象,返回一个 R类型的对象; 单参数单返回值的行为接口;提供了 apply, compose, andThen, identity 方法;
public void testFunction() {
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
System.out.println(countBlackCat.apply(cats));
}
Function<List<Cat>, Long> countBlackCat = cats -> cats.stream().filter(cat -> "black".equals(cat.getColour())).count();
断言型接口
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
......
}
接收一个 T 类型的对象,返回布尔值,通常用于传递条件函数; 单参数布尔值的条件性接口。提供了 test (条件测试)
public void testPredicate(){
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
cats.stream().filter(filterCatName).forEach(cat-> System.out.println(cat.getName()));
}
Predicate<Cat> filterCatName = cat -> cat.getName().length() == 3;
Stream 流操作
java 8 stream流是对集合操作的抽象,可以执行复杂查询、过滤、映射、排序和聚合等操作,其特点是
不改变原有数据结构,不保存数据以及是Lazy操作,只在最终执行终止操作时,将中间记录的过程,进行求值操作。
其主要分为四类接口
- 中间转换操作 filter、map、flatMap、peek、limit、sorted、skip等
- 终止操作 toArray、reduce、collect、min、max、count、anyMatch、allMatch等
- 直接遍历 也是终止操作一种 forEach、forEachOrdered
- 创建流 empty、of、generate等
创建流
创建流有两种1.通过Stream接口的静态方法构造 2.通过Collection接口的默认方法stream()进行转换
public void testCreateStream() {
int sum = Stream.of(1, 3, 4).mapToInt(Integer::intValue).sum();
int max = Arrays.asList(1, 3, 4).stream().mapToInt(Integer::intValue).max().orElse(0);
System.out.println("和为" + sum + ", 最大值为" + max);
}
中间转换
转换流实际上将原始流通过某些行为操作转换另一个新流,下面介绍几个非常常用的转换流
filter过滤
对流中包含的元素使用给定的过滤函数进行过滤操作,那么新生成的流只包含符合条件的元素
public void testPredicate(){
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
cats.stream().filter(filterCatName).forEach(cat-> System.out.println(cat.getName()));
}
Predicate<Cat> filterCatName = cat -> cat.getName().length() == 3;
上面通过filter过滤出猫的名称长度等于3,满足该条件的进行输出
map转换
对于流中包含的元素使用给定的转换函数进行转换操作,新生成的流只包含转换后的元素
public void testMapStream() {
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
cats.stream().map(Cat::getColour).forEach(System.out::println);
}
上面通过map操作将cat猫的实体转换成猫的名称,并输出
flatMap 扁平化转换
与map类似,不同的是元素是流,扁平化为正常元素,再进行元素转换
public void testFlatMapStream(){
List<People> peopleList = Arrays.asList(new People("张三", 17, Arrays.asList(new Cat("tom", "black")
, new Cat("jack", "white"))),new People("李四", 19, Arrays.asList(new Cat("a", "black")
, new Cat("b", "white"))));
//统计养黑猫的个数
long count = peopleList.stream().flatMap(people -> people.getCats().stream()).filter(cat -> "black".equals(cat.getColour())).count();
System.out.println(count);
}
代码中flatMap 里面是个猫的流,通过flatMap转换成猫的元素,再通过filter过滤出黑猫,最后count统计个数
peek
peek是指将原有stream流的所有元素,经过peek消费,生成一个包含原有元素的新stream流
public void testPeek(){
int sum = Stream.of(1, 3, 4).peek(System.out::println).mapToInt(Integer::intValue).sum();
System.out.println(sum);
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
long black = cats.stream().peek(cat -> cat.setColour("black")).filter(cat -> "black".equals(cat.getColour())).count();
System.out.println(black);
}
上面代码可以看出求和,peek是打印出原有元素,并没有改变。统计黑猫时,我们peek里面改变了猫对象的颜色
最终统计黑猫树是3.这是因为peek接受的是一个Consumer,而void Consumer 是没有返回值,所以他的修改是回不到流中的,所以流的元素还是
原来的元素,这样就可以解释,为什么猫对象中颜色被改变,其实还是原有对象,并没有生产新的对象,这个与ma操作有很大区别。
limit,skip
limit 是对stream进行截取操作,获取前N个元素,而skip是对stream跳过操作,跳过前M个元素
public void testLimitSkip() {
int sum = Stream.of(1, 3, 4, 6, 7, 8, 9).skip(3).limit(3).peek(System.out::println).mapToInt(Integer::intValue).sum();
System.out.println(sum);
}
代码意义是跳过前3个元素,截取前3个元素,最终输出是6,7,8,和为21
流的聚合操作
聚合操作接受一个元素的输入,经过反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。
Collect可变聚合
collect聚合实际上将元素收集到一个结果容器中
/* *@param <R> type of the result
* @param supplier a function that creates a new result container. For a
* parallel execution, this function may be called
* multiple times and must return a fresh value each time.
* @param accumulator an <a href="package-summary.html#Associativity">associative</a>,
* <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function for incorporating an additional element into a result
* @param combiner an <a href="package-summary.html#Associativity">associative</a>,
* <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function for combining two values, which must be
* compatible with the accumulator function
* @return the result of the reduction
*/
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
三个参数的含义:Supplier supplier是一个工厂函数,用来生成一个新的容器;BiConsumer accumulator也是一个函数,用来把Stream中的元素添加到结果容器中;BiConsumer combiner还是一个函数,用来把中间状态的多个结果容器合并成为一个(并发的时候会用到)
/** @param <R> the type of the result
* @param <A> the intermediate accumulation type of the {@code Collector}
* @param collector the {@code Collector} describing the reduction
* @return the result of the reduction
* @see #collect(Supplier, BiConsumer, BiConsumer)
* @see Collectors
*/
<R, A> R collect(Collector<? super T, A, R> collector);
Java8还给我们提供了Collector的工具类,正常都使用该工具类
下面列举几个工具类常用的操作
public void testCollect() {
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
//使用toList,获取所有猫名
List<String> catNames = cats.stream().map(Cat::getName).collect(Collectors.toList());
//使用groupingBy ,按照颜色分类
Map<String, List<Cat>> colourGroup = cats.stream().collect(Collectors.groupingBy(Cat::getColour));
//使用toMap转换成key,value键值对,假设猫名唯一
Map<String, String> catMap = cats.stream().collect(Collectors.toMap(Cat::getName, Cat::getColour));
//使用reducing将猫名合并按照逗号分隔
String name = cats.stream().map(Cat::getName).collect(Collectors.reducing((name1, name2) -> name1 + "," + name2)).orElse("");
}
其他汇聚操作
主要是reduce方法,该方法非常同意,比如stream里面count、sum、max、min都可以使用其实现
public void testReduce(){
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
//统计猫的颜色种类
long colourCount = cats.stream().map(Cat::getColour).distinct().count();
//reduce将猫名合并按照逗号分隔
String name = cats.stream().map(Cat::getName).reduce((name1, name2) -> name1 + "," + name2).orElse("");
}
并行流
parallelStream其实就是一个并行执行的流.它通过默认的ForkJoinPool,可能提高你的多线程任务的速度.
public void testParallelStream(){
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
//统计猫的颜色种类
long count = cats.parallelStream().map(Cat::getColour).distinct().count();
//错误用法
Set<String> colourSet = new HashSet<>();
cats.parallelStream().map(Cat::getColour).forEach(col-> colourSet.add(col));
long catCount = colourSet.size();
}
parallelStream里直接去修改变量是非线程安全的,但是采用collect和reduce操作就是满足线程安全,
另外什么时候使用parallelStream,需要考虑任务大小,运行机器是否多核去决定,并不是说并行比串行就快,所以大家谨慎使用。
Optional
为什么单独把Optional 给单独拎出来说一说,因为java 8 设计它本身来替换掉null,因此java 8 引入了一个名为java.util.Optional的新的类。
本身我们在定义接口方法时,如果对方法返回明确有空异常,那么定义成optional,别人引用的时候,肯定需要
判断空异常,这样更加明确。
创建Optional对象
- Optional.empty:声明一个空的Optional;
- Optional.of:依据一个非空值创建Optional;
- Optional.ofNullable:可接受null的Optional;
public Optional<Cat> testOptional() {
List<Cat> cats = Arrays.asList(new Cat("tom", "black"), new Cat("jack", "white")
, new Cat("lily", "black"));
//查询一个名字叫lily的猫
return cats.stream().filter(cat -> "lily".equals(cat.getName())).findFirst();
}
本文地址:https://blog.csdn.net/weixin_43946605/article/details/109577044
上一篇: php提高脚本性能的4个技巧
下一篇: Java代码块—在程序中的执行顺序
推荐阅读
-
java 8 新特性功能和用法介绍03---CompletableFuture基本用法介绍
-
java 8 新特性功能和用法介绍---Java Stream API
-
深入理解Java8新特性之Stream API的终止操作步骤
-
java 8新特性2 Stream、新的时间API
-
深入理解Java8新特性之Stream API的创建方式和中间操作步骤
-
Java8新特性之Stream API详解
-
实例理解Java8新特性中Stream API和Optional类的使用
-
java 8 新特性功能和用法介绍03---CompletableFuture基本用法介绍
-
【Java 20】Java8的其他新特性 - Lambda表达式、函数式接口、方法引用、构造器引用、数组引用、Stream API、Optional类
-
java 8 新特性功能和用法介绍---Java Stream API