文章目录
Java8新特性
面试问到你Java8的新特性,你能答上来吗
Java现在更新到了很高的版本,不过不建议它一更新我们就去了解、就去学。因为很少概率能用的到,现在很多公司或企业使用的版本都是比较低的。新版本需要经过岁月的考验,才能知道哪些特性是好用的
哪些是不好用的。
Java8可以看成自Java5以来最具有革命性的版本,**非常推荐**学习Java8的新特性。
在学习的时候,会遇到的非常多听着、看着都一头雾水的概念。不要被劝退,看代码,带着疑问去学习。怎么去实现?它能用来干嘛?
第一遍学习完过后,只是给自己了解到,“哦,原来有这个东西,可以这么样子用”,想进一步掌握,还是需要多敲代码,多思考。
Java8新特性简介
说一说Java8的新特性?
1.lambda表达式:
- 速度更快
- 代码更少(增加了新的语法 )
2.强大的Stream API
-
并行流和串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率
Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel() 与 sequential()在并行流与顺序流之间进行切换。
-
便于并行
3.Optional:
- 最大化减少空指针异常
- Nashorn引擎,允许在JVM上运行JS应用
4.时间和日期API
1、Lambda 表达式(重点)
关于Lambda 表达式和函数式接口,参考我的另一篇文章 Lambda表达式(重量级新特性)
2、函数式接口
1、使用lambda必须要具有接口,且要求接口中有且只有一个抽象方法。
无论是JDK内置的Runnable,Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才能使用lambda。
2、使用lambda必须具有上下文推断。
也就是方法的参数或者局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个的抽象方法的接口,称为“函数式接口”。
//添加函数式接口的注解,表示限定该接口只能有一个方法体
@FunctionalInterface
public interface Test01 {
void test01();//方法体1
//void test02();
//方法体2,如果添加了函数式接口注解,该接口有超过一个方法,将会编译报错
}
如何理解函数式接口
Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,java不但可以支持OOP还可以支持OOF(面向函数编程)!
在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
2.1、Java内置四大函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier 供给型接口 | 无 | T | 返回类型为T的对象,包含方法: T get() |
Function<T,R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法: R apply(T t) |
Predicate 断定型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t) |
下面用代码举两个例子
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
public class Test02 {
//1.消费型接口
public void shopping(double money, Consumer<Double> con) {
con.accept(money);
}
//2.断定型。根据给定的规则,过滤集合中的字符串。规则在Predicate制定
public List<String> filterString(List<String> list, Predicate<String> pre) {
List<String> filterString = new ArrayList<>();
for (String s : list) {
if (pre.test(s)) { //这里只是判断,在使用的时候再创建规则传过来,灵活。
filterString.add(s);
}
}
return filterString;
}
//main方法
public static void main(String[] args) {
Test02 test02 = new Test02();
//1.测试消费型接口
//普通写法
test02.shopping(400, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("买了一个鸡肉卷,花了" + aDouble);
}
});
//Lambda表达式
test02.shopping(500, (money) -> System.out.println("买了一个汉堡包,花了" + money));
//2.测试断定型接口
List<String> list = Arrays.asList("冬瓜", "西瓜", "南瓜", "北瓜", "种豆", "得瓜");
//普通写法
List<String> list1 = test02.filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
//制定规则,如果s里面存在 瓜 ,返回true
return s.contains("瓜");
}
});
System.out.println(list1);
//Lambda表达式
List<String> list2 = test02.filterString(list, (s) -> !s.contains("瓜"));//这里是不存在瓜,返回true
System.out.println(list2);
}
}
还有非常多的内置函数接口,这只是用得比较多的四个。
学习这里的时候,觉得,哎好像我直接定义一个方法就能实现同样的功能了吖,为什么还要这么复杂,还要多写这些函数式接口。
后面发现,使用这些接口,能非常的灵活。比如断定型,假如有多种过滤规则,列如上面的,一个过滤含有“瓜”的字符串,一个过滤不含有的。如果我们不使用函数式接口的话,那就是写两个方法,而且代码大部分都是一样的,造成了冗余,如果是有几十个几百个规则呢?
而函数式接口就是提供了一个模板,我们只需要在使用的时候,顺便将规则传进来就可以了。并不需要修改源码。
3、方法引用和构造器引用(Stream API中用到)
3.1、方法引用
方法引用(Metod References)
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
- 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法。
- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
- 格式:使用操作符
::
将类(或对象)与方法名分隔开来。 - 如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
看不懂。往下看代码,待会再回来看。
用代码实现三种使用情况,将会用到上面的四个内置函数式接口。
先建一个实体类Employee
import java.util.Objects;
//定义一个员工实体类
public class Employee {
private int id;
private String name;
private int age;
private double salary;
public Employee() {
System.out.println("调用了无参构造");
}
public Employee(int id) {
this.id = id;
System.out.println("调用了id参数构造");
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
System.out.println("调用了双参构造");
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return id == employee.id &&
age == employee.age &&
Double.compare(employee.salary, salary) == 0 &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, salary);
}
}
提供测试数据类:
package com.yong.method;
import java.util.ArrayList;
import java.util.List;
/**
* @Author cyh
* @Date 2021/8/23 16:52
*/
/**
* 提供测试数据
*/
public class EmployeeData {
public static List<Employee> getEmployees() {
List<Employee> list = new ArrayList<>();
list.add(new Employee(1001,"貂蝉",30,9808));
list.add(new Employee(1002,"西施",32,5132));
list.add(new Employee(1003,"杨玉环",18,4567));
list.add(new Employee(1004,"王昭君",32,7845));
list.add(new Employee(1005,"西门庆",22,6954));
list.add(new Employee(1006,"菠萝吹雪",62,4521));
list.add(new Employee(1007,"韩信",27,9874));
list.add(new Employee(1008,"王大锤",48,2135));
return list;
}
}
测试:
import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 方法引用的使用
* 1.对象::实例方法名
* 2.类::静态方法名
* 3.类::实例方法名
*/
public class Demo01 {
public static void main(String[] args) {
Demo01 method = new Demo01();
//1
method.test01();
method.test01_1();
//2
method.test02();
//3
method.test03();
method.test03_01();
}
/**
* 1.当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
* 对象::实例方法
*/
public void test01() {
//lambda写法
Consumer<String> con1 = s -> System.out.println(s);//打印s的值,s的值哪里来
con1.accept("乌鸦坐飞机");//s的值从这里来,accept方法接收一个值给s
/**方法引用写法
*
* System是一个类
* System.out: out是PrintStream类型的一个常量
* void println:println是PrintStream的一个方法
* 如果使用这个,那么这种情况就是lambda已经有实现的方法了,下面使用方法引用
* 对象::实例方法名
*/
PrintStream ps = System.out;
Consumer<String> con2 = ps::println; //Consumer无返回值
con2.accept("老鹰捉小鸡");
/**运行结果
* 乌鸦坐飞机
* 老鹰捉小鸡
*/
}
public void test01_1() {
//再举一个例子说明,带返回值的使用Supplier
Employee employee = new Employee(1100, "张三", 45, 2255);
//lambda
Supplier<String> sup1 = () -> employee.getName(); //注意泛型和返回类型保持一致
System.out.println(sup1.get()); //输出:张三
//方法引用
Supplier<String> sup2 = employee::getName;
System.out.println(sup2.get()); //输出:张三
}
/**
* 2.类::静态方法名
* Integer中的compare是静态方法,小于返回-1,等于返回0,大于返回1
* static int compare(int x, int y)
*/
public void test02(){
//lambda
Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
System.out.println(com1.compare(33,44)); // -1
//类::静态方法名
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(63,44)); // 1
}
/**
* 3.类::实例方法名
* String中的compareTo是一个实例方法
* int compareTo(String anotherString)
*/
public void test03(){
//lambda
Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc", "abc"));//返回的是比较的第一个不同的ASCII值的差值,相同返回0
//类::实例方法名
Comparator<String> com2 = String::compareTo;
System.out.println(com2.compare("ccc", "cbb"));
}
public void test03_01(){
Employee employee = new Employee(1001, "李四", 22, 8888);
//lambda
Function<Employee, String> fun1 = employee1 -> employee.getName();
System.out.println(fun1.apply(employee));//李四
//类::实例方法名
Function<Employee, Integer> fun2 = Employee::getId; //注意这里是类Employee,而不是对象employee
System.out.println(fun2.apply(employee));//1001
}
}
3.2、构造器引用
一、构造器引用
和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型。
直接看代码
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class Demo02 {
//构造器引用的使用
public static void main(String[] args) {
Demo02 constructor = new Demo02();
//无参构造
constructor.test01();
//单参构造
constructor.test02();
//双参构造
constructor.test03();
}
//无参构造
public void test01(){
Supplier<Employee> sup1 = () -> new Employee(); //Supplier<T>,返回值类型为T
Employee employee = sup1.get(); //生成了一个对象
System.out.println(employee);
Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2.get());
/**运行结果
* 调用了无参构造
* Employee{id=0, name='null', age=0, salary=0.0}
*/
}
//单参构造
public void test02(){
Function<Integer, Employee> fun1 = Employee::new; //Function<T,R>,对类型为T的对象应用操作,并返回R类型的结果
Employee employee = fun1.apply(1002);
System.out.println(employee);
/**
* 调用了id参数构造
* Employee{id=1002, name='null', age=0, salary=0.0}
*/
}
//双参构造
public void test03(){
//BiFunction中的R apply(T t,U u)
BiFunction<Integer,String,Employee> fun = Employee::new;
Employee employee = fun.apply(1889, "王五");
System.out.println(employee);
/**运行结果
* 调用了双参构造
* Employee{id=1889, name='王五', age=0, salary=0.0}
*/
}
}
将数组看成一个特殊的类即可像构造器引用一样使用。
import java.util.Arrays;
import java.util.function.Function;
public class Demo03 {
//数组引用的使用
public static void main(String[] args) {
Demo03 arrays = new Demo03();
arrays.test01();
arrays.test02();
}
public void test01() {
Function<Integer, String[]> fun = String[]::new;
String[] strings = fun.apply(3);
System.out.println(Arrays.toString(strings));
//[null, null, null]
}
public void test02(){
Function<Integer,Employee[]> fun =Employee[]::new;
Employee[] e = fun.apply(3);
System.out.println(Arrays.toString(e));
//[null, null, null],只是创建了一个容器,并没有对象在里面
}
}
4、Stream API(重点)
4.1、强大的Stream API
-
Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个则是Stream API。
-
Stream APl ( java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
-
Stream是 Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
-
使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。
简言之,Stream API提供了一种高效且易于使用的处理数据的方式。
为什么要使用Stream API?
- 在实际开发中,项目中大多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。(过滤数据:一个是在数据库中过滤,一个是在java层面过滤)
- Stream和 Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
什么是Stream?
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,Stream讲的是计算!
注意:
- Stream 自己不会存储元素。I
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream的操作步骤
-
创建Stream
一个数据源(比如:集合、数组),获取一个流对象
-
进行中间操作
一个中间操作链,对数据源的数据进行想要的处理,只有中间操作不执行!
-
终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。并且不能再被二次使用。
4.2、创建Stream的方式
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
//获取数据
List<Employee> employees = EmployeeData.getEmployees();
//创建方式1,返回一个数据的顺序流,stream
Stream<Employee> stream1 = employees.stream();
//创建方式2,返回一个并行流,parallelStream
Stream<Employee> stream2 = employees.parallelStream();
//创建方式3,通过数组
String[] strs = new String[]{"sda", "dsa", "2ads"};
Stream<String> stream3 = Arrays.stream(strs);
//创建方式4,通过Stream的of()
Stream<Integer> stream4 = Stream.of(1, 2, 3, 4, 5);
//创建方式5:创建无限流
/**
* 假设想遍历前10个偶数,下面这么写就是,从0开始,拿到加了2的值,但里面那层又会执行,不停的无限迭代。
*/
//无限流,forEach是终止操作
//Stream.iterate(0, t -> t + 2).forEach(System.out::println);//会无限打印偶数。
//加一个中间操作,遍历前10个偶数。
Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);
//生成10个随机数,generate
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
}
4.3、Stream的中间操作
多个中间操作
可以连接起来形成一个流水线
,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!
而在终止操作时一次性全部处理
,这称为惰性求值
。
1.筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的 hashCode()和equals()去除重复元素 |
limit(long maxsize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补 |
测试:
过滤 filter:
用上面提供的测试数据,使用流的方法获取工资大于6000的员工的信息
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.List;
import java.util.stream.Stream;
public class Test02 {
public static void main(String[] args) {
//获取测试数据
List<Employee> list = EmployeeData.getEmployees();
//拿到流
Stream<Employee> stream = list.stream();
//获取工资大于6000员工的数据并打印,filter
stream.filter(employee -> employee.getSalary() > 6000).forEach(System.out::println);
/**
Employee{id=1001, name='貂蝉', age=30, salary=9808.0}
Employee{id=1004, name='王昭君', age=33, salary=7845.0}
Employee{id=1005, name='西门庆', age=22, salary=6954.0}
Employee{id=1007, name='韩信', age=27, salary=9874.0}
*/
}
}
limit
打印前三条数据
因为stream一旦执行了终止操作,就不能再被使用了,所以如果在上面的代码再调用limit方法,就会报错。
stream.limit(3).forEach(System.out::println);
//java.lang.IllegalStateException: stream has already been operated upon or closed
需要重新生成流,再操作
list.stream().limit(3).forEach(System.out::println);
/*
Employee{id=1001, name='貂蝉', age=30, salary=9808.0}
Employee{id=1002, name='西施', age=32, salary=5132.0}
Employee{id=1003, name='杨玉环', age=18, salary=4567.0}
*/
skip
跳过前三个,如果数据不够跳的,比如跳过100个,则返回空。
list.stream().limit(3).forEach(System.out::println);
/*
Employee{id=1004, name='王昭君', age=33, salary=7845.0}
Employee{id=1005, name='西门庆', age=22, salary=6954.0}
Employee{id=1006, name='菠萝吹雪', age=62, salary=4521.0}
Employee{id=1007, name='韩信', age=27, salary=9874.0}
Employee{id=1008, name='王大锤', age=48, salary=2135.0}
*/
distinct()
筛选,去除重复元素。
//先添加重复元素,然后跳过前5个,再做筛选
list.add(new Employee(1010, "孙尚香", 20, 9988));
list.add(new Employee(1010, "孙尚香", 20, 9988));
list.add(new Employee(1010, "孙尚香", 20, 9988));
list.stream().skip(5).distinct().forEach(System.out::println);
/**
* Employee{id=1006, name='菠萝吹雪', age=62, salary=4521.0}
* Employee{id=1007, name='韩信', age=27, salary=9874.0}
* Employee{id=1008, name='王大锤', age=48, salary=2135.0}
* Employee{id=1010, name='孙尚香', age=20, salary=9988.0}
*/
2.映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
map ToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
map Tolnt( TolntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
map ToLong( ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
map的使用
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class TestMap {
public static void main(String[] args) {
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
//将小写转为大写
list.stream().map(String::toUpperCase).forEach(System.out::println);
//输出 AA BB CC DD //映射关系 aa —> AA...
//获取员工名字长度小于等于2的员工的名字
List<Employee> employees = EmployeeData.getEmployees();
//获得一个name流
Stream<String> nameStream = employees.stream().map(Employee::getName);
//使用name流过滤name长度
nameStream.filter(name -> name.length() < 3).forEach(System.out::println);
//优化代码
employees.stream().map(Employee::getName).filter(name-> name.length()<=2).forEach(System.out::println);
/**
* 貂蝉
* 西施
* 韩信
*/
//Stream嵌套Stream
//将list里面的string元素再拆分为字符型元素,并且拿到它的流的流,使用map
Stream<Stream<Character>> streamStream = list.stream().map(TestMap::toStream);
//打印流的流
streamStream.forEach(stream->{
stream.forEach(System.out::println);
});
//使用flagMap,代码简单,效果一样,可以类比List的addALl
list.stream().flatMap(TestMap::toStream).forEach(System.out::println);
/**
* a
* a
* b
* b
* c
* c
* d
* d
*/
}
public static Stream<Character> toStream(String s){
ArrayList<Character> list = new ArrayList<>();
for (Character c : s.toCharArray()) {
list.add(c);
}
return list.stream();
}
}
3.排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,按比较器顺序排序 |
第二个排序需要了解为什么对实体类排序会报错,参考我的另一篇博客Comparable异常
可以在实体类中实现Comparable方法,重写comparaTo方法,也可以自定义比较规则,再传过去。
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class SortTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(12, -59, 33, 66, 14, 99);
//自然排序
list.stream().sorted().forEach(System.out::println);
/*-59 12 14 33 66 99*/
//比较器排序,定制排序规则
List<Employee> employeeList = EmployeeData.getEmployees();
//根据年龄升序排序
employeeList.stream().sorted((o1, o2) -> o1.getAge()-o2.getAge()).forEach(System.out::println);
System.out.println();
//写法二,方法引用
employeeList.stream().sorted(Comparator.comparingInt(Employee::getAge)).forEach(System.out::println);
System.out.println();
//根据工资升序排序
employeeList.stream().sorted(Comparator.comparingDouble(Employee::getSalary)).forEach(System.out::println);
System.out.println();
//根据年龄升序排序,如果年龄相同,根据工资升序排序
employeeList.stream().sorted((o1, o2) -> {
int num = o1.getAge()-o2.getAge();
if(num!=0){
return num;
}else {
return Double.compare(o1.getSalary(),o2.getSalary());
}
}).forEach(System.out::println);
}
}
4.4、Stream的终止操作
-
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。
-
流进行了终止操作后,不能再次使用。
1.匹配与查找
常用方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用Collection接口需要用户去做迭代,称为外部迭代。相反,Stream API使用内部迭代——它帮你把迭代做了) |
下面来看例子,熟悉这些方法的使用
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class TestShutDown {
public static void main(String[] args) {
//拿到测试数据
List<Employee> list = EmployeeData.getEmployees();
//检擦所有员工年龄是否都够18,allMatch
boolean b = list.stream().allMatch(employee -> employee.getAge() >= 18);
System.out.println("所有员工年龄是否都够18:"+b);//true
//是否存在有员工的工资大于1万,存在返回true
boolean b1 = list.stream().anyMatch(employee -> employee.getSalary() > 10000);
System.out.println(b1); //false
//是否没有员工姓赵,noneMatch,存在返回false
boolean b2 = list.stream().noneMatch(employee -> employee.getName().startsWith("赵"));
System.out.println(b2);//true
//返回第一个元素,Optional类型
Optional<Employee> first = list.stream().findFirst();
System.out.println(first);
//Optional[Employee{id=1001, name='貂蝉', age=30, salary=9808.0}]
//返回任意元素
Optional<Employee> any = list.parallelStream().findAny(); //使用串行流会显示第一个
System.out.println(any);
//Optional[Employee{id=1006, name='菠萝吹雪', age=62, salary=4521.0}]
//统计求员工工资大于6千的个数,count
long count = list.stream().filter(employee -> employee.getSalary()>6000).count();
System.out.println(count); //4
//返回最高的工资的员工
Optional<Employee> maxEmployee = list.stream().max(Comparator.comparingDouble(Employee::getSalary));
System.out.println(maxEmployee);
//返回最高的工资
Optional<Double> maxSalary = list.stream().map(e -> e.getSalary()).max(Double::compare);
System.out.println(maxSalary);
//最小同理
//forEach:内部迭代,使用流的终止操作
list.stream().forEach(System.out::println);
System.out.println();
list.forEach(System.out::println);//使用集合的遍历方法
}
}
2.归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回Optional |
备注: map和 reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名。
map做映射,reduce做规约。
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class TestReduce {
public static void main(String[] args) {
//计算整数1-10的和
Stream<Integer> numStream = Stream.iterate(1, t -> t + 1).limit(10);//生成1-10
Integer sum = numStream.reduce(0, Integer::sum); //reduce,将流中元素反复结合起来,得到一个值,identity是偏移量
System.out.println(sum); //55
//计算员工工资的总和
List<Employee> list = EmployeeData.getEmployees();
Stream<Double> moneyStream = list.stream().map(Employee::getSalary); //得到工资的流
Optional<Double> sumMoney = moneyStream.reduce(Double::sum);//不加identity,返回一个optional
System.out.println(sumMoney); //Optional[50836.0]
}
}
3.收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法 | 返回类型 | 作用 |
---|---|---|
toList | List | 把流中元素收集到List |
toSet | Set | 把流中元素收集到Set |
toCollection | Collection | 把流中元素收集到创建的集合 |
counting | Long | 计算流中元素的个数 |
summinglnt | Integer | 对流中元素的整数属性求和 |
averaginglnt | Double | 计算流中元素Integer属性的平均值 |
summarizinglnt | lntSummaryStatistics | 收集流中Integer属性的统计值。如:平均值 |
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.List;
import java.util.stream.Collectors;
public class TestCollector {
public static void main(String[] args) {
List<Employee> employees = EmployeeData.getEmployees();
//查找工资大于6000的员工,返回一个List或Set
List<Employee> list = employees
.stream().filter(employee -> employee.getSalary() > 6000)
.collect(Collectors.toList());//Set改成toSet即可。也可以to其他东西
list.forEach(System.out::println);
}
}
5、Optional类(了解即可)
- 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了
解决空指针异常
,Google公司著名的Guava项目引入了Optional类
,Guava通过使用检查空值的方式来防止代码污染
,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java8类库的一部分。 Optional<T>类(java.util.Optional)是一个容器类
,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。- Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional提供了许多有用的方法,这样我们就不用显示进行空值检验,我们主要关注也是Optional的方法。
创建Optional类对象的方法:
方法 | 作用 |
---|---|
Optional.of(T t) | 创建一个Optional 实例,t必须非空; |
Optional.empty() | 创建一个空的Optional实例 |
Optional.ofNullable(T t) | t可以为null |
判断Optional容器中是否包含对象:
方法 | 返回值 | 作用 |
---|---|---|
isPresent() | boolean | 判断是否包含对象 |
ifPresent(Consumer<? super T> consumer) | void | 如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。 |
获取Optional容器的对象:
方法 | 返回值 | 作用 |
---|---|---|
get() | T | 如果调用对象包含值,返回该值,否则抛异常 |
orElse(T other) | T | 如果有值则将其返回,否则返回指定的other对象。 |
orElseGet(Supplier<? extends T> other) | T | 如果有值则将其返回,否则返回由Supplier接口实现提供的对象。 |
orElse Throw(Supplier<? extends X>exceptionSupplier) | T | 如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。 |