JDK8-17性特性
文章目录
-
如何学习新特性?
角度1:新的语法规则(多关注) 自动装箱、自动拆箱、注解、enum、Lambda表达式、方法引用、switch表达式、try-catch变化、record等 角度2:增加、过时、删除API StringBuilder、ArrayList、新的日期时间的API、Optional等 角度3:底层的优化、JVM参数的调整、GC的变化、内存结构(永久代--->元空间)|
1. JDK8新特性——lambda表达式
1.1 JDK8新特性特点
- 速度更快
- 代码更少(增加了新的语法:Lambda表达式)。
- 强大的**Stream APl**
- 便于并行
- **并行流**就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
- Java 8中将并行进行了优化,可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换。
- 最大化减少空指针异常:Optional
- Nashorn引擎,允许在JVM上运行JS应用
- javascript可以在jvm运行
- Nashorn项目在JDK 9中得到改进;在JDK11中 Deprecated,后续JDK15版本中remove。在JDK11中取以代之的是GraalVM。(GraalvM是一个运行时平台,它支持Java和其他基于Java字节码的语言,但也支持其他语言,如JavaScript,Ruby,Python或LLVM。性能是Nashorn的2倍以上。)
1.2 lambda表达式的使用
-
lambda表达式
* Lambda表达式的使用举例:(o1,o2) -> Integer.compare(o1,o2); * Lambda表达式的格式举例:lambda形参列表-> lambda体 * Lambda表达式的格式 -> : lambda操作符(箭头操作符) ->的左边: lambda形参列表,对应着要重写的接口中的抽象方法的形参列表。 ->的右边: lambda体,对应着接口的实现类要重写的方法的方法体。 * Lambda表达式的本质: -> lambda表达式作为接口的实现类的对象。 -> lambda表达式是一个匿名函数。
-
函数式接口
-
什么是函数式接口?为什么需要函数式接口?
-> 如果接口中只声明有一个抽象方法,则此接口就称为函数式接口。
-> 因为只有给函数式接口提供实现类的对象时,我们才可以使用lambda表达式。
-
api中函数式接口所在的包:jdk8中声明的函数式接口都在java.util.function包下。
-
4个基本的函数式接口
接口类型 接口 对应的抽象方法 消费型接口 Consumer< T > void accept(T t) 供给型接口 Supplier< T > T get() 函数型接口 Function<T, R> R apply(T t) 判断型接口 Predicate< T > boolean test(T t)
-
-
Lambda表达式的语法规则总结
- ->的左边:lambda形参列表,参数的类型都可以省略。如果形参只有一个,则一对 () 也可以省略。
- ->的右边:lambda体,对应着重写的方法的方法体。
如果方法体中只有一行执行语句,则一对 {} 可以省略。
如果有return关键字,则必须一并省略。
-
@Test public void test2(){ Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); } }; System.out.println(com1.compare(12, 32)); System.out.println("*******************"); // lambda表达式 Comparator<Integer> com2 = (o1, o2) ->{ return Integer.compare(o1, o2); }; System.out.println(com2.compare(23, 11)); }
2. 方法引用
-
方法引用举例:
Integer :: compare;
-
方法引用的理解:
-
方法引用,可以看做是基于lambda表达式的进一步刻画。(可以使用方法引用的前提是可以使用lambda表达式)
-
当需要提供一个函数式接口的实例时,我们可以使用lambda表达式提供此实例。
-
当满足一定的条件的情况下,我们还可以使用方法引用或构造器引用替换lambda表达式。
-
-
方法引用的本质:方法引用作为了函数式接口的实例
-
方法引用格式:
类(或对象) :: 方法名
-
具体使用情况说明:
-
对象 :: 实例方法
要求:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的形参列表和返回值类型都相同(或一致)。此时,可以
考虑使用方法b实现对方法a的替换、覆盖。此替换或覆盖即为方法引用
注意:此方法b是非静态的方法,需要对象调用
-
类 :: 静态方法
要求:函数式接口中的抽象方法a与其内部实现时调用的类的某个静态方法b的形参列表和返回值类型都相同(或一致)。此时,可 以考虑使用方法b实现对方法a的替换、覆盖。
注意:此方法b是静态的方法,需要类调用
-
类 : 实例方法
要求:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的返回值类型相同。同时,抽象方法a中有n个参数,方 法b中有n-1个参数,且抽象方法a的第1个参数作为方法b的调用者,且抽象方法a的后n-1个参数与方法b的n-1个参数的类 型相同(或一致)。则可以考虑使用方法b实现对方法a的替换、覆盖。
注意:此方法b是非静态的方法,需要对象调用(但是形式上,写出对象a所属的类)
-
package methodRef;
import org.junit.Test;
import java.util.Comparator;
public class MethodRefTest {
// 1. 对象 :: 实例方法
@Test
public void test1(){
// 1.
Consumer<String> con1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con1.accept("hello!");
// 2. lambda表达式
Consumer<String> con2 = s -> System.out.println(s);
con2.accept("hello!");
// 3. 方法引用
Consumer<String> con3 = System.out :: println;
con3.accept("hello!");
}
// 2. 类 :: 静态方法
@Test
public void test2(){
// 1.
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
System.out.println(com1.compare(12, 23));
// 2. lambda表达式
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
System.out.println(com2.compare(12, 23));
// 3. 方法引用
Comparator<Integer> com3 = Integer :: compare;
System.out.println(com3.compare(12, 23));
}
// 3. 类 :: 实例方法
@Test
public void test3(){
// 1.
Comparator<String> com1 = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
};
System.out.println(com1.compare("abc", "abd"));
// 2. lambda表达式
Comparator<String> com2 = (s1, s2) -> s1.compareTo(s2);
System.out.println(com2.compare("abc", "abd"));
// 3. 方法引用
Comparator<String> com3 = String :: compareTo;
System.out.println(com3.compare("abc", "abd"));
}
}
3. 构造器引用
-
格式:
类名 :: new
-
说明:调用了类名对应的类中的某一个确定的构造器
具体调用的是类中的哪一个构造器:取决于函数式接口的抽象方法的形参列表
-
数组引用格式:
数组名[] :: new
-
package methodRef; import org.junit.Test; public class ConsRefTest { //构造器方法引用 @Test public void test1(){ // 1. Supplier<Employee> sup1 = new Supplier<Employee>() { @Override public Employee get() { return new Employee(); } }; System.out.println(sup1.get()); // 2. 方法引用 Supplier<Employee> sup2 = Employee::new; System.out.println(sup2.get()); } //数组方法引用 @Test public void test2(){ // 1. Function<Integer, Employee[]> fun1 = new Function<Integer, Employee[]>() { @Override public Employee[] apply(Integer len) { return new Employee[len]; } }; System.out.println(fun1.apply(2).length); // 2. 方法引用 Function<Integer, Employee[]> fun2 = Employee[]::new; System.out.println(fun2.apply(2).length); } }
4. StreamAPI
4.1 说明
- Stream APl ( java.util.stream)把真正的函数式编程风格引入到Java中。可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用stream API来并行执行操作。
4.2 为什么要使用Stream API
- 实际开发中,项目中多数数据源都来自于MySQL、Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。
4.3 StreamAPI的使用说明
- Stream自己不会存储元素。
- stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。
- Stream一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。
4.4 Stream执行流程步骤
-
Stream的实例化步骤
package streamAPI; import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; /* Stream实例化 * */ public class StreamAPITest { //创建Stream方式一:通过集合 @Test public void test1(){ List<Employee> list = EmployeeData.getEmployee(); // default Stream<E> stream():返回一个顺序流 Stream<Employee> stream = list.stream(); // default Stream<E> parallelStream():返回一个并行流 Stream<Employee> stream1 = list.parallelStream(); } //创建Stream方式二:通过数组 @Test public void test2(){ Integer[] arr = new Integer[]{1,2,3,4,5}; //调用Arrays类的static <T> Stream<T> stream(T[] array):返回一个流 Stream<Integer> stream = Arrays.stream(arr); } //创建Stream方式三:通过Stream的of() @Test public void test3(){ Stream<String> stream = Stream.of("AA", "BB", "CC", "DD", "EE"); } }
-
一系列的中间操作步骤
package streamAPI; import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.stream.Stream; /* Stream的中间操作 * */ public class StreamAPITest1 { // 1-筛选与切片 @Test public void test1(){ // filter(Predicate p)——接受Lambda,从流中排除某些元素 // 查询员工表中薪资大于5000的员工信息 List<Employee> list = EmployeeData.getEmployee(); Stream<Employee> stream = list.stream(); stream.filter(emp -> emp.getSalary() > 5000).forEach(System.out::println); System.out.println(); // limit(n)——截断流,使其元素不超过给定数量 list.stream().limit(3).forEach(System.out::println); System.out.println(); // skip()n——跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流 list.stream().skip(2).forEach(System.out::println); System.out.println(); // distinct()——筛选,通过流所生成元素的hashcode()和equals()去除重复元素 list.add(new Employee(1007,"小顾", 40,12200.5)); list.add(new Employee(1007,"小顾", 40,12200.5)); list.add(new Employee(1007,"小顾", 40,12200.5)); list.add(new Employee(1007,"小顾", 40,12200.5)); list.stream().distinct().forEach(System.out::println); } // 2-映射 @Test public void test2(){ // map(Function f)——接受一个函数作为参数,将元素转换为其他形式或提取信息 // 转换为大写 List<String> list = Arrays.asList("aa","bb","cc","dd"); list.stream().map(str->str.toUpperCase()).forEach(System.out::println); // 获取员工姓名长度大于3的员工 List<Employee> list1 = EmployeeData.getEmployee(); list1.stream().filter(emp->emp.getName().length()>3).forEach(System.out::println); // 获取员工姓名长度大于3的员工姓名 // 方式一: list1.stream().filter(emp->emp.getName().length()>3).map(emp->emp.getName()).forEach(System.out::println); // 方式二: list1.stream().filter(emp->emp.getName().length()>3).map(Employee::getName).forEach(System.out::println); } // 3-排序 @Test public void test3(){ // sorted()——自然排序 Integer[] arr = new Integer[]{325,0,2,58,79,658,258,10}; String[] arr1 = new String[]{"RR","GG","WW","JJ","BB"}; Arrays.stream(arr).sorted().forEach(System.out::println); System.out.println(Arrays.toString(arr));//Stream不会改变原对象,还是原来数组的顺序 // sorted(Comparator com)——定制排序 List<Employee> list = EmployeeData.getEmployee(); list.stream().sorted((e1, e2)->e1.age- e2.age).forEach(System.out::println); // 对字符串进行降序排列 Arrays.stream(arr1).sorted((s1, s2)->-s1.compareTo(s2)).forEach(System.out::println); } }
-
执行终止操作
package streamAPI; import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /* Stream的终止操作 * */ public class StreamAPITest2 { // 1-匹配与查找 @Test public void test1(){ // allMatch(Predicate p)——检查是否匹配所有元素 // 练习:是否所有员工的年龄都大于18 List<Employee> list = EmployeeData.getEmployee(); System.out.println(list.stream().allMatch(emp -> emp.age > 18)); // anyMatch(Predicate p)——检查是否至少匹配一个元素 // 练习:是否存在员工的年龄都大于18 System.out.println(list.stream().anyMatch(emp -> emp.age > 18)); // 练习:是否存在员工工资大于10000 System.out.println(list.stream().anyMatch(emp -> emp.salary >10000)); // findFirst()——返回第一个元素 System.out.println(list.stream().findFirst()); } @Test public void test2(){ // count——返回流中元素的总个数 List<Employee> list = EmployeeData.getEmployee(); System.out.println(list.stream().count()); // max(Comparator c)——返回流中最大值 // 练习:返回最高工资的员工 System.out.println(list.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))); // 练习:返回最高工资 System.out.println(list.stream().map(emp -> emp.salary).max((salary1, salary2) -> Double.compare(salary1, salary2)).get()); System.out.println(list.stream().map(emp -> emp.salary).max(Double::compare).get()); // min(Comparator c)——返回流中最小值 // 练习:返回最低工资 System.out.println(list.stream().map(emp -> emp.salary).min((salary1, salary2) -> Double.compare(salary1, salary2)).get()); // forEach(Consumner c)——内部迭代 list.stream().forEach(System.out::println); // 对于集合,jdk8新增遍历方法 list.forEach(System.out::println); } // 2-归约 @Test public void test3(){ // reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值,返回T // 练习:计算1-10的和 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); System.out.println(list.stream().reduce(0, (x1, x2) -> x1 + x2)); System.out.println(list.stream().reduce(0, (x1, x2) -> Integer.sum(x1,x2))); System.out.println(list.stream().reduce(0, Integer::sum)); // reduce(BinaryOperator)——可以将流中元素反复结合起来,得到一个值,返回Optional<T> // 计算公司中所有员工工资总和 List<Employee> list1 = EmployeeData.getEmployee(); System.out.println(list1.stream().map(emp -> emp.getSalary()).reduce(Double::sum)); } // 3-收集 @Test public void test4(){ // collect(Collector c)——将流转换为其他形式,接受一个Collector接口的实现,用于给Stream中元素做汇总的方法 // 练习:查找工资大于6000的员工,结果返回一个List或Set List<Employee> list = EmployeeData.getEmployee(); List<Employee> list1 = list.stream().filter(emp -> emp.getSalary() > 6000).collect(Collectors.toList()); list1.forEach(System.out::println); System.out.println(); // 练习:按照员工年龄进行排序,返回一个新的List中 List<Employee> list2 = list.stream().sorted((emp1,emp2)->emp1.getAge()- emp2.getAge()).collect(Collectors.toList()); list2.forEach(System.out::println); } }
5. 新语法结构
5.1 Java的REPL工具: jShell命令
-
说明:
- JDK9的新特性:Java终于拥有了像Python和scala之类语言的REPL工具(交互式编程环境,read - evaluate - print - loop) :jShell。
- 以交互式的方式对语句和表达式进行求值。即写即得、快速运行。
- 利用 jshell 在没有创建类的情况下,在命令行里直接声明变量,计算表达式,执行语句。
-
常用指令:
/help:获取有关使用jshell工具的信息 /help intro : jshell工具的简介 /list :列出当前session里所有有效的代码片段 /vars :查看当前session下所有创建过的变量 /methods :查看当前session下所有创建过的方法 /imports :列出导入的包 /history :键入的内容的历史记录 /edit :使用外部代码编辑器来编写Java代码 /exit :退出jshell工具
5.2 异常处理之try-catch资源关闭
-
说明
- 在try()中声明的资源,无论是否发生异常,无论是否处理异常,都会自动关闭资源对象,不用手动关闭了。
- 这些资源实现类必须实现AutoCloseable或Closeable接口,实现其中的close()方法。Closeable是AutoCloseable的子接口。Java7几乎把所有的“资源类”(包括文件IO的各种类、JDBC编程的Connection、Statement等接口)都进行了改写,改写后资源类都实现了AutoCloseable或Closeable接口,并实现了close()方法。
- 写到try()中的资源类的变量默认是final声明的,不能修改。
-
JDK9新特性
-
try的前面可以定义流对象,try后面的(中可以直接引用流对象的名称。在try代码执行完毕后,流对象也可以释放掉,也不用写finally了。
格式:
A a = new A(); B b = new B(); try(a;b){ 可能产生的异常代码 }catch(异常类名 变量名){ 异常处理逻辑 }
-
5.3 switch表达式
- JDK12中预览特性:
- Java 12将会对switch声明语句进行扩展,使用case L ->来替代以前的break;,省去了break 语句,避免了因少写break 而出错。
- 同时将多个case合并到一行,显得简洁、清晰,也更加优雅的表达逻辑分支。
- 为了保持兼容性,case条件语句中依然可以使用字符 : ,但是**同一个switch结构里不能混用 -> 和 : **,否则编译错误。
5.4 新的引用数据类型:record(记录)
-
JDK15中第二次预览特性
JDK16中转正特性
-
记录不适合的场景:
- 不能在record声明的类中定义实例字段
- 类不能声明为abstract
- 不能声明显式的父类等
-
record的设计目标是提供一种将数据建模为数据的好方法。
record不是JavaBeans的直接替代品,因为record的方法不符合JavaBeans的get标准。
JavaBeans通常是可变的,而记录是不可变的。尽管它们的用途有点像,但记录并不会以某种方式取代JavaBean。
6. API变化
6.1 Optional类
-
JDK8的新特性
-
空指针异常是导致Java应用程序失败的最常见原因。
-
optional< T >类(java.util.Optional)是一个容器类,它可以保存类型T的值
代表这个值存在。或者仅仅保存null,表示这个值不存在。如果值存在,则isPresent()方法会返回true,调用get()方法会返回该对象。
-
使用Optional避免空指针的问题:
// 1.实例化: // ofNullable(T value):用来创建一个Optional实例,value可能是空,也可能非空 Optional<String> optional = 0ptional.ofNullable(star); // orElse(T other):如果Optional实例内部的value属性不为null,则返回value。如果value为null,则返回other.