Java 8 新特性
学习内容来自尚硅谷Java入门视频教程(在线答疑+Java面试真题)
简介
- 速度更快
- 代码更少(lambda 表达式)
- 强大的 stream API
- 便于并行
- 最大化减少空指针异常
- Nashorm引擎,运行 JVM 运行 JS 应用
Lambda 表达式
Lambda 是一个匿名函数,可以理解为一段可传递的代码(将代码像数据一样传递)
使用 Lambda 表达式可以写出更加简洁灵活的代码
首先看两个例子:
@Test
public void test1() {
// 传统方式,创建匿名内部类实现一个线程对象
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("遇到困难睡大觉");
}
};
runnable.run();
System.out.println("====================");
// lambda 方式
Runnable r2 = () -> System.out.println("遇到困难就干饭");
r2.run();
}
@Test
public void test2() {
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(554);
list.add(465);
list.add(9);
list.add(6);
// 传统方法,重写排序的 compare 方法
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
for (Integer integer : list) {
System.out.println(integer);
}
System.out.println("==================");
// lambda 实现自定义逆序排序
Collections.sort(list, (a, b) -> b - a);
for (Integer integer : list) {
System.out.println(integer);
}
}
在上述例子中
->
就是 lambda 操作符,也叫做箭头操作符->
左边是 lambda 形参列表,其实就是接口中抽象方法的形参列表->
右边是 lambda 体,就是就是重写的抽象方法的方法体
lambda 的本质还是接口的匿名内部类的实例对象
lambda 的语法大致分为六类:
- 要实现的抽象方法无参,无返回值(void)
public void test1() {
// 传统方式,创建匿名内部类实现一个线程对象
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("遇到困难睡大觉");
}
};
runnable.run();
System.out.println("====================");
// lambda 方式
Runnable r2 = () -> {
System.out.println("遇到困难就干饭");
};
r2.run();
}
- 要实现的抽象方法需要一个参数,无返回值(void)
public void test3() {
// 传统方式
LambdaInterface lambdaInterface = new LambdaInterface() {
@Override
public void eat(String food) {
System.out.println("遇到困难吃" + food);
}
};
lambdaInterface.eat("牛肉烩面");
System.out.println("================");
// lambda 方式
LambdaInterface lambdaInterface1 = (String a) -> {
System.out.println("遇到困难也不吃" + a);
};
lambdaInterface1.eat("牛肉烩馍");
}
- 要实现的抽象方法需要一个参数(省略参数类型,可有编译器自行推断出,称为”类型维护“),无返回值(void)
public void test3() {
// 传统方式
LambdaInterface lambdaInterface = new LambdaInterface() {
@Override
public void eat(String food) {
System.out.println("遇到困难吃" + food);
}
};
lambdaInterface.eat("牛肉烩面");
System.out.println("================");
// lambda 方式
LambdaInterface lambdaInterface1 = (a) -> {
System.out.println("遇到困难也不吃" + a);
};
lambdaInterface1.eat("牛肉烩馍");
}
- 要实现的抽象方法需要一个参数,无返回值(void),省略参数类型和参数的小括号
public void test3() {
// 传统方式
LambdaInterface lambdaInterface = new LambdaInterface() {
@Override
public void eat(String food) {
System.out.println("遇到困难吃" + food);
}
};
lambdaInterface.eat("牛肉烩面");
System.out.println("================");
// lambda 方式
LambdaInterface lambdaInterface1 = a -> {
System.out.println("遇到困难也不吃" + a);
};
lambdaInterface1.eat("牛肉烩馍");
}
- 要实现的抽象方法需要多个参数,多条执行语句,并且可以有返回值
public void test4() {
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator.compare(45, 21));
System.out.println("==============");
Comparator<Integer> comparator1 = (a, b) -> {
System.out.println(a);
System.out.println(b);
return a.compareTo(b);
};
System.out.println(comparator1.compare(45, 21));
}
- 要实现的抽象方法参数不限,只有一条执行语句,并且可以有返回值。可以省略大括号和 return
public void test5() {
// 省略前
Comparator<Integer> comparator1 = (a, b) -> {
return a.compareTo(b);
};
System.out.println(comparator1.compare(45, 21));
System.out.println("==============");
// 省略后
Comparator<Integer> comparator2 = (a, b) -> a.compareTo(b);
System.out.println(comparator1.compare(45, 21));
}
总结:
- lambda 形参列表的类型可以省略
- lambda 形参数量为 1 时,可以省略小括号
- lambda 执行语句只有一条时,可以省略大括号和 return
- lambda 用来实现接口的匿名内部类的实例对象,且接口中只能由一个抽象方法,这种接口也成为函数式接口
函数式(Functional)接口
若一个接口只声明了一个抽象方法,则该接口为函数式接口
在上面说到了函数式接口,在 Java 中,函数式接口一般会加上注解 @FunctionalInterface
,如
加上注解后,编译器就会检验接口是否为函数式接口,并且 javadoc 也会包含声明,说明该接口为函数式接口
Java 内置四大核心函数式接口
小测试:
public class LambdaTest2 {
@Test
public void testConsumer() {
happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("给" + aDouble + "干饭去");
}
});
System.out.println("---------------------");
happyTime(500, d -> System.out.println("给" + d + "上网去"));
}
public void happyTime(double money, Consumer<Double> consumer) {
consumer.accept(money);
}
@Test
public void testPredicate() {
List<String> list = new ArrayList<>();
list.add("背景");
list.add("北京");
list.add("南京");
list.add("维京");
list.add("西京");
List<String> list1 = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(list1);
System.out.println("------------------------");
List<String> list2 = filterString(list, s -> s.contains("京"));
System.out.println("list2 = " + list2);
}
public List<String> filterString(List<String> list, Predicate<String> predicate) {
List<String> filterList = new ArrayList<>();
for (String s : list) {
if (predicate.test(s)) {
filterList.add(s);
}
}
return filterList;
}
}
其他接口
方法引用与构造器引用
方法引用
当要传递给 Lambda 体的操作已经有实现的方法,可以直接使用方法引用
方法引用可以看作是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法
使用方式
类(对象) :: 方法名
具体分为三种情况:
-
对象::非静态方法
-
类::静态方法
上面两种的要求:要求 接口中的抽象方法的形参列表和返回值类型 与 方法引用的方法的形参列表和返回值类型 相同(主要针对前两种情况)
-
类::非静态方法
举个栗子:
public class MethodReferenceTest {
// 1. 对象::非静态方法
@Test
public void test1() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("杰瑞狗");
System.out.println("----------------------");
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con2.accept("jerryDog");
}
// 1. 对象::非静态方法
@Test
public void test2() {
Employee emp = new Employee(1001, "jerry", 23, 5000);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());
System.out.println("---------------------");
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
// 2. 类::静态方法
@Test
public void test3() {
Comparator<Integer> com1 = (a, b) -> Integer.compare(a, b);
System.out.println(com1.compare(45, 12));
System.out.println("------------------------");
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(45, 12));
}
// 2. 类::静态方法
@Test
public void test4() {
Function<Double, Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(5.65478));
System.out.println("---------------------");
Function<Double, Long> func2 = Math::round;
System.out.println(func2.apply(6.89798));
}
// 3. 类::非静态方法
@Test
public void test5() {
Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc", "dsw"));
System.out.println("-----------------------------");
// compareTo 有两个参数
// 这里默认是第一个参数调用该类的方法,而传进方法的形参为第二个参数
// :: 左侧为第一个参数的类,右边为需要调用的方法
Comparator<String> com2 = String::compareTo;
System.out.println(com2.compare("abc", "dsw"));
}
// 3. 类::非静态方法
@Test
public void test6() {
BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
System.out.println(pre1.test("abc", "abc"));
System.out.println("-------------------------");
BiPredicate<String, String> pre2 = String::equals;
System.out.println(pre2.test("abc", "sss"));
}
// 3. 类::非静态方法
@Test
public void test7() {
Employee emp = new Employee(1001, "jerry", 45, 7984);
Function<Employee, String> func1 = e -> e.getName();
System.out.println(func1.apply(emp));
System.out.println("---------------------------");
Function<Employee, String> func2 = Employee::getName;
System.out.println(func2.apply(emp));
}
}
构造器引用
与方法引用类似,不过调用的不是方法,还是构造器,返回的结果即为对应类的对象
如:
// 构造器引用
@Test
public void test1() {
Supplier<Employee> sup1 = () -> new Employee();
System.out.println(sup1.get());
System.out.println("-------------------------");
Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2);
}
对应的构造器为:
public Employee() {
System.out.println("调用空参构造器");
}
输出结果为:
调用空参构造器
course.javaBase.java8.Employee@12f41634
-------------------------
调用空参构造器
course.javaBase.java8.Employee@262b2c86
可见,通过构造器引用直接生成了对象
再看个例子:
// 构造器引用
@Test
public void test2() {
Function<Integer, Employee> func1 = id -> new Employee(id);
System.out.println(func1.apply(1001));
System.out.println("-----------------------------");
Function<Integer, Employee> func2 = Employee::new;
System.out.println(func2.apply(1002));
}
对应的构造器
public Employee(int id) {
this.id = id;
System.out.println("构造器调用..");
}
输出结果:
构造器调用..
course.javaBase.java8.Employee@12f41634
-----------------------------
构造器调用..
course.javaBase.java8.Employee@371a67ec
数组引用
其实就是构造器引用,不过对应的返回类型是数组
如:
// 数组引用
@Test
public void test3() {
Function<Integer, String[]> func1 = len -> new String[len];
System.out.println(func1.apply(6).length);
System.out.println("-----------------------");
Function<Integer, String[]> func2 = String[]::new;
System.out.println(func2.apply(8).length);
}
输出结果:
6
-----------------------
8
强大的 Stream API
Stream API (java.util.stream)把真正的函数式编程风格引入 Java,这是目前对 Java 类库最好的补充
Stream API 提供了高效且易于使用的处理数据的方式
Stream
Stream 是用来对数据源进行处理的
Stream 是延迟执行的,具体地讲,分为三个步骤
- 创建 Stream 实例,根据数据源获取一个流
- 中间操作,即准备好对数据源的操作(可能是一系列操作),但不执行
- 终止操作,真正执行刚刚准备好的操作,产生结果,并关闭 Stream
如下图
实例化方式
有四种方式可以实例化 Stream
- 通过集合
- 通过数组
- 通过 Stream 的 of()
- 创建无限流
通过集合
可以通过调用 Collection 类的 default 类型方法来返回流
default Stream<T> stream()
default Stream<T> parallelStream()
public void test1() {
// 获取一个 List 对象
List<Employee> employees = EmployeeData.getEmployees();
// 通过调用 Collection 的 default 类型方法 stream(),返回一个顺序流
// default Stream<T> stream()
Stream<Employee> stream = employees.stream();
// 通过调用 Collection 的 default 类型方法 parallelStream(),返回一个并行流
// default Stream<T> parallelStream()
Stream<Employee> parallelStream = employees.parallelStream();
}
通过数组
通过调用 Arrays 类的静态方法获取流
static <T> Stream<T> stream(T[] array)
public void test2() {
int[] arr = {1, 2, 3, 45, 789};
// static <T> Stream<T> stream(T[] array)
// 因为传进去的数组是 int 类型,所以返回的 stream 为 IntStream 类的对象
IntStream stream = Arrays.stream(arr);
Employee emp1 = new Employee(1001);
Employee emp2 = new Employee(1002);
Employee[] empArr = new Employee[]{emp1, emp2};
// 通过调用 Arrays 类的静态方法获取流
Stream<Employee> stream1 = Arrays.stream(empArr);
}
通过 Stream 的 of()
通过调用 Stream 类的方法 of() 获取
public static<T> Stream<T> of(T... values)
public void test3() {
// 通过调用 Stream 类的方法 of() 获取,of() 为数据
// public static<T> Stream<T> of(T... values)
Stream<Integer> integerStream = Stream.of(1, 2, 52, 22);
}
创建无限流
根据生成的数据获取流
创建无限流有两种方式
- Stream 类中的迭代方法 iterate()
public void test4() {
// Stream 类中的迭代方法
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
/*
其中 final T seed 可以理解为循环开始的初始值
final UnaryOperator<T> f 是继承了函数型接口的函数式接口
*/
// 遍历从 2 开始的 10 个偶数
Stream.iterate(2, t -> t + 2).limit(10).forEach(System.out::println);
/*
其中 final T seed 为 2
final UnaryOperator<T> f 为 lambda 表达式
limit(10) 为循环结束条件,表示生成十个数据就终止
forEach(System.out::println) 是终止操作,参数为每次循环执行的方法,其中 System.out::println 是方法引用
*/
}
- Stream 类中的生成方法 generate()
public void test5() {
// Stream 类中的生成方法
// public static<T> Stream<T> generate(Supplier<T> s)
/*
Supplier<T> s 是供给型接口,用来生成数据
*/
Stream.generate(Math::random).limit(10).forEach(System.out::println);
/*
Math::random 是方法引用,引用 Math 类的 random() 方法生成随机数
limit(10) 为循环结束条件,表示生成十个数据就终止
forEach(System.out::println) 是终止操作,参数为每次循环执行的方法,其中 System.out::println 是方法引用
*/
}
中间操作
中间操作可以是一个操作链,在 Stream 终止操作之前不会真正执行
筛选与切片操作
例子:
// 筛选与切片功能演示
@Test
public void test1() {
List<Employee> list = EmployeeData.getEmployees();
// Stream<T> filter(Predicate<? super T> predicate);
// filter 方法会对 stream 中的数据进行过滤,保留断定型接口方法判断为真的数据
// 查询员工表中年龄大于 100 的员工
list.stream().filter(e -> e.getAge() > 100).limit(2).forEach(System.out::println);
System.out.println("-----------------------");
// limit(n) 截断流,取流的前 n 个数据
list.stream().limit(3).forEach(System.out::println);
System.out.println("-----------------------");
// skip(n) 跳过前 n 个数据
list.stream().skip(5).forEach(System.out::println);
System.out.println("-----------------------");
// distinct() 去重,通过对象的 hashCode() 和 equals 方法判断
list.add(new Employee(1010, "狗杰瑞", 45, 4102.2));
list.add(new Employee(1010, "狗杰瑞", 45, 4102.2));
list.add(new Employee(1010, "狗杰瑞", 45, 4102.2));
list.add(new Employee(1010, "狗杰瑞", 45, 4102.2));
list.stream().distinct().forEach(System.out::println);
System.out.println("-----------------------");
}
映射操作
例子:
// 映射
@Test
public void test2() {
// map(Function f) 通过函数 f 对流中的所有数据进行处理
List<String> list = Arrays.asList("aa", "sds", "qwe", "zxc", "dd");
// 将流中的所有字符串变成大写
list.stream().map(str -> str.toUpperCase(Locale.ROOT)).forEach(System.out::println);
// 获取姓名长度大于 3 的员工的姓名
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().map(Employee::getName).filter(s -> s.length() > 3).forEach(System.out::println);
// 将原集合中的每个字符串处理成流,然后把所有的流组合在一起形成一个流(就像集合里有集合一样)
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest02::fromStringToStream);
// 这里类似双层 for 循环
streamStream.forEach(s -> s.forEach(System.out::println));
// flatMap(Function f) 将流中的每个数据处理为流,然后将所有处理后的流整合成一个流
// 以下输出结果和上方相同
list.stream().flatMap(StreamAPITest02::fromStringToStream).forEach(System.out::println);
}
// 将字符串中多个字符构成的集合转换成对应的 Stream 的实例
public static Stream<Character> fromStringToStream(String str) {
ArrayList<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
排序
例子:
// 排序
@Test
public void test3() {
List<Integer> list = Arrays.asList(12, 45, 78, 5, 1, 74865, 456);
list.stream().sorted().forEach(System.out::println);
// 自定义降序排序
list.stream().sorted((a, b) -> b - a).forEach(System.out::println);
}
终止操作
终止操作会真正执行中间操作(链)并产生结果
匹配与查找
例子:
// 匹配与查找
@Test
public void test1() {
List<Employee> employees = EmployeeData.getEmployees();
System.out.println(employees.stream().allMatch(e -> e.getAge() > 10)); // true
System.out.println(employees.stream().anyMatch(e -> e.getAge() < 11)); // false
System.out.println(employees.stream().noneMatch(e -> e.getAge() < 11)); // true
System.out.println(employees.stream().findFirst());
}
此外还有
归约
例子:
// 归约
@Test
public void test2() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// T reduce(T identity, BinaryOperator<T> accumulator)
// 其中 identity 表示初始值,即 accumulator 计算结果加上 identity 为 reduce 结果
System.out.println(list.stream().reduce(10, Integer::sum)); // 55
// T reduce(BinaryOperator<T> accumulator)
// 返回 accumulator 计算结果
System.out.println(list.stream().reduce(Integer::sum)); // 45
}
收集
其中 Collector 接口的实现主要是 Collectors,主要方法有
例子:
// 收集
@Test
public void test3() {
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> list = employees.stream().filter(e -> e.getAge() > 100).collect(Collectors.toList());
list.forEach(System.out::println);
Set<Employee> set = employees.stream().filter(e -> e.getAge() > 100).collect(Collectors.toSet());
set.forEach(System.out::println);
}
Optional 类
Optional<T> 类是一个容器类,可以保存类型 T 的值,表示这个值存在。
比较常用的功能是用其保存 null,表示空值,这样做可以避免空指针异常
Optional 提供很多有用的方法,方便程序员进行空值检测
创建 Optional 类对象的方法
Optional.of(T t)
创建一个 Optional 实例,t 必须非空Optional.empty()
创建一个空的 Optional 实例Optional.ofNullable(T t)
t 可以为 null
例子:
// 创建
@Test
public void testCreate() {
Dog dog = new Dog();
// of(T t) t 必须非空
Optional<Dog> optionalDog = Optional.of(dog);
System.out.println(optionalDog); // Optional[Dog{name='null'}]
dog = null;
// ofNullable(T t) t 可以为空
Optional<Dog> optionalDog1 = Optional.ofNullable(dog);
System.out.println(optionalDog1); // Optional.empty
Dog dog1 = optionalDog1.orElse(new Dog("杰瑞"));
System.out.println(dog1); // Dog{name='杰瑞'}
}
判断 Optional 容器中是否包含对象
boolean isPresent()
判断是否包含对象void isPresent(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 接口实现方法提供的异常
实例对比
设当前有两个类 Cat 和 Dog,其中 Cat 有 Dog 类型的成员变量 dog,Dog 有 String 类型的成员变量 name
若想实现一个方法获取 Cat 的 dog 的 name,那么有:
public String getDogName(Cat cat) {
return cat.getDog().getName();
}
// 防止空指针异常,优化
public String getDogNamePro(Cat cat) {
if (cat != null) {
Dog dog = cat.getDog();
if (dog != null) {
return dog.getName();
}
}
return null;
}
// 使用 Optional 优化
public String getDogName_Optional(Cat cat) {
Optional<Cat> catOptional = Optional.ofNullable(cat);
Cat cat1 = catOptional.orElse(new Cat(new Dog("jerry")));
Dog dog = cat1.getDog();
Optional<Dog> dogOptional = Optional.ofNullable(dog);
Dog dog1 = dogOptional.orElse(new Dog("杰瑞狗"));
return dog1.getName();
}