Java8新特性
Java8新特性
- 增加了新的语法Lambda表达式
- Stream API
- Optional最大化减少空指针异常
1. Lambda表达式
-
为什么使用Lambda表达式:
Lambda是一个匿名函数,可以理解为是一段可以传递的代码(将代码像数据一样进行传递),使代码更加简洁,更加灵活 -
Lambda表达式语法:
Lambda表达式在Java语言中引入了一个新的语法元素和操作符,这个操作符为 " >",该操作符被称为Lambda操作符,它将Lambda分为两个部分:
左侧:指定了Lambda表达式需要的所有参数
右侧:指定了Lambda体,即Lambda表达式要执行的功能 -
语法简介:
- 语法格式一:无参,无返回值,Lambda体只需一条语句
Runnable r = () -> System.out.println("Hello Lambda");
- 语法格式二:Lambda需要一个参数
Consumer<String> cons = (e) -> System.out.println(e);
- 语法格式三:Lambda只需要一个参数时,参数的小括号可以省略
Consumer<String> cons = e -> System.out.println(e);
- 语法格式四:Lambda需要两个参数,并且有返回值
BinaryOperator<Long> big = (x,y) -> {
System.out.println("函数接口方法");
return x + y;
}
- 语法格式五:当Lambda体只有一条语句时,return与大括号可以省略
BinaryOperator<Long> big = (x,y) -> x + y;
- 语法格式六:数据类型可以省略,因为可由编译器推断得出,称为 " 类型推断 "
BinaryOperator<Long> big = (Long x, Long y) -> {
System.out.println("函数接口方法");
return x + y;
}
2. 类型推断
Lambda表达式中的参数类型都是由编译器推断得出的,Lambda表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型,Lambda表达式的类型依赖于上下文环境,是由编译器推断出来的,这就是所谓的"类型推断"
3. 函数式接口
-
什么是函数式接口:
❶ 只包含一个抽象方法的接口,称为函数式接口
❷ 可以通过Lambda表达式来创建该接口的对象(若Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)
❸ 可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口 -
函数式接口的使用:
❶ 创一个接口类,在类中创建如下方法:
/**
* 函数式接口中使用泛型
*/
@FunctionalInterface
public interface Fun<T>{
public T getValue(T t);
}
❷ 创一个测试类:
/**
*将创的函数式接口作为参数传递Lambda表达式
*/
public String toUpperString(Fun<String> f, String str){
return f.getValue(str);
}
@Test
public void test(){
String s = tuUpperString((e) -> e.tuUpperCase(), "aaaa");
System.out.println(s);
}
作为参数传递Lambda表达式:为了将Lambda表达式作为参数传递,接收Lambda表达式的参数类型必须是与该Lambda表达式兼容的函数式接口的类型
4. Java内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer< T > 消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t); |
Supplier< T > 供给型接口 | 无 | 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 >(Function子接口) | T | T | 对类型为T的对象进行一元运算,并返回T类型的结果,包含方法为T apply(T t); |
BinaryOperator< T >(BiFunction子接口) | T,T | T | 对类型为T的对象进行二元运算,并返回T类型的结果,包含方法为T apply(T t1,T t2); |
BiConsumer<T,U> | T,U | void | 对类型为T,U参数应用操作,包含方法为void accept(T t,U u); |
ToIntFunction< T > ToLongFunction< T > ToDoubleFunction< T > | T | int long double | 分别计算int,long,double值的函数 |
IntFunction< T > LongFunction< T > DoubleFunction< T > | int long double | R | 参数分别为int,long,double类型的函数 |
5. 方法引用
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致)
方法引用:使用操作符 " :: " 将方法名和对象或类的名字分隔开来
如下三种主要使用情况:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
-
方法引用举例:
列如:
(e) -> System.out.println(e);
等同于:
System.out::println;
例如:
BinaryOperator< Double > bo = (x,y) -> Math.pow(x,y);
等同于:
BinaryOperator< Double > bo = Math::pow;
例如:
compare((x,y) -> x.equals(y), “abc”, “abc”);
等同于:
compare(String::equals,“abc”, “abc”);
注意:当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引用方法的第二个参数(或无参数)时:ClassName::methodName
6. 构造器引用
格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容,可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致
例如:
Function<Integer, MyClass> fun = (e) -> new MyClass(e);
等同于:
Function<Integer,MyClass> fun = MyClass::new;
7. 数组引用
格式:type[] ::new
例如:
Function<Integer,Integer[]> fun = (e) -> new Integer[e];
等同于:
Function<Integer,Integer[]> fun = Integer[]::new;
8. Stream API
-
了解Stream API
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作,使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询,也可以使用Stream API来并行执行操作,简而言之,Stream API提供了一种高效且易于使用的处理数据的方式 -
什么是Stream
是数据渠道,用于操作数据源(集合,数组等)所生成的元素序列
" 集合讲的是数据,流讲的是计算 "
注意:
❶ Stream自己不会存储元素
❷ Stream不会改变源对象,相反,他们会返回一个持有结果的新Stream
❸ Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行
-
Stream的操作三个步骤
❶ 创建Stream
一个数据源(如:集合,数组),获取一个流
❷ 中间操作
一个中间操作链,对数据源的数据进行处理
❸ 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果 -
创建Stream
Java8中的Collection接口被扩展,提供了两个获取流的方法:
❶ default Stream< E > stream():返回一个顺序流
❷ default Stream< E > parallelStream():返回一个并行流 -
由数组创建流
Java8中的Arrays的静态方法stream()可以获取数组流:
❶ static < T > Stream< T > stream(T[] array):返回一个流
重载形式,能够处理对应基本类型的数组:
❶ public static IntStream stream(int[] array)
❷ public static LongStream stream(long[] array)
❸ public static DoubleStream stream(double[] array)
-
由值创建流
可以使用静态方法Stream.of(),通过显示值创建一个流,它可以接收任意数量的参数
❶ public static< T > Stream< T > of(T… values):返回一个流
-
由函数创建流:创建无限流
可以使用静态方法Stream.iterate()和Stream.generate(),创建无限流
❶ 迭代
public static< T > Stream< T > iterate(final T seed, final UnaryOperator< T > f);
❷ 生成
public static< T > Stream< T > generate(Supplier< T > s);
-
Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为"惰性求值"
筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的hashCode()和equals()去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补 |
映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序 |
-
Stream的终止操作
终端操作会从流的流水线生成结果,其结果可以是任何不是流的值,例如:List,Integer,甚至是void
查找与匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用Collection接口需要用户去做迭代,称为外部迭代,相反,Stream API使用内部迭代-它帮你把迭代做了) |
归约
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回T |
---|---|
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回Optional< T > |
备注:map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名
收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List,Set,Map),但是Collectors实用类提供了很多静态方法,可以方便的创建常见收集器实例,具体方法与实例如下表:
方法 | 返回类型 | 作用 | 描述 |
---|---|---|---|
toList | List< T > | 把流中元素收集到List | List< Employee > emps = list.stream().collect(Collectors.toList()); |
toSet | Set< T > | 把流中元素收集到Set | Set< Employee > emps = list.stream().collect(Collectors.toSet()); |
toCollection | Collection< T > | 把流中元素收集到创建的集合 | Collection< Employee > emps = list.stream().collect(Collectors.toCollection(ArrayList::new)); |
counting | Long | 计算流中元素的个数 | long count = list.stream().collect(Collectors.counting()); |
summingInt | Integer | 对流中元素的整数属性求和 | int total = list.stream().collect(Collectors.summingInt(Employee::getSalary)); |
averagingInt | Double | 计算流中元素Integer属性的平均值 | double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary)); |
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值,如:平均值 | list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); |
joining | String | 连接流中每个字符串 | String str = list.stream().map(Employee::getName).collect(Collectors.joining()); |
maxBy | Optional< T > | 根据比较器选择最大值 | Optional< Emp > max = list.stream.collect(Collectors.maxBy(comparingInt(Employee::getSalary))); |
minBy | Optional< T > | 根据比较器选择最小值 | Optional< Emp > max = list.stream.collect(Collectors.minBy(comparingInt(Employee::getSalary))); |
reducing | 归约产生的类型 | 从一个座位累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 | int total = list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 | int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); |
groupingBy | Map<K, List< T >> | 根据某属性值对流分组,属性为k,结果为v | Map<Emp.Status, List< Emp >> map = list.stream().collect(Collectors.groupingBy(Employee::getStatus)); |
partitioningBy | Map<Boolean, List< T >> | 根据true或false进行分区 | Map<Boolean, List< Emp >> map = list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
9. 并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流
Java8中将并行进行了优化,可以很容易的对数据进行并行操作,Stream API可以声明性的通过parallel() 与 sequential() 在并行流与顺序流之间进行切换
10. Fork/Join框架
Fork/Join框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join汇总
Fork/Join框架与传统线程池的区别:
- 采用 " 工作窃取 " 模式(work-stealing):
当执行新的任务时它可以将其拆分,分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中 - 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能