目录
1、JDK8
1.1、Lambda表达式
允许把函数作为一个方法的参数,可替换匿名内部类。
Lambda表达式语法
// 无参数无返回值:
() -> System.out.println("Hello World")
// 有一个参数无返回值:
(x) -> System.out.println(x)
// 有一个参数无返回值,可省略参数小括号:
x -> System.out.println(x)
// 有多个参数,有返回值,有多条Lambda语句:
(x,y) -> {System.out.println("xxx"); return "sdf";}
// 有多个参数,有返回值,只有一条Lambda语句:
(x, y) -> xxx
1.2、Stream类
对一个集合中的一系列元素进行聚合操作。
Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。
Stream不会存储元素;
Stream不会改变源对象,会返回一个持有结果的Stream;
Stream操作是延时执行的,会等到需要结果的时候才会去真正执行。
中间操作符
终止操作符
示例
List<String> list = Arrays.asList(new String[]{"15","32","25","64","32"});
list.stream().distinct().forEach(item -> System.out.println(item));
1.3、Optional类
主要是为了解决空指针异常NullPointException问题
构造方法
Optional.of(T)
该方式的入参不能为null,否则会有NPE,在确定入参不为空时使用该方式。
Optional.ofNullable(T)
该方式的入参可以为null,当入参不确定为非null时使用。
Optional.empty()
这种方式是返回一个空Optional,等效Optional.ofNullable(null)
常用方法
ifPresent() <==> obj != null
当Optional实例的值非空时返回true,否则返回false;
orElseGet()
当Optional包含非空值时返回该值,否则通过接收的function生成一个默认的;
map()
转换当前Optional的值,并返回一个新的Optional实例;
orElse()
与orElseGet方法相似,不同的是orElse()直接返回传入的默认值。
orElseThrow()
value值为null时,直接抛出一个异常
示例
1.4、接口默认方法
接口允许有实现方法,只需要在方法名前新增default关键字即可。
作用
为接口添加新方法,同时保证不影响已有的实现。
解决default方法冲突的三步骤
- 方法签名相同时,才表示出现了冲突。
- 类中的方法优先级最高。类或者父类中的方法实现优先级大于任何接口的默认方法
- 子接口的默认方法优先级更高。
- 若最终还是无法判断,那么实现类必须通过显示复写的方式复写默认方法,然后再自己通过xxx.super.xxx()的方式来指定具体使用哪个接口的实现
示例(default方法冲突)
多重继承中,如果出现了同名的默认方法
示例1:若继承的两个接口相互独立且存在相同的默认方法,则需要重写default方法,并制定继承哪一个接口的default方法。
示例2:若继承的两个接口存在相同的默认方法且为继承关系,则调用的是子类接口的default方法。
1.5、Date Time API
旧版时间API
- 设计差:两个Date类,java.util.Date包含日期和时间,java.sql.Date仅包含日期,设计差;
- 非线程安全:日期可变,用于时间转换的SimpleDateFormat也不是线程安全的。
新版时间API
主要有LocalDate、LocalTime和LocalDateTime三个日期类,实例为不可变对象,线程安全。JDK8新增的日期及时间位于java.time包下。
- LocalDate:表示日期,包含:年月日。格式为:2020-01-13
- LocalTime:表示时间,包含:时分秒。格式为:16:39:09.307
- LocalDateTime:表示日期时间,包含:年月日 时分秒。格式为:2020-01-13T16:40:59.138
- DateTimeFormatter:日期时间格式化类
- Instant:时间戳类
- Duration:用于计算 2 个时间(LocalTime,时分秒)之间的差距
- Period:用于计算 2 个日期(LocalDate,年月日)之间的差距
- ZonedDateTime:包含时区的时间
部分API举例介绍
// 获取当前日期
LocalDate now = LocalDate.now();
// 指定日期 LocalDate.of(year,month,day)
LocalDate date = LocalDate.of(2008, 8, 8);
// 获取当前时间
LocalTime now = LocalTime.now();
// 指定日期 LocalTime.of(hour,minute,second)
LocalTime date = LocalTime.of(13, 26, 39);
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
// 指定日期时间 LocalDateTime.of(year,month,day,hour,minute,second)
LocalDateTime date = LocalDateTime.of(2018, 7, 23, 18, 59, 31);
// 修改年[修改时间]
System.out.println("修改年后:" + now.withYear(9102));
// 增加年(减使用 minusYear()方法)
System.out.println("+2年后:" + now.plusYears(2));
1.6、Base64
Base64只是一种编解码算法,而非加密算法,一般对重要信息做加密不使用Base64。
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
/**
* jdk8提供的Base64编解码效率远大于sun.misc和 Apache Commons Codec的效率
*/
public class Base64Test {
public static void main(String[] args) {
String str = "这是一次Base64编解码测试";
Encoder encoder = Base64.getEncoder(); // 编码对象
Decoder decoder = Base64.getDecoder(); // 解码对象
try {
// 1.编码
String encoderStr = encoder.encodeToString(str.getBytes("UTF-8"));
System.out.println(encoderStr);//6L+Z5piv5LiA5qyhQmFzZTY057yW6Kej56CB5rWL6K+V
// 2.解码
byte[] decode = decoder.decode(encoderStr);
System.out.println(new String(decode, "UTF-8")); // 这是一次Base64编解码测试
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
1.7、方法引用
常与Lambda表达式一起使用。
如果Lambda表达式所要实现的功能,可以用其他方法替代,那么则可以使用方法引用。
引用方式
1、instanceName::methodName 对象::方法名
2、ClassName::staticMethodName 类名::静态方法
3、ClassName::MethodName 类名::普通方法
4、ClassName::new 类名::new 调用的构造器
5、TypeName[]::new String[]::new 调用数组构造器
举例说明
List<LocalDate> dates = new ArrayList<>();
dates.add(LocalDate.now());
dates.add(LocalDate.of(2019, 11, 12));
dates.add(LocalDate.of(2020, 5, 6));
// 获取年份集合
List<Integer> years = dates.stream().map(LocalDate::getYear).collect(Collectors.toList());
System.out.println(years);
1.8、并行(parallel)数组
- 对于Stream流,增加parallelStream并行流;
- 对于普通数组,增加Arrays.parallelSort()并行排序。
// Stream串行
long count = IntStream.range(0, 100000).filter(this::isPrime).count();
System.out.println(count);
// Stream并行
long count1 = IntStream.range(0, 100000).parallel().filter(this::isPrime).count();
System.out.println(count1);
// 获取一个并行流
List<JSONObject> objects = Lists.newArrayList();
Stream<JSONObject> jsonObjectStream = objects.parallelStream();
// 并行数组
long[] arrayOfLong = new long [20000];
。。。省略数组的初始化。。。
Arrays.parallelSort( arrayOfLong );
1.9、对并发的新支持
LongAdder、CompletableFuture、StampedLock
2、JDK9
2.1、接口的private方法
JDK8中的接口新增了默认方法,允许在接口中定义方法体。从JDK9开始,可以在接口中添加private的私有方法和私有静态方法,不将私有方法暴露给接口的实现类调用。
- 接口中private方法不能是abstract抽象方法。因为abstract抽象方法是公开的用于给接口实现类实现的方法,所以不能是private。
- 接口中私有方法只能在接口内部的方法里面被调用。
- 接口中私有静态方法可以在其他静态和非静态接口方法中使用。
- 接口中私有非静态方法不能在私有静态方法内部使用。
3、JDK11
3.1、局部变量类型推断(var)
在一个局部定义(方法内部)中,编译器可以自动发现类型。在编译时,编译器会检查赋值语句右侧代码,从而推断出具体类型。它查看声明的右侧,如果这是一个初始化语句,它会用那个类型取代var。
// 简化类型,变量名尽量见名知意
// DocumentationTool dtl = new DocumentationTool();
var dockmentationTool = new DocumentationTool();
// 使用数据类型标志来帮助var去推断出预期的基本数据类型(int, long, float, double)
var intNumber = 20; // 推断为int
var longNumber = 20L; // 推断为long
var floatNumber = 20F; // 推断为float, 20.0
var doubleNumber = 20D; // 推断为double, 20.0
4、JDK14
4.1、switch中的箭头语法
在case语句中,使用箭头替换老版的冒号,可以省略break语句。
// switch case旧方式
public void old(int i) {
switch(i) {
case 1: System.out.println("one");
break;
case 2: System.out.println("two");
break;
case 3: System.out.println("three");
break;
default: System.out.println("default");
}
}
// switch case新方式
public void new(int i) {
switch(i) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("three");
default -> System.out.println("default");
}
}
4.2、将switch作为表达式
将switch作为一个表达式,可以通过switch获取一个值。
在使用冒号语法时,可以使用yield关键字从switch中返回结果,yield和break不能同时使用。
public void test1(int i) {
String result = switch(i) {
case 1: yield "one";
case 2: yield "two";
case 3: yield "three";
default: yield "default";
}
System.out.println(result);
}
public void test2(int i) {
String result = switch(i) {
case 1 -> "one";
case 2 -> "two";
case 3 -> "three";
default -> "default";
}
System.out.println(result);
}
5、JDK16
5.1、instanceof 智能转型
public void dumb(Object x) {
if (x instanceof String s && s.length() > 0) {
System.out.format("%d %s%n", s.length(), s.toUpperCase())
}
}
如上示例所示,如果x是String类型,就会自动用String类型创建一个新的变量s(s被称为模式变量),s在整个作用域中都可用。
但是,在某些极端情况,智能转型会产生一些奇怪的作用域行为:
public void f1(Object o) {
if (!(o instanceof String s)) {
System.out.println("Not a String");
throw new RuntimeException();
}
// 此处s仍在作用域中!
System.out.println(s.toUpperCase()); //[1]
}
public void f2(Object o) {
if (!(o instanceof String s)) {
System.out.println("Not a String");
}
// 编译报错:无法找到s
System.out.println(s.toUpperCase()); //[1]
}
6、JDK17
6.1、密封类和密封接口
密封类/接口规定了只有指定的类/接口可以扩展和实现他们。换句话说,密封类/接口可以限制自己能派生出哪些类。
举例说明:
定义密封类(sealed ... permits ...)
sealed class Shape permits Circle, Hexagon, Rectangle {...}
- 通过使用sealed关键字,定义一个密封类Shape;
- permits用于限制只有指定的子类能继承密封类Shape。
- 子类继承
sealed类的子类只能通过下面的某个修饰符来定义:
- final:不允许有进一步的子类;
- sealed:允许有一组密封子类;
- non-sealed:允许有未知子类。
6.2、switch中的case null
在switch中引入原本非法的case null。
// 老版本switch case
public void old(String s) {
if (s == null) {
System.out.println("null");
return;
}
switch(s) {
case "XX" -> System.out.println("XX");
default -> System.out.println("default");
}
}
// 新版本switch case
public void new(String s) {
switch(s) {
case null -> System.out.println("null");
case "XX" -> System.out.println("XX");
default -> System.out.println("default");
}
}