注意:所有文章除特别说明外,转载请注明出处.
Java 8 新特性
[TOC]
1. Lambda表达式
Lambda表达式允许将函数作为一个方法的参数(函数作为参数传递进方法中),使用Lambda表达式可以使程序变得紧凑。
// 1. 不需要参数,返回值为 5
() -> 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)
package cn.edu.xidian.B.Demo.Java8;
public class Java8Test {
public static void main(String[] args) {
Java8Test java8Test = new Java8Test();
MathOperation addtion = (int a, int b) -> a + b;
MathOperation substraction = (a, b) -> a - b;
MathOperation multiplication = (int a, int b) -> {return a*b;};
MathOperation division = (int a, int b) -> a/b;
System.out.println("8+5=" + java8Test.oprate(8,5, addtion));
System.out.println("8-5=" + java8Test.oprate(8,5, substraction));
System.out.println("8*5=" + java8Test.oprate(8,5, multiplication));
System.out.println("8/5=" + java8Test.oprate(8,5, division));
GreetingService greetingService = message -> System.out.println("hello" + message);
GreetingService greetingService1 = message -> System.out.println("hello" + message);
greetingService.sayMessage("world");
greetingService1.sayMessage("google");
}
interface MathOperation{
int operation(int a, int b);
}
interface GreetingService{
void sayMessage(String message);
}
private int oprate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
注意:1.Lambda 表达式主要用来定义行内执行的方法类型接口。2.Lambda表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
2. 变量作用域
lambda表达式只能引用标记final的外层局部变量,所以lambda内部修改定义在域外的局部变量。
3.Lambda表达式实现Runnable接口
1.使用匿名内部类
// 1.1使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
}).start();
// 1.2使用 lambda expression
new Thread(() -> System.out.println("Hello world !")).start();
// 2.1使用匿名内部类
Runnable race1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
};
// 2.2使用 lambda expression
Runnable race2 = () -> System.out.println("Hello world !");
// 直接调用 run 方法(没开新线程哦!)
race1.run();
race2.run();
4.方法的引用
提示:在Java8中方法的引用使用 :: 形式,比如在类Car中引用方法run()方法,直接使用形式 Car :: run 的形式。
5.函数式接口
Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。
提醒:加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法。
1.函数式接口里允许定义默认方法
函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的。
2.函数式接口里允许定义静态方法
default void test();
3. 总结
1. 如果一个接口只要一个抽象方法,那么该接口就是一个函数式接口
2. 如果我们在某一个接口上声明了FunctionalInterface注解,那么编译器就会按照函数式接口的定义来要求该接口。
3. 如果某一个接口只有一个抽象方法,但我们并没有给该接口声明FunctionalInterface注解,那编译器会对该接口定义为函数式接口。
4. If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does em>not</em> count toward the interface's abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere.
5. 在将函数作为一等公民的语言中,Lambda表达式的类型是函数,但是在Java中,Lambda表达式是对象,它们必须依附于一类特别的对象类型——函数式接口。
6. 高阶函数,如果一个函数的参数是为一个函数,那么该函数就称为高阶函数。
7. 流是存在短路运算的,只要其中一个操作满足条件,后面的操作就将不会再去执行。
4. Function 接口
表示接收一个参数,并返回一个结果函数式接口。
Function<T, R> T 表示输入参数类型 R 表示结果类型
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
5. BiFunction 接口
BiFunction<T,U,R>描述了 参数类型为T和U,而返回类型为R的函数。
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*/
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
总结:在之前我们定义函数功能的时候,我们在定义的时候已经固定好实现逻辑,然后现在的函数式编程只是定义了一个通用的方法,然后用户在具体使用的时候定义它的实现逻辑。
6. Predicate接口
/**
* 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);
//逻辑与操作
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
//逻辑非操作
default Predicate<T> negate() {
return (t) -> !test(t);
}
//逻辑或操作
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
...
在源码中查看具体的方法。
7. Supplier 接口
不接受参数,且返回结果。
/**
* Gets a result.
*
* @return a result
*/
T get();
8. Consumer接口
表示接收一个单一的输入参数,不返回结果。
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
//example
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
Optional
Optional
8.7.1 使用Optional类型值
有效使用Optional的关键在于:使用一个要么如果值不存在,产生另外一个替代者。要么如果值存在,使用该值的方法。即不会报空指针异常。
第一种策略:当没有可匹配项时,我们可能会希望使用一个默认值,可能是一个空字符串。
//封装的字符串,如果没有的话则为空字符串
String result = optionalString.orElse("");
//只有在被需要时才会被调用
String result = optionalString.orElseGet(() -> System.getProperty("myapp.default"));
//提供一个可以产生异常对象的方法
String result = optionalString.orElseThrow(IllegalStateException::new);
8.7.2 如何不使用Optional类型值
8.7.3 创建Optional类型值
如果我们需要创建Optional对象的方法,有一些静态方法选择。Optional.of(result)和Optional.empty()。
public static Optional<Double> inverse(Double x){
return x == 0 ? Optional.empty() : Optional.of(1 / x);
}
8.7.4 使用flatMap来组合可选值函数
8.7.5 将Optional转变成流
/**
* If a value is present, apply the provided mapping function to it,
* and if the result is non-null, return an {@code Optional} describing the
* result. Otherwise return an empty {@code Optional}.
*
* @apiNote This method supports post-processing on optional values, without
* the need to explicitly check for a return status. For example, the
* following code traverses a stream of file names, selects one that has
* not yet been processed, and then opens that file, returning an
* {@code Optional<FileInputStream>}:
*
* <pre>{@code
* Optional<FileInputStream> fis =
* names.stream().filter(name -> !isProcessedYet(name))
* .findFirst()
* .map(name -> new FileInputStream(name));
* }</pre>
*
* Here, {@code findFirst} returns an {@code Optional<String>}, and then
* {@code map} returns an {@code Optional<FileInputStream>} for the desired
* file if one exists.
*
* @param <U> The type of the result of the mapping function
* @param mapper a mapping function to apply to the value, if present
* @return an {@code Optional} describing the result of applying a mapping
* function to the value of this {@code Optional}, if a value is present,
* otherwise an empty {@code Optional}
* @throws NullPointerException if the mapping function is null
*/
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
Spliterator
Spliterator是一个可分割迭代器(splitable iterator),可以和iterator顺序遍历迭代器一起看。jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator,后面我们也会结合ArrayList中的spliterator()一起解析。
//单个对元素执行给定的动作,如果有剩下元素未处理返回true,否则返回false
boolean tryAdvance(Consumer<? super T> action);
//对每个剩余元素执行给定的动作,依次处理,直到所有元素已被处理或被异常终止。默认方法调用tryAdvance方法
default void forEachRemaining(Consumer<? super T> action) {
do { } while (tryAdvance(action));
}
//对任务分割,返回一个新的Spliterator迭代器
Spliterator<T> trySplit();
//用于估算还剩下多少个元素需要遍历
long estimateSize();
//当迭代器拥有SIZED特征时,返回剩余元素个数;否则返回-1
default long getExactSizeIfKnown() {
return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
}
//返回当前对象有哪些特征值
int characteristics();
//是否具有当前特征值
default boolean hasCharacteristics(int characteristics) {
return (characteristics() & characteristics) == characteristics;
}
//如果Spliterator的list是通过Comparator排序的,则返回Comparator
//如果Spliterator的list是自然排序的 ,则返回null
//其他情况下抛错
default Comparator<? super T> getComparator() {
throw new IllegalStateException();
}
提示:特征值其实就是为表示该Spliterator有哪些特性,用于可以更好控制和优化Spliterator的使用。
6.Java8 默认方法
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个 default 关键字即可实现默认方法。
默认方法很大程度上解决向后兼容的问题。避免其破坏类。
7.Java 8 Stream
此抽象可以让编程人员以一种声明的方式处理数据。这种风格将要处理的元素集合看作是一种流,流在管道中传输,并且可以在管道的结点上进行处理。
1.Stream流
Stream流是数据渠道,用于操作数据源(集合、数组)所生成的元素序列,其可以多个操作链接起来运行,内部迭代。
流的组成:
1. 源
2. 零个或多个中间操作
3. 终止操作
注意:stream流不会改变原有的对象,会返回一个新的stream。
提示:Stream操作还有两个基础特征。1.Pipelining:中间操作都会返回流对象本身。2.内部迭代:通过访问者模式(Visitor)实现。
流操作的分类:
1. 惰性求值
2. 及早求值
提示:只有在遇到上面的操作之后,中间操作才会执行。如果没有及早求值或终止操作,中间操作不会执行。
2.创建Stream
流的典型工作流程:
1.创建Stream
2.指定将初始流转换成其它流的中间操作,可能需要多步操作
3.应用终止操作产生结果
使用Collection接口的Stream()方法可以将任何集合转换为Stream。如果是数组的话可以使用静态方法 Stream.of将它转化为Stream。
Stream<String> words = Stream.of(contents.split("\\PL+");
8. filter | map | flatMap 方法
流的转换产生一个新流,该流的元素来源于其它流。filter转换生成一个匹配一定条件的新流。
List<String> words = ...;
Stream<String> longWords = words.stream().filter(w -> w.length() > 12);
filter的参数是一个Predicate
对象,即从T到boolean的函数。
考虑将一个流中的值进行某种形式的转换,可以考虑使用map()方法,并且传递给它一个执行转换的函数。
Stream<String> lowercaseWords = words.stream().map(String::toLowerCase);
Stream<String> firstLetters = words.stream().map(s -> s.substring(0, 1));
使用flatMap()方法将字符串转换成只包含字符串的流。
List<String> words = ...;
Stream<String> flatResult = words.stream().flatMap(w -> codePoints(w));
2.流与集合之间的比较
1.在集合中,其会包含当前数据结构中所有的值,可以随时对其操作,在集合里面的元素值已经提前计算好。而流则是按需就散,按照当前使用的需要计算数据。
创建stream的过程就是将一个数据源(集合或者数组等)转换为一个流。这里有三种方式创建stream流。
2.迭代的差异,集合进行外部迭代,一遍遍的的去进行迭代操作。而流由流水线直接进行内部迭代。
//1 通过Collection系列提供的stream()(串行) 或parallelStream()(并行)获取
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();//串行流
Stream<String> stream2 = list.parallelStream();//并行流
//2 通过Arrays中的静态方法stream() 获取数据流
User[] u = new User[2];
Stream<User> stream3 = Arrays.stream(u);
//3 通过Stream类中的静态方法of()
Stream<String> stream4 = Stream.of("11","2");
3.中间操作
<!--这里表示对数据源进行一系列的操作处理,链接起来作为一条流水线处理各种操作。-->
4.方法
1.filter(predicate)-接收lambda,从流中排除某些元素。
2.limit(n)-截断流,使其元素不超过给定数量。
3.skip(n)-跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流,与limit(n)互补。
4.distinct-筛选,通过流所生成元素的hashcode()和equals()去重复元素。
5.forEach 迭代流中的每个数据
6.map 映射每个元素对应的结果,类似类型转换
8.flatMap() 将流中的每一个元素映射为一个流,再把每一个流连接成为一个新流
7.collect 作为终端操作,接收的是一个Collector接口参数,能对数据进行一些搜集归总操作。
7.1 toList
7.2 toSet
7.3 toCollection
7.4 toMap
9.reduce 用于组合流中的元素 如:求和|求积|求最大值等
//计算年龄总和
int sum = list.stream().map(Person::getAge).reduce(0, (a,b) -> a + b);
//或者是这样
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);
//例
//打印年龄大于18的前4位用户,并且跳过第1个用户
public void test1(){
list.stream().filter((x)->x.getAge()>18).distinct().limit(4)
.skip(1).forEach(System.out::println);
}
8.6 简单归约
归约就是如何从流数据中获得答案,归约是终止操作。如简单的归约函数:count() | max() | min() 方法,这些方法都会返回一个Optional
Optional<String> largest = words.max(String::compareToIngnoreCase);
System.out.println("largest:" + largest.orElse(""));
方法引用(method reference)
方法引用实际上是个lambda表达式的一种语法糖。
我们可以将方法引用看做是一个函数指针,function pointer。
方法引用分为四类:
1. 类名::静态方法名,方法引用与方法的静态引用(方法调用)没有任何关系。
2. 引用名(对象名)::实例方法名
3. 类名::实例方法名,这里的调用者是第一个参数,后续的所有参数都是作为参数传入进去的。
4. 构造方法引用:类名::new
提示:实现类的优先级比接口的优先级要高一些。
在有现成的方法的时候,可以使用方法引用来代替lambda表达式。
流不能重复使用。
9.映射
1.map 接收lambda,并将元素转换成其它形式或提取信息,会将元素映射成新的元素。
2.flatMap 接收函数作为参数,并将流中的每个值转换成流,将流链接成另外一个流。
10.构建流
1.Stream.of("Aaron","Brian","Carol"); //这里构建一个字符串流
2.Stream.empty();//这里生成空流
11.总结
1.counting 用于计算总和
long l = list.stream().collect(counting());
//或者这样
long l = list.stream().count();
2.summingInt | summingLong | summingDouble
int sum = list.stream().collect(summingInt(Person::getAge));//计算年龄总和
//或者是这样
int sum = list.stream().mapToInt(Person::getAge).sum();
//或者这样
int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();
提示:函数式编程通常提供多种方式完成同一操作。
3.averagingInt | averagingLong | averagingDouble
Double average = list.stream().collect(averagingInt(Person::getAge));
//或者是这样
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
4.joining 连接字符串
此方法是对流里面的字符串进行连接 底层实现是使用用于字符串连接的StringBuilder
String s = list.stream().map(Person::getName).collect(joining());
String s = list.stream().map(Person::getName).collect(joining(","));//这里是在获得名字中间用,区分开
5.取最值 maxBy() | minBy()
此方法需要Comparator接口作为参数
Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));
//或者是这样
Optional<Person> optional = list.stream().max(comparing(Person::getAge));
6.summarizingInt | summarizingLong | summarizingDouble
这三种方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型
//IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值,可以通过下面这些方法获得相应的数据
IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));
数据的分区与分组
Student student1 = new Student("zhangsan", 86, 18);
Student student2 = new Student("lisi", 89, 20);
Student student3 = new Student("wangwu", 93, 23);
Student student4 = new Student("lisi", 79, 24);
List<Student> students = Arrays.asList(student1, student2, student3, student4);
//group
Map<String, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getName));
System.out.println(map);
System.out.println("--------------------------------");
Map<Integer, List<Student>> map2 = students.stream().collect(Collectors.groupingBy(Student::getScore));
System.out.println(map2);
System.out.println("--------------------------------");
Map<String, Long> map3 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
System.out.println(map3);
System.out.println("--------------------------------");
Map<String, Double> map4 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
System.out.println(map4);
System.out.println("--------------------------------");
//partion
Map<Boolean, List<Student>> map5 = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() >= 90));
System.out.println(map5);
并行
我们通过list.stream()将List类型转换为流类型,还可以通过list.parallelStream()转换成并行流。所以通常可以通过使用parallelStream代替Stream()方法。
注意:并行流是将内容分成多个数据块,使用不同的线程分别处理每个数据块的Luis。
底层(bottom)
分组与分区
分区是分组的一种特例,分区的话就只有false和true两种类型。
Collector 收集器
Collector作为collect()方法的参数。
Collector是一个接口,它是一个可变的汇聚操作,将输入元素累积到一个可变的结果容器中。
它会在所有元素都处理完毕之后,将累积的结果转换成一个最终的表示(这是一个可选操作)。它支持串行和并行两种操作。
Collectors本身提供了关于Collector的常见汇聚实现,Collectors本身实际上是一个工厂。
为了确保串行与并行操作结果的等价性,Collector函数需要满足两个条件:identity(同一性)与associativity(结合性)。
a == combiner.apply(a, supplier.get());
(List
list1, List list2) -> {list1.addAll(list2); return list1} 函数式编程最大的特点:表示做什么,而不是如何做。
combiner()函数,假设有四个线程同时去执行,就会生成四个不同的结果,combiner函数就是将四个不同的结果合并成一个结果。该函数是在多线程并行环境下起作用的。
自定义 Collector
Collectors 底层
对于Collectors静态工厂类来说,其实现一共分为两种情况:
1. 通过CollectorImpl来实现。
2. 通过reducing方法来实现,reducing方法本身又是通过CollectorImpl来实现。
Comparator 比较器
如果存在特化的方法和通用的方法,那么一定调用特化的方法,这样的效率要高一些。
####