JDK 8新特性概述
在自己工作、开发、学习中,特别是在阅读别人的项目源码时,总是能看到使用Lambda表达式和Stream流的代码写法,这两个新特性是JDK 8主要的特性和自己的编程风格。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8,现如今已经JDK14都出来了,所以了解,阅读,和使用JDK 8新特性是非常有必要的,在面试的过程中也会经常被问到。
Java8 新增了非常多的特性,我们主要学习以下几个:
1. Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
-
默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
-
函数式接口 − 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 java8引入@FunctionalInterface 注解声明该接口是一个函数式接口。
-
Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
-
Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
-
Base64 − Base64编码已经成为Java类库的标准,Java 8 内置了 Base64 编码的编码器和解码器。
使用下面的案例感受一下JDK8 的编程风格
/** * Created by tao. * Date: 2021/9/3 13:34 * 描述: jdk1.8 排序 */ import java.util.Collections; import java.util.List; import java.util.ArrayList; import java.util.Comparator; public class Demo1 { public static void main(String args[]) { List<String> names1 = new ArrayList<String>(); names1.add("Google "); names1.add("Runoob "); names1.add("Taobao "); names1.add("Baidu "); names1.add("Sina "); List<String> names2 = new ArrayList<String>(); names2.add("Google "); names2.add("Runoob "); names2.add("Taobao "); names2.add("Baidu "); names2.add("Sina "); Demo1 tester = new Demo1(); System.out.println("使用 Java 7 语法: "); tester.sortUsingJava7(names1); System.out.println(names1); System.out.println("使用 Java 8 语法: "); tester.sortUsingJava8(names2); System.out.println(names2); } // 使用 java 7 排序 private void sortUsingJava7(List<String> names) { Collections.sort(names, new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.compareTo(s2); } }); } // 使用 java 8 排序 private void sortUsingJava8(List<String> names) { Collections.sort(names, (s1, s2) -> s1.compareTo(s2)); } }
Lambda 表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
lambda表达式语法
(parameters) -> expression 或 (parameters) ->{ statements; }
lambda表达式的重要特征:
* 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
* 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
* 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
* 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
代码案例一:Lambda 表达式基本使用
import java.util.HashMap; import java.util.Map; /** * Created by tao. * Date: 2021/9/3 14:03 * 描述: 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) */ public class Demo2 { public static void main(String[] args) { Demo2 tester = new Demo2(); // 类型声明 MathOperation addition = (int a, int b) -> a + b; // 不用类型声明 MathOperation subtraction = (a, b) -> a - b; // 大括号中的返回语句 MathOperation multiplication = (int a, int b) -> a * b; // 没有大括号及返回语句 MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); // 不用括号 GreetingService greetService1 = message -> System.out.println("Hello " + message); // 用括号 GreetingService greetService2 = (message) -> System.out.println("Hello " + message); greetService1.sayMessage("Runoob"); greetService2.sayMessage("Google"); Map<String, Integer> items = new HashMap<>(); items.put("A", 10); items.put("B", 20); items.put("C", 30); items.put("D", 40); items.put("E", 50); items.put("F", 60); //普通方式遍历 Map System.out.println("普通方式遍历 Map"); for (Map.Entry<String, Integer> entry : items.entrySet()) { System.out.println("Item : " + entry.getKey() + " Count : " + entry.getValue()); } //在 java8 中,可以使用 forEach + lambda 表达式循环 Map。3 System.out.println("在 java8 中,可以使用 forEach + lambda 表达式遍历Map"); items.forEach((k, v) -> System.out.println("Item : " + k + " Count : " + v)); items.forEach((k, v) -> { if ("E".equals(k)) { System.out.println("Hello E"); } }); } interface MathOperation { int operation(int a, int b); } interface GreetingService { void sayMessage(String message); } private int operate(int a, int b, MathOperation mathOperation) { return mathOperation.operation(a, b); } }
运行结果如下:
代码案例二:Lambda 表达式变量作用域问题
/** * Created by tao. * Date: 2021/9/3 14:26 * 描述: Lambda 表达式:变量作用域 */ public class Demo3 { private final static String salutation = "Hello! "; public static void main(String args[]) { //不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。 GreetingService greetService1 = message -> System.out.println(salutation + message); greetService1.sayMessage("Runoob"); //可以直接在 lambda 表达式中访问外层的局部变量: final int num = 1; Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); // 输出结果为 3 } interface GreetingService { void sayMessage(String message); } public interface Converter<T1, T2> { void convert(int i); } }
运行结果如下
方法引用
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。
在 Car 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。
代码如下:
/** * Created by tao. * Date: 2021/9/3 14:41 * 描述: 方法引用:引用通过方法的名字来指向一个方法,JDK1.8中方法引用使用一对冒号 :: */ public class Demo4 { public static void main(String[] args) { //构造器引用:它的语法是Class::new,或者更一般的Class< T >::new final Car car = Car.create(Car::new); final List<Car> cars = Arrays.asList(car); //静态方法引用:它的语法是Class::static_method, cars.forEach(Car::collide); //特定类的任意对象的方法引用:它的语法是Class::method cars.forEach(Car::repair); //特定对象的方法引用:它的语法是instance::method final Car police = Car.create(Car::new); cars.forEach(police::follow); //方法引用实例 List<String> names = new ArrayList(); names.add("Google"); names.add("Runoob"); names.add("Taobao"); names.add("Baidu"); names.add("Sina"); names.forEach(System.out::println); } @FunctionalInterface public interface Supplier<T> { T get(); } static class Car { //Supplier是jdk1.8的接口,这里和lamda一起使用了 public static Car create(final Supplier<Car> supplier) { return supplier.get(); } public static void collide(final Car car) { System.out.println("Collided " + car.toString()); } public void follow(final Car another) { System.out.println("Following the " + another.toString()); } public void repair() { System.out.println("Repaired " + this.toString()); } } }
运行结果如下:
函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式,我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型
但确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
如定义了一个函数式接口如下:
@FunctionalInterface interface GreetingService { void sayMessage(String message); }
函数式接口可以对现有的函数友好地支持 lambda。
JDK 1.8 新增加的函数接口:
java.util.function
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
下面使用代码实现测试Predicate
import java.util.Arrays; import java.util.List; import java.util.function.Predicate; /** * Created by tao. * Date: 2021/9/3 15:05 * 描述: Java 8 函数式接口 * Predicate<T>接口 * 接受一个输入参数,返回一个布尔值结果。 */ public class Demo5 { public static void main(String args[]) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); // Predicate<Integer> predicate = n -> true // n 是一个参数传递到 Predicate 接口的 test 方法 // n 如果存在则 test 方法返回 true System.out.println("输出所有数据:"); // 传递参数 n eval(list, n -> true); // Predicate<Integer> predicate1 = n -> n%2 == 0 // n 是一个参数传递到 Predicate 接口的 test 方法 // 如果 n%2 为 0 test 方法返回 true System.out.println("输出所有偶数:"); eval(list, n -> n % 2 == 0); // Predicate<Integer> predicate2 = n -> n > 3 // n 是一个参数传递到 Predicate 接口的 test 方法(传递的是一个方法) // 如果 n 大于 3 test 方法返回 true System.out.println("输出大于 3 的所有数字:"); eval(list, n -> n > 3); } public static void eval(List<Integer> list, Predicate<Integer> predicate) { for (Integer n : list) { if (predicate.test(n)) { System.out.println(n + " "); } } } }
代码运行结果如下
默认方法
Java 8 新增了接口的默认方法。
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个 default 关键字即可实现默认方法。
为什么要使用默认方法?
接口支持申明带实现的方法,否则在项目中如果某个接口被很多类给实现,改动一个接口的方法,则实现它的类全部都要改变
所以,Java8中引入了一种新的机制:接口支持申明带实现的方法(不需要实现类去实现的默认方法)。
默认方法测试案例如下:
/** * Created by tao. * Date: 2021/9/3 15:39 * 描述: 默认方法: * 接口支持申明带实现的方法,否则在项目中如果某个接口被很多类给实现,改动一个接口的方法,则实现它的类全部都要改变 * 所以,Java8中引入了一种新的机制:接口支持申明带实现的方法(不需要实现类去实现的默认方法)。 */ public class Demo6 { public static void main(String args[]) { Vehicle vehicle = new Car(); vehicle.print(); } interface Vehicle { default void print() { System.out.println("我是一辆车!"); } static void blowHorn() { System.out.println("按喇叭!!!"); } } interface FourWheeler { default void print() { System.out.println("我是一辆四轮车!"); } } static class Car implements Vehicle, FourWheeler { public void print() { Vehicle.super.print(); FourWheeler.super.print(); Vehicle.blowHorn(); System.out.println("我是一辆汽车!"); } } }
运行结果如下
Java8中接口的默认方法的使用场景和细节可以参考:https://www.cnblogs.com/sum-41/p/10878807.html
Stream流
Stream简介
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
+--------------------+ +------+ +------+ +---+ +-------+ | stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect| +--------------------+ +------+ +------+ +---+ +-------+
以上的流程转换为 Java 代码为:
List<Integer> transactionsIds = ids.stream() .filter(b -> b.getColor() == RED) .sorted((x,y) -> x.getWeight() - y.getWeight()) .mapToInt(Widget::getWeight) .sum();
什么是 Stream
Stream(流)是一个来自数据源的元素队列并支持聚合操作
* 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
* 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
* 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
* Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
* 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
注意:
Stream流属于管道流,只能被消费(使用)一次,第一个Stream流调用完毕方法,数据就会流转到下一个Stream上,而这时第一个Stream流已经使用完毕,就会关闭了,所以第一个Stream流就不能再调用方法了。
代码测试一
该测试案例实现了在开发中常用得到Steam流操作,主要有以下示例:
1. 顺序流处理
2. 并行流处理
3. filter 方法用于通过设置的条件过滤出元素
4. Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串
5. map 方法用于映射每个元素到对应的结果
6. 统计:主要用于int、double、long等基本类型上
7. limit 方法用于获取指定数量的流
8. reduce 归约:也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
9. 归集(toList/toSet/toMap) 因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。
10. 接合(joining) joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
使用到的Person实体类:
public class Person { private String name; // 姓名 private int salary; // 薪资 private int age; // 年龄 private String sex; //性别 private String area; // 地区 // 构造方法 public Person(String name, int salary, int age, String sex, String area) { this.name = name; this.salary = salary; this.age = age; this.sex = sex; this.area = area; } public Person() { } // 省略了get和set,请自行添加 }
测试代码如下:
import cn.kt.domin.Person; import java.util.*; import java.util.stream.Collectors; /** * Created by tao. * Date: 2021/9/3 16:02 * 描述: Java 8 Stream * Stream流属于管道流,只能被消费(使用)一次 * 第一个Stream流调用完毕方法,数据就会流转到下一个Stream上 * 而这时第一个Stream流已经使用完毕,就会关闭了 * 所以第一个Stream流就不能再调用方法了 */ public class Demo7 { public static void main(String args[]) { // 计算空字符串 List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); // 输出10个随机数 Random random = new Random(); System.out.println("使用 Java 8: "); System.out.println("列表: " + strings); // 1. 顺序流处理 long count = strings.stream().filter(String::isEmpty).count(); System.out.println("空字符串的数量为(顺序流): " + count); // 2. 并行处理 count = strings.parallelStream().filter(String::isEmpty).count(); System.out.println("空字符串的数量为(并行处理): " + count); // 3. filter 方法用于通过设置的条件过滤出元素。 count = strings.stream().filter(string -> string.length() == 3).count(); System.out.println("字符串长度为 3 的数量为: " + count); // 4. Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串 List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("筛选后的列表: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString); // 5. map 方法用于映射每个元素到对应的结果 List<Integer> squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList()); System.out.println("Squares List: " + squaresList); System.out.println("列表: " + integers); // 6. 统计:主要用于int、double、long等基本类型上 IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("所有数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage()); System.out.println("随机数: "); // 7. limit 方法用于获取指定数量的流 System.out.print("limit 获取前 x 个指定数量的流:"); integers.stream().limit(5).sorted().forEach(System.out::println); System.out.print("skip 获取指定数量的流:"); integers.stream().skip(2).forEach(System.out::println); // 8. reduce 归约:也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。 List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4); // 求和方式1 Optional<Integer> sum = list.stream().reduce((x, y) -> x + y); // 求和方式2 Optional<Integer> sum2 = list.stream().reduce(Integer::sum); // 求和方式3 Integer sum3 = list.stream().reduce(0, Integer::sum); // 求乘积 Optional<Integer> product = list.stream().reduce((x, y) -> x * y); // 求最大值方式1 Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y); // 求最大值写法2 Integer max2 = list.stream().reduce(1, Integer::max); System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3); System.out.println("list求积:" + product.get()); System.out.println("list求和:" + max.get() + "," + max2); //9. 归集(toList/toSet/toMap) 因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。 List<Integer> arrList = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20); List<Integer> listNew = arrList.stream().filter(x -> x % 2 == 0).collect(Collectors.toList()); Set<Integer> set = arrList.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet()); List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000) .collect(Collectors.toMap(Person::getName, p -> p)); System.out.println("toList:" + listNew); System.out.println("toSet:" + set); System.out.println("toMap:" + map); //10. 接合(joining) joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。 List<String> slist = Arrays.asList("A", "B", "C"); String string = slist.stream().collect(Collectors.joining("-")); System.out.println("拼接后的字符串:" + string); } }
测试结果如下:
代码测试二
该测试是基于Person实体类,主要是应用于实际场景而实现的案例,主要实现的案例场景有:
* 案例一:筛选员工中工资高于8000的人,并形成新的集合。
* 案例二:将员工的薪资全部增加1000。
* 案例三:统计员工人数、平均工资、工资总额、最高工资。
* 案例四:将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组
* 案例五:每个员工减去起征点后的薪资之和
* 案例六:将员工按工资由高到低(工资一样则按年龄由大到小)排序
测试代码如下:
import cn.kt.domin.Person; import java.util.*; import java.util.stream.Collectors; /** * Created by tao. * Date: 2021/9/3 17:01 * 描述: */ public class PersionDemo { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); personList.add(new Person("Owen", 9500, 25, "male", "New York")); personList.add(new Person("Alisa", 7900, 26, "female", "New York")); //案例一:筛选员工中工资高于8000的人,并形成新的集合。 System.out.println("=======================案例一===================="); List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName) .collect(Collectors.toList()); System.out.println("高于8000的员工姓名:" + fiterList); //案例二:将员工的薪资全部增加1000。 System.out.println("=======================案例二===================="); // 不改变原来员工集合的方式 List<Person> personListNew = personList.stream().map(person -> { Person personNew = new Person(person.getName(), 0, 0, null, null); personNew.setSalary(person.getSalary() + 10000); return personNew; }).collect(Collectors.toList()); System.out.println(personListNew); // 改变原来员工集合的方式 List<Person> personListNew2 = personList.stream().map(person -> { person.setSalary(person.getSalary() + 10000); return person; }).collect(Collectors.toList()); System.out.println(personListNew2); //案例三:统计员工人数、平均工资、工资总额、最高工资。 System.out.println("=======================案例三===================="); // 求总数 long count = (long) personList.size(); // 求平均工资 Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary)); // 求最高工资 Optional<Integer> max = personList.stream().map(Person::getSalary).max(Integer::compare); // 求工资之和 int sum = personList.stream().mapToInt(Person::getSalary).sum(); // 一次性统计所有信息 DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary)); System.out.println("员工总数:" + count); System.out.println("员工平均工资:" + average); System.out.println("员工最高工资:" + max.get()); System.out.println("员工工资总和:" + sum); System.out.println("员工工资所有统计:" + collect); //案例四:将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组 System.out.println("=======================案例四===================="); // 将员工按薪资是否高于8000分组 Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000)); // 将员工按性别分组 Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex)); // 将员工先按性别分组,再按地区分组 Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea))); System.out.println("员工按薪资是否大于8000分组情况:" + part); System.out.println("员工按性别分组情况:" + group); System.out.println("员工按性别、地区:" + group2); //案例五:每个员工减去起征点后的薪资之和 System.out.println("=======================案例五===================="); sum = personList.stream().map(Person::getSalary).reduce(0, (i, j) -> (i + j - 5000)); System.out.println("员工扣税薪资总和:" + sum); //案例六:将员工按工资由高到低(工资一样则按年龄由大到小)排序 System.out.println("=======================案例六===================="); // 按工资升序排序(自然排序) List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName) .collect(Collectors.toList()); // 按工资倒序排序 List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()) .map(Person::getName).collect(Collectors.toList()); // 先按工资再按年龄升序排序 List<String> newList3 = personList.stream() .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName) .collect(Collectors.toList()); // 先按工资再按年龄自定义排序(降序) List<String> newList4 = personList.stream().sorted((p1, p2) -> { if (p1.getSalary() == p2.getSalary()) { return p2.getAge() - p1.getAge(); } else { return p2.getSalary() - p1.getSalary(); } }).map(Person::getName).collect(Collectors.toList()); System.out.println("按工资升序排序:" + newList); System.out.println("按工资降序排序:" + newList2); System.out.println("先按工资再按年龄升序排序:" + newList3); System.out.println("先按工资再按年龄自定义降序排序:" + newList4); } }
测试结果如下:
Optional类
Optional 类简介
第一次遇见Optional 类的时候还是在使用SpringData JPA的时候,使用它内部 findById 等方法的时候,返回的是一个Optional修饰的实体类,当时只知道可以使用 .get() 方法将里面的实体获取出来,后来发现这个类是JDK8 的新特性增加的。
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
常用类方法
Optional类使用测试
该测试Demo也使用到了上文提供的Person实体类
主要的测试方法如下:
1. of方法测试:返回一个指定Optional的非空值,如果是控制,会报NullPointException
2. ofNullable测试:返回一个Optional描述的指定值,如果为空,则返回Optional
3. get测试:如果Optional存在一个值,则返回,不存在则抛出NoSuchElementException
4. isPresent测试:如果有值返回true,如果为null返回false
5. ifPresent测试:该方法也可以检测是否为null,同时该方法还可以接受一个Consumer参数
6. orElse测试:如果返回值存在则返回,否则返回other
7. orElseGet测试:如果有值则返回,没有则调用Supplier函数,并返回
8. orElseThrow:有值时返回该值,没有时则抛出异常
9. filter测试:接受一个Predicate参数,返回测试结果为true的值。如果测试结果为false,会返回一个空的 Optional
测试代码如下”:
import cn.kt.domin.Person; import java.util.Optional; /** * Created by tao. * Date: 2021/9/6 13:25 * 描述: Optional类使用 */ public class Demo8 { public static void main(String args[]) { Demo8 demo8 = new Demo8(); demo8.optionalTest1Of(); demo8.optionalTest2OfNullable(); demo8.optionalTest3Get(); demo8.optionalTest4IsPresent(); demo8.optionalTest5IfPresent(); demo8.optionalTest6OrElse(); demo8.optionalTest7OrElseGet(); demo8.optionalTest8OrElseThrow(); demo8.optionalTest9Filter(); } /** * 1. of方法测试:返回一个指定Optional的非空值,如果是控制,会报NullPointException。 */ public void optionalTest1Of() { Person person1 = new Person(); Person person2 = new Person(); person2.setName("张三"); Person person3 = null; Optional<Person> user1 = Optional.of(person1); Optional<Person> user2 = Optional.of(person2); // 打印空user对象 System.out.println("person1:" + user1.toString()); // 打印name = 张三的user对象 System.out.println("person2:" + user2.toString()); // NullPointerException Optional<Person> user3 = Optional.of(person3); } /** * 2. ofNullable测试:返回一个Optional描述的指定值,如果为空,则返回Optional。 */ public void optionalTest2OfNullable() { Person u1 = new Person(); Person u2 = new Person(); u2.setName("张三"); Person u3 = null; Optional<Person> u11 = Optional.ofNullable(u1); Optional<Person> u12 = Optional.ofNullable(u2); Optional<Person> u13 = Optional.ofNullable(u3); // 打印空user对象 System.out.println("u11:" + u11.toString()); // 打印name = 张三的user对象 System.out.println("u12:" + u12.toString()); // 打印empty System.out.println("u13:" + u13.toString()); // 可以抛出自定义异常 Optional.ofNullable(u3).orElseThrow(NullPointerException::new); } /** * 3. get测试:如果Optional存在一个值,则返回,不存在则抛出NoSuchElementException。 */ public void optionalTest3Get() { Person u2 = new Person(); u2.setName("张三"); Person u3 = null; Person user2 = Optional.ofNullable(u2).get(); // 打印name = 张三的Person对象 System.out.println("user2:" + user2); // NoSuchElementException Person user3 = Optional.ofNullable(u3).get(); System.out.println("user3:" + user3); } /** * 4. isPresent测试:如果有值返回true,如果为null返回false。 */ public void optionalTest4IsPresent() { Person u2 = new Person(); u2.setName("张三"); Person u3 = null; boolean booleanUser2 = Optional.ofNullable(u2).isPresent(); boolean booleanUser3 = Optional.ofNullable(u3).isPresent(); // true System.out.println("booleanUser2:" + booleanUser2); // false System.out.println("booleanUser3:" + booleanUser3); } /** * 5. ifPresent测试:该方法也可以检测是否为null,同时该方法还可以接受一个Consumer参数。 */ public void optionalTest5IfPresent() { Person u2 = new Person(); u2.setName("张三"); Optional.ofNullable(u2).ifPresent(user -> user.setAge(21)); // 21 System.out.println("u2:" + u2.getAge()); } /** * 6. orElse测试:如果返回值存在则返回,否则返回other。 */ public void optionalTest6OrElse() { Person u2 = new Person(); u2.setName("张三"); Person u3 = null; Person user = Optional.ofNullable(u3).orElse(u2); // 打印u2对象 System.out.println("user:" + user.toString()); } /** * 7. orElseGet测试:如果有值则返回,没有则调用Supplier函数,并返回。 */ public void optionalTest7OrElseGet() { Person u2 = new Person(); u2.setName("张三"); Person u3 = null; Person user1 = Optional.ofNullable(u3).orElseGet(() -> new Person()); Person user2 = Optional.ofNullable(u3).orElseGet(() -> u2); // 打印Person空对象 System.out.println("user1:" + user1.toString()); // u2对象 System.out.println("user2:" + user2.toString()); } /** * 8. orElseThrow:有值时返回该值,没有时则抛出异常。 */ public void optionalTest8OrElseThrow() { Person u1 = null; Person u2 = new Person(); u2.setName("张三"); Person user2 = Optional.ofNullable(u2).orElseThrow(() -> new IllegalStateException()); // 打印u2对象 System.out.println("user2:" + user2.toString()); // IllegalStateException Person user1 = Optional.ofNullable(u1).orElseThrow(() -> new IllegalStateException()); } /** * 9. filter测试:接受一个Predicate参数,返回测试结果为true的值。如果测试结果为false,会返回一个空的 Optional。 */ public void optionalTest9Filter() { Person user = new Person(); user.setName("张三"); user.setAge(11); Person user1 = Optional.ofNullable(user).filter(u -> u.getName().equals("张三") && u.getAge() == 11).orElseThrow(() -> new NullPointerException()); // Person对象 System.out.println("user1:" + user1); // NullPointerException Person user2 = Optional.ofNullable(user).filter(u -> u.getName().equals("张三") && u.getAge() == 22).orElseThrow(() -> new NullPointerException()); System.out.println("user2:" + user2); } }
内置Base64
在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
* 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
* URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
* MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。
其中内嵌类和内嵌方法如下
测试代码如下
import java.io.UnsupportedEncodingException; import java.util.Base64; import java.util.UUID; /** * Created by tao. * Date: 2021/9/6 13:44 * 描述: * 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。 * URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。 * MIME:输出映射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。 */ public class Demo9 { public static void main(String args[]) { try { // 使用基本编码 String base64encodedString = Base64.getEncoder().encodeToString("如我西沉".getBytes("utf-8")); System.out.println("Base64 编码字符串 (基本) :" + base64encodedString); // 解码 byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString); System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8")); base64encodedString = Base64.getUrlEncoder().encodeToString("http://qkongtao.cn".getBytes("utf-8")); System.out.println("Base64 编码字符串 (URL) :" + base64encodedString); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 10; ++i) { stringBuilder.append(UUID.randomUUID().toString()); } byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8"); String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes); System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString); } catch (UnsupportedEncodingException e) { System.out.println("Error :" + e.getMessage()); } } }
运行结果如下
源码下载
https://gitee.com/KT1205529635/hello-jdk8
小结
本次JDK 8新特性的学习是根据查阅相关资料,并且通过自己的代码手动实现,并且进行整理,整理的Demo和案例都相对清晰易懂,对于我以后看笔记和有缘朋友的学习应该会有一定的帮助。学习主要参考了菜鸟教程和微信公众号的相关文章,这篇文章的篇幅也相对比较大,希望对各位和自己之后的学习会有一定的帮助。