【Java SE】二十二、Java 8 新特性
Java 8 是 oracle 公司于 2014 年 3 月发布,可以看成是自 Java 5 以来最具革命性的版本。Java 8 为 Java 语言、编译器、类库、开发工具与JVM带来了大量新特性,是目前最常用的 Java 版本。
函数式接口
只包含一个抽象方法的接口,称为函数式接口。所有以前用匿名实现类表示的现在都可以用 Lambda 表达式来写。
你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。我们可以在一个接口,上使用 @Functionallnterface
注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明, 说明这个接口是一个函数式接口。在 java.util.function 包下定义了 Java 8的丰富的函数式接口。
Java 内置四大核心函数式接口,如下表:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> | T | void | 消费型接口,对类型为 T 的对象应用操作,包含方法: void accept(T t) |
Supplier<T> | null | T | 供给型接口,返回类型为 T 的对象,包含方法: T get() |
Function<T, R> | T | R | 函数型接口,对类型 T 的对象应用操作,并返回 R 类型的对象,包含方法: R apply(T t) |
Predicate<T> | T | boolean | 断定型接口,确定 T 类型对象是否满足某约束,并返回 boolean 值,包含方法: boolean test(T t) |
其他接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 对类型为 T, U 参数应用操作,返回 R 类型的结果。包含方法: R apply(T t, U u) |
UnaryOperator<T> | T | T | 对类型 T 的对象进行一元运算,并返回 T 类型的结果。包含方法为: T apply(T t) |
BinaryOperator<T> | T, T | T | 对类型 T 的对象进行二元运算,并返回 T 类型的结果。包含方法为: T apply(Tt1, T t2) |
BiConsumer<T, U> | T, U | void | 对类型为T, U参数应用操作。包含方法: void accept(T t, U u) |
BiPredicate<T, U> | T, U | boolean | 略。包含方法为: void accept(Tt, U u) |
TolntFunction<T> ToLongFunction<T> ToDoubleFunction<T> |
T | int long double |
分别计算 int、long、double 值的函数 |
IntFunction<R> LongFunction<R> DoubleFunction<R> |
int long double |
R | 参数分别为 int、long、double 类型的函数 |
## Lambda 表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递) ,使用它可以写出更简洁、更灵活的代码。
示例如下:
//匿名方法
Comparator<Integer> com1 = new Comparator<Integer>() { // Comparator其实就是一个函数式接口
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
System.out.println(com1.compare(12,21));
//Lambda表达式
Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2);
System.out.println(com2.compare(32,21));
//方法引用
Comparator<Integer> com3 = Integer :: compare;
System.out.println(com3.compare(32,21));
从上面的实例,我们可以看到 Lambda 表达式如下:
(o1,o2) -> Integer.compare(o1,o2)
- 中间: Lambda 操作符或箭头操作符
- 左边: Lambda 形参列表 (其实就是按口中的抽象方法的形参列表)
- 右边: Lambda 体 (其实就是重写的抽象方法的方法体)
总共分 6 种语法格式,如下:
//无参,有返回值
Runnabler1 = () -> {System.out.println("Hello Lambda!");};
//一个参数,但是没有返回值。
Consumer<String> con = (String str) -> {System.out.println(str);};
//数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> con = (str) -> {System.out.printn(str);};
//若只需一个参数,则参数的小括号可以省略
Consumer<String> con = str -> {System.out.printn(str);};
//需要两个或以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> com = (x,y) -> {
System.out.printn("实现函数式接口方法! ");
return Integer.compare(x,y);
};
//只有一条语句,若有return与大括号,则都可以省略
Comparator<Integer> com = (x,y) -> Integer.compare(x,y); // {return Integer.compare(x,y)}
注:Lambda 表达式的本质是作为函数式接口的实例
方法引用和构造器引用
方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是 Lambda 表达式的一个语法糖。
当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用。
注意:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致 (仅针对于前两种情况)
//下面的例子皆省略调用方法
public void test1() {
//情况一: 对象::实例方法
//Consumer中的void accept(T t) & PrintStream中的void println(T t)
Consumer<string> con1 = str -> System.out.println(str); //lambda表达式
System.out.print1n("****************************");
PrintStream ps = System.out;
Consumer<String> con2 = ps::println; //方法引用
//情况二: 类::静态方法
//Function中的R apply(T t) & Math中的Long round(Double d)
Function<Double,Long> func1 = d -> Math.round(d);
System.out.printn("****************************");
Function<Double,Long> func2 = Math::round;
//情况三:类::实例方法(难点)
//Comparator中的int comapre(T t1,T t2) & String中的int t1.compareTo(t2)
Comparator<string> com1 = (s1,s2) -> s1.compareTo(s2);
System.out.printn("****************************");
Comparator<string> com2 = String::compareTo;
//BiPredicate中的boolean test(T t1, T t2) & String中的boolean t1.equals(t2)
BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
System.out.printn("****************************");
BiPredicate<String,string> pre2 = String::equals;
//Function中的apply(T t) & 自定义类(Person)中的String getName();
Function<Person,String> func1= e -> e.getName();
System.out.printn("****************************");
Function<Employee,String> func2 = Employee::getName;
}
构造器实质上是没有返回值的方法,所以引用相似,如下
//构造器引用需要类有相应参数的构造器
public void test2() {
//Supplier中的T get()
Supplier<Employee> sup1 = () -> new Employee();
System.out.printn("****************************");
Supplier<Employee> sup2 = Employee::new; //构造器引用
//Function中的R apply(T t)
Function<Integer, Person> func1 = id -> new Person(id);
System.out.println("****************************");
Function<Integer, Person> func2 = Employee::new;
//BiFunction中的R apply(T t, U u)
BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
System.out.println("****************************");
BiFunction<Integer,String,Employee> func2 = Employee::new;
//Function中的R apply(T t),可以把数组看作一个特殊的类
Function<Integer ,String[]> func1 = length -> new String[length]; //这是个lambda表达式应用于数组的例子
String[] arr1 = func1.apply(5); //创建一个长度为5的数组
System.out.println("****************************");
Function<Integer,String[]> func2 = String[]::new; //数组引用
}
Stream API
Stream是 Java 8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,StreamAPl 提供了一种高效且易于使用的处理数据的方式。集合讲的是数据,Stream讲的是计算
注意:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。
- Stream 操作是延迟执行的,这意味着他们会等到需要结果的时候才执行。
Stream 的实例化
//1.通过集合
List<String> str = new ArrayList<>();
// default Stream<E> stream(): 返回一个顺序流
Stream<String> stream = str.stream();
// default Stream<E> parallelStream() :返回- -个并行流
Stream<String> parallelStream = str.parallelStream();
//2.通过数组
int[] arr = new int[]{1,2,3,4,5,6};
//调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr); // 还有long和double类型的Stream
Stream<String> stream1 = Arrays.stream(new String[10]); // 也可以用泛型
//3.通过Stream的of()
Stream<Integer> stream = Stream.of(1,2,3,4,5);
//4.创造无限流: Stream.iterate()和Stream.generate(),不加限制则会一直运行下去
//迭代: public static<T> Stream<T> iterate(final T seed, final Unaryoperator<T> f)
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println); //遍历前10个偶数
//生成: public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
注:其实有点像 Python 里的生成器
Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值“。
筛选与切片
主要有四个方法,接下来我将会使用一个存有 Employee 类的集合来举例,如下:
List<Employee> list = EmployeeData.getEmployees(); //获取集合
//1.filter(Predicate p) - 过滤,接收 Lambda,从流中排除某些元素
Stream<Employee> stream = list.stream(); //获取stream实例
stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println); //筛出工资大于7000的员工
//2.limit(n) - 载断流,便其元素不超过给定数
list.stream().limit(3).forEach(System.out::println);
//3.skip(n) -跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
list.stream().skip(3).forEach(System.out::println);
//4.distinct() - 筛选,通过流所生成元素的hashCode()和equals()去除重复元素
list.stream().distinct().forEach(System.out::println);
注:Stream 的执行流程是单向不可逆的。想要从头开始,需在执行终止操作(迭代)后,重新实例化
映射
有 2 个主要方法,共 5 个,如下:
//1.map(Function f) - 接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
//2.flatMap(Function f) - 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
//比较复杂,不演示
其余方法如下:
方法 | 说明 |
---|---|
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream |
mapTolnt(TolntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream |
排序
只有 1 个重载方法,如下:
//sorted() - 自然排序 | sorted(Comparator com) - 定制排序
List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
list.stream().sorted().forEach(System.out::println);
List<String> list2 = Arrays.asList("abc", "ddjc", "pa", "cca");
list2.stream().sorted((x,y) -> String.compare(x,y)).forEach(System.out::println);
Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,如: List,Integer, 还是 void。流进行了终止操作后,不能再次使用。
匹配与查找
我们依然以上面的 Employee 类为例:
List<Employee> employees = EmployeeData.getEmployees();
//allMatch(Predicate p) - 检查是否匹配所有元素
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18); //是否所有的员工的年龄都大于18
System.out.println(allMatch);
//anyMatch(Predicate p) - 检查是否至少匹配一个元素,同上不演示
//noneMatch(Predicate p) - 检查是否没有匹配的元素,同上不演示
//findFirst - 返回第一个元素
Optional<Employee> employee = employees.stream().findFirst();
System.out.println(emp1oyee);
//findAny - 返回当前流中的任意元素,返回值依然是Optional,只不过要用,用法同上不演示
Optional<Employee> employee1 = employees.parallelStream().findAny();
System.out.println(employee1);
//count - 返回流中元素的总个数,返回值是long,用法简单不演示
//max(Comparator c) - 返回流中最大值
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> maxSalary = salaryStream.max(Double::compare); //返回最高工资
System.out.print1n(maxsalary);
//min(Comparator c) - 返回流中最小值,同上不演示
//forEach(Consumer c) - 内部迭代,之前演示过了
注:集合也有一个 forEach
方法,两个方法的对象不同
归约
//reduce(T identity, BinaryOperator) - 可以将流中元素反复结合起来, 得到一个值。返回T
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum); //计算1-1日的自然数的和
System.out.println(sum);
//reduce(BinaryOperator) - 可以将流中元素反复结合起来, 得到一个值。返回Optional<T>
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
Optional<Double> sumMoney = salaryStream.reduce(Double::sum); //求公司所有员工工资的总和,(d1,d2) -> d1 + d2也行
System.out.println(sumMoney);
收集
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。
//collect(Collector c) - 将流转换为其他形式,接收-个Collector接口的实现,用于给Stream中元素做汇总的方法
//查找工资大于6e的员工,结果返回为一个List或Set
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.tolist());
employeeList.forEach(System.out::println); //这是集合的迭代方法
Collector 接口中的方法如下表:
方法 | 返回类型 | 作用 |
---|---|---|
toList() | List<T> | 把流中元素收集到 List |
toSet() | Set<T> | 把流中元素收集到 Set |
toCollection(ArrayList::new) | Collection<T> | 把流中元素收集到创建的集合 |
counting() | Long | 计算流中元素的个数 |
summingInt(Employee::getSalary) | Integer | 对流中元素的整数属性求和 |
averagingInt(Employee::getSalary) | Double | 计算流中元素 Integer 属性的平均值 |
summarizingInt(Employee::getSalary) | IntSummaryStatistics | 收集流中 Integer 属性的统计值,如:平均值 |
joining() | String | 连接流中每个字符串 |
maxBy(comparingInt(Employee::getSalary)) | Optional<T> | 根据比较器选择最大值 |
minBy(comparingInt(Employee::getSalary)) | Optional<T> | 根据比较器选择最小值 |
reducing(0, Employee::getSalary,Integer::sum) | 归约产生的类型 | 从一个作为累加器的初始值开始利用BinaryOperator 与流中元素逐个结合,从而归约成单个值 |
collectingAndThen(Collectors.toList(),List::size) | 转换函数近回的类型 | 包裹另一个收集器,对其结果转换函数 |
groupingBy(Employee::getStatus) | Map<K,List<T>> | 根据某属性值对流分组,属性为 K,结果为 V |
partitioningBy(Employee::getManage) | Map<Boolean,List<T>> | 根据 true 或 false 进行分区 |
Optional 类
Optional<T>类是一个容器类(类似于包装类),它可以保存类型 T 的值,代表这个值存在。或者仅保存 null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。Optional 类的 Javadoc 描述如下:这是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回 true,调用 get() 方法 会返回该对象。
Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
创建 Optional 类对象的方法:
-
Optional.of(T t):创建一个 Optional 实例,T 必须非空;
-
Optional.empty():创建一个空的 Optional 实例
-
Optional.ofNullable(T t):t 可以为 null
判断 Optional 容器中是否包含对象:
-
boolean isPresent():判断是否包含对象
-
void ifPresent(Consumer<? super T> consumer):如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它。
获取 Optional 容器的对象:
-
T get():如果调用对象包含值,返回该值,否则抛异常
-
T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象
-
T orElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由Supplier接口实现提供的对象
-
T orElse Throw(Supplier<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常。
示例如下:
// 构建一个空的Optional对象
Optional<String> empty = Optional.empty();
System.out.println(empty); // Optional.empty
// 构建一个非空的Optional对象
Optional<String> hello = Optional.of("Hello");
System.out.println(hello); // Optional[Hello]
// 构建一个可能为空的Optional对象
String name = null;
Optional<String> optionalName = Optional.ofNullable(name);
System.out.println(optionalName); // Optional.empty
// 获取Optional对象里的值
String value = hello.get();
System.out.println(value); // Hello
// 如果为空,返回默认值
String defaultValue = optionalName.orElse("World");
System.out.println(defaultValue); // World
// 如果为空,返回函数结果
String supplierValue = optionalName.orElseGet(() -> "Java");
System.out.println(supplierValue); // Java
// 如果为空,抛出异常
try {
String exceptionValue = optionalName.orElseThrow(() -> new RuntimeException("No value"));
System.out.println(exceptionValue);
} catch (RuntimeException e) {
e.printStackTrace();
/*
java.lang.RuntimeException: No value
at com.example.OptionalDemo.lambda$main$0(OptionalDemo.java:30)
at java.base/java.util.Optional.orElseThrow(Optional.java:408)
at com.example.OptionalDemo.main(OptionalDemo.java:30)
*/
}