-
JDK9
-
接口私有方法
在JDK8之前,接口是抽象方法和常量值的定义的集合,并且只包含常量和方法的定义,而没有变量和方法的实现。
在JDK8中,接口引入了default关键字以添加默认实现,对接口进行了增强。
而在JDK9中,接口引入了private关键字,使得接口更加灵活。
public interface TestInterface {
default void test() {
System.out.println("this is test method");
privateMethod();
}
private void privateMethod() {
System.out.println("this is private method");
}
}
class PrivateTestInterface implements TestInterface {
public static void main(String[] args) {
PrivateTestInterface privateTest = new PrivateTestInterface();
privateTest.test();
}
}
其中,私有方法必须要提供方法体,并且此方法只能被接口中的其他私有方法或是默认实现调用。
-
集合类工厂方法
在JDK9之前,如果我们想创建一个集合,例如HashMap,只能先创建对象,再向其中填充对应的值。
而在JDK9之后,我们可以通过Map.of方法快速创建并初始化对象。
//JDK9之前
Map<Integer, Integer> map1 = new HashMap<>();
map1.put(1, 2);
map1.put(3, 4);
//JDK9之后
Map<Integer, Integer> map2 = Map.of(1, 2, 3, 4)
其他集合类,例如List、Set,也有对应的of方法。
查看List接口的of方法,发现JDK是通过将of方法重载了若干次以达到快速填充对象的效果,List、Map、Set通过这种方式均至多能够一次性填充10个元素。
不过通过这种方式创建的集合类都是不可变集合,无法对其中的元素进行更改。如果需要使用可变集合,还是只能老老实实手动创建。
-
stream功能增强
- stream.of增强
在JDK8中,如果使用stream.of传入了null,对其操作可能会造成NPE。
而JDK9中提供了Stream.ofNullable方法,可以对null对象进行过滤,返回空stream。
- stream.iterate增强
//JDK8只能像这样生成无限的流,并使用limit()进行限制
Stream.iterate(0, i -> i + 1).limit(20).forEach(System.out::println);
//JDK9可以直接指定 i < 20以终止流
Stream.iterate(0, i -> i < 20, i -> i + 1).forEach(System.out::println);
- stream数据截断功能
提供了takeWhile和dropWhile功能,可以对stream流进行截断处理,类似for循环加上break。
List<Integer> list = List.of(1, 2, 3, 4, 1, 2, 3, 4);
//元素满足小于4时通过,大于等于4时直接将流截断
list.stream().takeWhile(i -> i < 4).forEach(System.out::println);
//元素满足大于等于4时通过,小于等于4的部分直接截断
list.stream().dropWhile(i -> i < 4).forEach(System.out::println);
分别输出1、2、3和4、1、2、3、4。
-
try-with-resources功能增强
//JDK9之前
try (InputStream inputStream = Files.newInputStream(Paths.get("pom.xml"));) {
for (int i = 0; i < 100; i++)
System.out.print((char) inputStream.read());
}
//JDK9以后
InputStream inputStream = Files.newInputStream(Paths.get("pom.xml"));
try (inputStream) {
for (int i = 0; i < 100; i++)
System.out.print((char) inputStream.read());
}
-
CompletableFuture功能增强
在JDK9中,主要引入了对CompletableFuture支持超时和延迟的新方法,弥补了缺少设置异步超时时间的缺憾。
其中,orTimeout方法是指定超时时间,如果超时则抛出TimeoutException
completeOnTimeout方法是指定默认值和超时时间,如果超时则采用默认值,不抛出异常
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> timeout = CompletableFuture.supplyAsync(() -> calc(50))
.orTimeout(100, TimeUnit.MILLISECONDS)
.exceptionally(ex -> 0);
//输出0
System.out.println(timeout.get());
CompletableFuture<Integer> completeOnTimeout = CompletableFuture.supplyAsync(() -> calc(50))
.completeOnTimeout(123, 100, TimeUnit.MILLISECONDS);
//输出123
System.out.println(completeOnTimeout.get());
}
public static Integer calc(Integer para) {
try {
// 模拟一个长时间的执行
Thread.sleep(1000);
} catch (InterruptedException ignored) {}
return para * para;
}
-
String类型存储优化
在JDK9之前,String底层是由char数组实现的,每个char占用两个字节的内存空间。我们在日常使用中可能大部分情况下都在使用英文字母,较少使用一些中文或者其他复杂的字符,这种存储方式就会比较浪费空间了。
而在JDK9中,String类引入了一种称为"Compact Strings"的新实现方式,将字符串的表示方式从char数组改为byte数组,并使用一种编码方式将Unicode字符映射到一个或两个字节的表示方式。这种实现方式可以大大减少内存使用,尤其是对于包含大量ASCII字符的字符串。
JDK9中,引入了一个coder标识,用来区分是普通的拉丁字母还是UTF16字符,如果字符串中都是能用LATIN1就能表示的就是0,否则就是UTF-16。
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
这样的优化使得大部分场景中,String占用的内存可以少50%,大大节省内存空间。
-
JDK10~JDK11
-
局部变量类型推断
这个功能来自于JDK10的特性JEP 286: Local-Variable Type Inference,它允许在没有类型信息的情况下声明局部变量,并且只使用var关键字。
我们可以做到如下事项:
//String
var str = "abc";
//List<? extends Serializable>
var list = List.of(1, 2.0, "3");
//List<CompletableFuture<Integer>>
var list2 = Stream.of(1, 2, 3).map(i -> CompletableFuture.completedFuture(0)).toList();
//在JDK11中引入,对lambda表达式的增强
Consumer<String> consumer = (var str) -> {};
var节省了大量的代码长度,在多数情况下使得代码便于编写,但可能会提高阅读成本。
但需要注意的是,var 没有改变Java的本质,var只是一种简便的写法,在定义局部变量时,任意什么类型都可以用var定义变量的类型会根据所赋的值来判断。
-
String类方法增强
JDK11中提供了String类的一些新方法,在日常开发中能够更加便利。
不过一般还是用StringUtils就可以解决了。
var empty = "";
var blank = " ";
System.out.println(empty.isEmpty());
System.out.println(blank.isEmpty());
System.out.println(blank.isBlank()); //isBlank方法用于判断是否字符串为空或者是仅包含空格
var str = "AB\nC\nD";
//根据字符串中的\n换行符进行切割,分为多个字符串,并转换为Stream进行操作
str.lines().forEach(System.out::println);
//比如现在我们有一个ABCD,但是现在我们想要一个ABCDABCD这样的基于原本字符串的重复字符串
System.out.println(str.repeat(2)); //一个repeat就搞定了
str = " A B C D ";
System.out.println(str.trim()); //去除字符串前后的半角空白字符
System.out.println(str.strip()); //去除字符串前后的全角和半角空白字符
System.out.println(str.stripLeading()); //去除首部空白字符
System.out.println(str.stripTrailing()); //去除尾部空白字符
-
JDK12~16
-
switch语法增强
在Java 12引入全新的switch语法,让我们使用switch语句更加的灵活,比如我们想要编写一个根据成绩得到等级的方法:
public String grade(int score) {
score /= 10;
String res = null;
switch (score) {
case 10:
case 9:
res = "优秀";
break;
case 8:
case 7:
res = "良好";
break;
case 6:
res = "及格";
break;
default:
res = "不及格";
break;
}
return res;
}
public String gradeNew(int score) {
score /= 10;
var res = switch (score) {
case 10, 9 -> "优秀";
case 8, 7 -> "良好";
case 6 -> "及格";
//也可以引入代码块,并用yield返回switch的值
default -> {
System.out.println("do something");
yield "不及格";
}
};
return res;
}
-
字符串文本增强
在JDK15之前,我们编写字符串文本需要在每一行都添加双引号,并用+号拼接字符串,如下所示:
String html =
"<html>\n" +
"<body>\n"+
" <h1>Java 15 新特性:文本块 | 程序猿DD</h1>\n"+
" <p>didispace.com</p>\n"+
"</body>\n"+
"</html>\n";
但是JDK15中引入了和JavaScript及Python相同的三引号文本块,上面这段文本可以改写成这样:
String html = """
<html>
<body>
<h1>Java 15 新特性:文本块 | 程序猿DD</h1>
<p>didispace.com</p>
</body>
</html>
""";
-
instanceof增强
在JDK16之前,我们使用instanceof去判断类的类型时,如果想将类进行类型转换,需要在方法内进行强转,如下:
而在JDK16中,强化了instanceof的功能,在使用instanceof判断类型成立后,会自动强制转换类型为指定类型,简化了我们手动转换的步骤,如下所示:
-
空指针异常提示增强
空指针异常是日常开发中必不可少的一环,但是平时要排查空指针异常时,我们并不能简单通过异常提示来得知究竟是什么导致了空指针异常,我们必须得翻代码才能揣测是什么为null导致的异常。
而在JDK14中,对空指针异常的提示进行了增强,我们可以直接得知空指针的原因,使错误更加直观,可惜对于stream、lambda等操作还是无法给出具体的提示。
-
JDK17
-
switch模式匹配
如果使用instanceof判断取出的值的不同类型去做不同操作,可能会带来大量的if-else,如下:
if (data.get("key") instanceof String s) {
log.info(s);
} else if (data.get("key") instanceof Double s) {
log.info(s);
} else if (data.get("key") instanceof Integer s) {
log.info(s);
}
这个时候,我们可以使用JDK17中引入的switch模式匹配功能,简化if-else操作,提升可读性:
switch (data.get("key1")) {
case String s -> log.info(s);
case Double d -> log.info(d.toString());
case Integer i -> log.info(i.toString());
default -> log.info("");
}
switch模式匹配功能的特点是:
- case条件中直接涵盖了类型的判断和类型的转换,这个功能类似于JDK16中的instanceof增强功能
- 每个case的处理逻辑用lambda语法来实现,可以免去break语句(JDK14引入)
-
密封类
在面向对象语言中,我们可以通过继承(extend)来实现类的能力复用、扩展与增强。但有的时候,有些能力我们不希望被继承了去做一些不可预知的扩展。所以,我们需要对继承关系有一些限制的控制手段。而密封类的作用就是限制类的继承。
对于继承能力的控制,Java很早就已经有一些了,主要是这两种方式:
final修饰类,这样类就无法被继承了package-private类(非public类),可以控制只能被同一个包下的类继承
但很显然,这两种限制方式的粒度都非常粗,如果有更精细化的限制需求的话,是很难实现的
为了进一步增强限制能力,Java 17中的密封类增加了几个重要关键词:
sealed:修饰类/接口,用来描述这个类/接口为密封类/接口non-sealed:修饰类/接口,用来描述这个类/接口为非密封类/接口permits:用在extends和implements之后,指定可以继承或实现的类
下面我们通过一个例子来理解这几个关键词的用法。
// 英雄基类 public class Hero { } // 坦克英雄的抽象 public class TankHero extends Hero { } // 输出英雄的抽象 public class AttackHero extends Hero { } // 辅助英雄的抽象 public class SupportHero extends Hero { } // 坦克英雄 public class Alistar extends TankHero { } // 输出英雄 public class Ezreal extends AttackHero { } // 辅助英雄 public class Soraka extends SupportHero { }
整体结构有三层,具体如下图所示:
- 第一层:Hero是所有英雄的基类,定义英雄的基础属性
- 第二层:按英雄的分类的三个不同抽象,定义同类英雄的公共属性
- 第三层:具体英雄的定义
这个时候,为了避免开发人员在创建新英雄的时候,搞乱这样的三层结构。就可以通过引入密封类的特性来做限制。
假设我们希望第一、第二层是稳定的,对于第二层英雄种类的抽象不允许再增加,此时我们就可以这样写:
public sealed class Hero permits TankHero, AttackHero, SupportHero {}
通过sealed关键词和permitspermits关键来定义Hero是一个需要密封的类,并且它的子类只允许为TankHero, AttackHero, SupportHero这三个。
完成这个改造之后,Hero的三个子类也需要制定对应的密封性,即sealed、non-sealed和permits。
根据上面的假设需求,第一、第二层稳定,允许第三层具体英雄角色可以后期不断增加新英雄,所以三类抽象英雄的定义可以这样编写:
public non-sealed class TankHero extends Hero {}
而对于第三层的英雄角色,已经是最后的具体实现,则可以使用final定义来阻断后续的继承关系,比如这样:
public final class Ezreal extends AttackHero {}
通过这样的设置,这三层英雄的结构中第一第二层就得到了比较好的保护。