Java 8 简介
Java 8的特征:
- 速度更快
- 代码更少,因为新加入了Lambda表达式
- 强大的Stream API
- 便于并行
- 最大化减少空指针异常:Optional
- Nashorn引擎,允许在JVM上运行JS应用。
Java 8主要更新内容参考官方说明 What’s New in JDK 8
新特性总结:

并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
Java 8中将并行进行了优化,可以很容易的对数据进行并行操作。Stream API可以使用parallel()与sequential()方法在并行流与顺序流之间进行切换。
一、Lambda表达式
Lambda表达式是JDK 8中新的语法,操作符为->,一个Lambda表达式由3部分构成:
左侧->右侧
其中操作符左侧指定Lambda表达式需要的参数列表,右侧指定了Lambda体,是抽象方法的实现逻辑,即Lambda表达式要执行的功能。
Lambda表达式的本质是函数式接口的实例,其作为接口的实例出现。
具体使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Runnable r = ()->{System.out.println("hello");};
Consumer<String> con = (String str) ->{System.out.println(str);};
Consumer<String> con = (str) ->{System.out.println(str);};
Consumer<String> con = str ->{System.out.println(str);};
Comparator<Integer> com = (x,y)->{ System.out.println("hello"); return Integer.compare(x,y); };
Comparator<Integer> com = (x,y)->Integer.compare(x,y);
|
总结:
二、函数式(Functional)接口
函数式接口指的是只包含一个抽象方法的接口。可以用注解@FunctionalInterface检查。
可以通过Lambda表达式创建函数式接口的对象,执行体就是实现抽象方法的代码。如果Lambda表达式抛出非运行时异常,该异常需要在目标接口的抽象方法上声明。
Lambda表达式是一个函数式接口的实例,只要一个对象是函数式接口的实例对象,就可以用Lambda表达式来表达。所以说实现函数式接口的匿名实现类的表示方式都可以改成Lambda表达式的形式。
java.util.function包下定义了Java8的丰富的函数式接口,其中有四种核心的函数式接口:
| 函数式接口 |
参数类型 |
返回类型 |
用途 |
消费型接口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(Tt 1,Tt 2); |
BiConsumer<T,U> |
T, U |
void |
对类型为T,U参数应用操作。 包含方法为:void accept(T t,U u); |
BiPredicate<T,U> |
T, U |
boolean |
包含方法为:boolean test(T t,U u); |
ToIntFunction<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类型的函数 |
使用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class LambdaTest { @Test public void test1(){ happyTime(500, new Consumer<Double>() { @Override public void accept(Double m) { System.out.println("价格为:" + m); } }); System.out.println("********************"); happyTime(400,money -> System.out.println("价格为:" + money)); } public void happyTime(double money, Consumer<Double> con){ con.accept(money); } }
|
三、方法引用与构造器引用
1、方法引用
当要传给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
方法引用,本质上是Lambda表达式,可以认为是Lambda表达式的一个语法糖。所以方法引用也是函数式接口的实例。方法引用的操作符为::
方法引用有三种方式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
对于前两种方式,要求接口中的抽象方法的形参列表和返回值类型必须和方法引用的方法的形参列表和返回值类型相同。
使用举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
public class MethodRefTest {
@Test public void test1() { Consumer<String> con1 = str -> System.out.println(str); con1.accept("北京"); PrintStream ps = System.out; Consumer<String> con2 = ps::println; con2.accept("beijing"); }
@Test public void test2() { Employee emp = new Employee(1001,"Tom",23,5600); Supplier<String> sup1 = () -> emp.getName(); System.out.println(sup1.get()); Supplier<String> sup2 = emp::getName; System.out.println(sup2.get()); }
@Test public void test3() { Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2); System.out.println(com1.compare(12,21)); Comparator<Integer> com2 = Integer::compare; System.out.println(com2.compare(12,3)); }
@Test public void test4() { Function<Double,Long> func = new Function<Double, Long>() { @Override public Long apply(Double d) {return Math.round(d);} }; Function<Double,Long> func1 = d -> Math.round(d); System.out.println(func1.apply(12.3)); Function<Double,Long> func2 = Math::round; System.out.println(func2.apply(12.6)); }
@Test public void test5() { Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2); System.out.println(com1.compare("abc","abd")); Comparator<String> com2 = String :: compareTo; System.out.println(com2.compare("abd","abm")); }
@Test public void test6() { BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2); System.out.println(pre1.test("abc","abc")); BiPredicate<String,String> pre2 = String :: equals; System.out.println(pre2.test("abc","abd")); }
@Test public void test7() { Employee employee = new Employee(1001, "Jerry", 23, 6000); Function<Employee,String> func1 = e -> e.getName(); System.out.println(func1.apply(employee)); Function<Employee,String> func2 = Employee::getName; System.out.println(func2.apply(employee)); } }
|
2、构造器引用
格式:ClassName::new
和方法引用类似,要求函数式接口的抽象方法的形参列表和构造器的形参列表一致。
抽象方法的返回值类型即为构造器所属的类的类型。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class ConstructorRefTest {
@Test public void test1(){ Supplier<Employee> sup = new Supplier<Employee>() { @Override public Employee get() {return new Employee();} }; Supplier<Employee> sup1 = () -> new Employee(); System.out.println(sup1.get()); Supplier<Employee> sup2 = Employee :: new; System.out.println(sup2.get()); }
@Test public void test3(){ BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name); System.out.println(func1.apply(1001,"Tom")); BiFunction<Integer,String,Employee> func2 = Employee :: new; System.out.println(func2.apply(1002,"Tom")); } }
|
3、数组引用
格式:type[]::new,其中type表示数组类型。
可以将数组看成一个特殊的类,写法与构造器引用一致。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class ArrayRefTest { @Test public void test4(){ Function<Integer,String[]> func1 = length -> new String[length]; String[] arr1 = func1.apply(5); System.out.println(Arrays.toString(arr1)); Function<Integer,String[]> func2 = String[] :: new; String[] arr2 = func2.apply(10); System.out.println(Arrays.toString(arr2)); } }
|
四、强大的Stream API
1、Stream简介
Stream API ( java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,Stream API可以极大提高Java程序员的生产力。
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
Stream API可以对集合进行操作,可以执行非常复杂的查找、过滤和映射数据等操作,类似于使用SQL执行的数据库查询。
也可以使用Stream API来并行执行操作。简言之,Stream API提供了一种高效且易于使用的处理数据的方式。
Stream和Collection集合的区别:
Collection是一种静态的内存数据结构。主要面向内存,存储在内存中。
Stream是有关计算的。主要是面向CPU,通过CPU实现计算。
Stream的特点:
- Stream 自己不会存储元素。
- Stream 不会改变源对象,会返回一个持有结果的新Stream,类似于视图(Collections工具类会对集合本身进行修改,比如排序操作)。
- Stream 操作是延迟执行的,即会等到需要结果的时候才执行,执行终止操作的时候才执行中间操作。
2、使用Stream
想要使用Stream,需要执行以下三个步骤:
- 实例化
Stream类(java.util.stream.Stream),得到Stream类对象stream。
- 中间操作。中间操作链,对数据源的数据进行处理,比如排序,筛选,映射等。
- 终止操作。执行终止操作时才会执行中间操作,并产生结果。终止操作后,
stream不能再被使用。
3、Stream实例化
Stream类有四种实例化方法:
- 通过集合。Java 8的
Collection接口被扩展,提供了两个获取Stream实例的方法:
default Stream<E> stream():返回一个顺序流
default Stream<E> parallelStream():返回一个并行流
- 通过数组。
Arrays工具类提供了stream()方法,用于获取数组流,对于不同的数据类型,提供了其重载方法,返回的类型也不同:
public static <T> Stream<T> stream(T[] array):返回Stream实例。
public static IntStream stream(int[] array):对于int型数组,返回IntStream实例。
public static LongStream stream(long[] array):对于long型数组,返回LongStream实例。
public static DoubleStream stream(double[] array):对于double型数组,返回DoubleStream实例。
- 通过
Stream类的of()方法。
public static<T> Stream<T> of(T...values):参数可以是单个数据,或者多个数据。如果直接将数组或者是集合作为参数,默认当作一个参数。参数不能为单个null,但是可以是多个null
- 通过
Stream类静态方法创建无限流。创建无限流有两种方式:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f):迭代
public static<T> Stream<T> generate(Supplier<T> s):生成
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class StreamAPITest { @Test public void test1(){ List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> stream = employees.stream(); Stream<Employee> parallelStream = employees.parallelStream(); }
@Test public void test2(){ int[] arr = new int[]{1,2,3,4,5,6}; IntStream stream = Arrays.stream(arr);
Employee e1 = new Employee(1001,"Tom"); Employee e2 = new Employee(1002,"Jerry"); Employee[] arr1 = new Employee[]{e1,e2}; Stream<Employee> stream1 = Arrays.stream(arr1); } @Test public void test3(){ List<Integer> list = new ArrayList<>(); Stream<List<Integer>> list1 = Stream.of(list); Stream<Object> objectStream = Stream.of(list.toArray());
int[] array = new int[3]; Stream<int[]> array1 = Stream.of(array); Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6); } @Test public void test4(){ Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println); Stream.generate(Math::random).limit(10).forEach(System.out::println); } }
|
4、Stream中间操作
有了Stream类的对象以后,可以执行中间操作。中间操作可以链式操作,只有遇到终止操作的时候,中间操作才会一次性执行,称为“惰性求值”。
中间操作分成以下3种:
①筛选与切片
| 方法 |
描述 |
filter(Predicate p) |
接收Lambda表达式,从流中排除某些元素 |
distinct() |
筛选,通过流所生成元素的hashCode()和equals()去除重复元素 |
limit(long maxSize) |
截断流,使其元素不超过给定数量 |
skip(long n) |
跳过元素,返回一个跳过前n个元素的流。 若流中元素不足n个,则返回一个空流。与limit(n)操作互补 |
②映射
| 方法 |
描述 |
map(Functionf) |
接收一个函数作为参数,该函数会被应用到每个元素上, 并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上, 产生一个新的DoubleStream。 |
mapToInt(ToIntFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上, 产生一个新的IntStream。 |
mapToLong(ToLongFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上, 产生一个新的LongStream。 |
flatMap(Function f) |
接收一个函数作为参数,将流中的每个值都换成另一个流, 然后把所有流连接成一个流 |
③排序
| 方法 |
描述 |
sorted() |
产生一个新流,按自然顺序排序 |
sorted(Comparator com) |
产生一个新流,按比较器顺序排序 |
5、Stream终止操作
终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void。
流进行了终止操作后,不能再次被使用。
终止操作有以下几种:
| 方法 |
描述 |
count() |
返回流中元素总数 |
max(Comparator c) |
返流中最大值 |
min(Comparator) |
返回流中最小值 |
forEach(Consumer c) |
内部迭代 (使用Collection接口需要用户去做迭代,称为外部迭代) |
reduce(T iden,BinaryOperator b), 规约 |
可以将流中元素反复结合起来,得到一个值。 返回T |
reduce(BinaryOperator b), 规约 |
可以将流中元素反复结合起来,得到一个值。 返回Optional |
collect(Collector c),收集 |
将流转换为其他形式。 接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
map和reduce的连接通常称为map-reduce模式。
Collector接口(收集器)中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
另外,Collectors类提供了很多静态方法,可以方便地创建Collector实例,具体方法如下表:
| 方法 |
返回类型 |
作用 |
toList |
List<T> |
把流中元素收集到List |
toSet |
Set<T> |
把流中元素收集到Set |
toCollection |
Collection<T> |
把流中元素收集到创建的集合 |
counting |
Long |
计算流中元素的个数 |
summingInt |
Integer |
对流中元素的整数属性求和 |
averagingInt |
Double |
计算流中元素Integer属性的平均值 |
summarizingInt |
IntSummaryStatistics |
收集流中Integer属性的统计值。如:平均值 |
joining |
String |
连接流中每个字符串 |
maxBy |
Optional<T> |
根据比较器选择最大值 |
minBy |
Optional<T> |
根据比较器选择最小值 |
reducing |
归约产生的类型 |
从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
collectingAndThen |
转换函数返回的类型 |
包裹另一个收集器,对其结果转换函数 |
groupingBy |
Map<K, List<T>> |
根据某属性值对流分组,属性为K结果为V |
partitioningBy |
Map<Boolean,List<T>> |
根据true或false进行分区 |
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
|
class EmployeeData { public static List<Employee> getEmployees(){ List<Employee> list = new ArrayList<>(); list.add(new Employee(1001, "马化腾", 34, 6000.38)); list.add(new Employee(1002, "马云", 12, 9876.12)); list.add(new Employee(1003, "刘强东", 33, 3000.82)); list.add(new Employee(1004, "雷军", 26, 7657.37)); list.add(new Employee(1005, "李彦宏", 65, 5555.32)); list.add(new Employee(1006, "比尔盖茨", 42, 9500.43)); list.add(new Employee(1007, "任正非", 26, 4333.32)); list.add(new Employee(1008, "扎克伯格", 35, 2500.32)); return list; } }
public class StreamAPITest2 { @Test public void test1(){ List<Employee> employees = EmployeeData.getEmployees(); boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18); System.out.println(allMatch);
boolean noneMatch = employees.stream(). noneMatch(e -> e.getName().startsWith("雷")); System.out.println(noneMatch); Optional<Employee> employee = employees.stream().findFirst(); System.out.println(employee); Optional<Employee> employee1 = employees.parallelStream().findAny(); System.out.println(employee1); } @Test public void test2(){ List<Employee> employees = EmployeeData.getEmployees(); long count = employees.stream().f ilter(e -> e.getSalary() > 5000). count(); System.out.println(count); Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary()); Optional<Double> maxSalary = salaryStream.max(Double::compare); System.out.println(maxSalary); Optional<Employee> employee = employees.stream(). min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())); System.out.println(employee); employees.stream().forEach(System.out::println); employees.forEach(System.out::println); } @Test public void test3(){ List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer sum = list.stream().reduce(0, Integer::sum); System.out.println(sum);
List<Employee> employees = EmployeeData.getEmployees(); Stream<Double> salaryStream = employees.stream().map(Employee::getSalary); Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2); System.out.println(sumMoney.get()); } @Test public void test4(){ List<Employee> employees = EmployeeData.getEmployees(); List<Employee> employeeList = employees.stream(). filter(e -> e.getSalary() > 6000). collect(Collectors.toList()); employeeList.forEach(System.out::println); Set<Employee> employeeSet = employees.stream(). filter(e -> e.getSalary() > 6000). collect(Collectors.toSet()); employeeSet.forEach(System.out::println); } }
|
五、Optional类
Optional<T>类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。是为了避免出现空指针异常而创建的。
Optional容器类,存放的是对象
原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
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 orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出由Supplier接口实现类提供的异常。
应用举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Girl{ private String name; public Girl() {} public Girl(String name) {this.name = name;} } public class OptionalTest{ public static void main(String[] args) { Girl girl = new Girl("Jack"); System.out.println(Optional.ofNullable(girl). orElse(new Girl("Jessica"))); System.out.println(Optional.ofNullable(null). orElse(new Girl("Tom"))); } }
|