|
java 在9~18之间的版本中新增了很多特性,我们针对较为突出的便于理解的一些语法变化进行说明。 除了下面罗列出的新特性之外还有一些其他的内容,这些内容对于初学者来说有的不便于理解,有的是不重要,所以没有罗列出来。
jdk9的变化
jshell
有的时候我们只是想写一段简单的代码,例如HelloWorld,按照以前的方式,还需要自己创建java文件,创建class,编写main方法,但实际上里面的代码其实就是一个打印语句,此时还是比较麻烦的。在jdk9中新增了jshell工具,可以帮助我们快速的运行一些简单的代码。
从命令提示符里面输入jshell,进入到jshell之后输入:
System.out.println("HelloWorld");此时可以直接看到命令提示符中打印了HelloWorld。
我们还可以输入下面代码然后按回车:
int a = 10;再输入下面代码按回车:
int b = 20;再输入下面代码按回车:
System.out.println(a + b);此时可以看到控制台打印出了a+b的值了。
如果要退出jshell的话,输入/exit即可
接口私有方法
在jdk9中新增了接口私有方法,我们可以在接口中声明private修饰的方法了,这样的话,接口越来越像抽象类了
public interface MyInterface {
//定义私有方法
private void m1() {
System.out.println("123");
}
//default中调用
default void m2() {
m1();
}
}改进的try with resource
之前我们使用try with resource用来自动关闭资源文件,特别是在IO流部分使用的比较多。使用方式是将需要自动关闭的资源对象的创建放到try后面的小括号中,在jdk9中我们可以将这些资源对象的创建放到外面,然后将需要关闭的对象放到try后面的小括号中即可,示例:
/*
改进了try-with-resources语句,可以在try外进行初始化,在括号内引用,即可实现资源自动关闭
*/
public class TryWithResource {
public static void main(String[] args) throws FileNotFoundException {
//jdk8以前
try (FileInputStream fileInputStream = new FileInputStream("");
FileOutputStream fileOutputStream = new FileOutputStream("")) {
} catch (IOException e) {
e.printStackTrace();
}
//jdk9
FileInputStream fis = new FileInputStream("abc.txt");
FileOutputStream fos = new FileOutputStream("def.txt");
//多资源用分号隔开
try (fis; fos) {
} catch (IOException e) {
e.printStackTrace();
}
}
}不能使用下划线命名变量
下面语句在jdk9之前可以正常编译通过,但是在jdk9(含)之后编译报错,在后面的版本中会将下划线作为关键字来使用
String _ = "monkey1024";String字符串的变化
写程序的时候会经常用到String字符串,在以前的版本中String内部使用了char数组存储,对于使用英语的人来说,字符用一个字节就能存储,因此在jdk9中将String内部的char数组改成了byte数组,这样就节省了一半的内存占用。String中增加了一个判断,倘若字符超过1个字节的话,会把byte数组的长度改为两倍char数组的长度,用两个字节存放一个char。在获取String长度的时候,其源码中有向右移动1位的操作(即除以2),这样就解决了上面扩容2倍之后长度不正确的问题。
模块化
JDK9将JDK分成一组模块,可以在编译时或运行时进行组合。这样可以减少内存开销,只需必要的模块,并非全部模块,可以简化各种类库和大型应用的开发和维护。比如在使用java开发的时候通常用不到图形化界面的库,分模块之后,在java的base模块中没有这些图形化相关的内容,达到了一个瘦身的效果。
jdk10的变化
局部变量类型推断
在jdk10以前声明变量的时候,我们会像下面这样:
String oldName = "jack";
int oldAge = 10;
long oldMoney = 88888888L;
Object oldObj = new Object();上面我们声明的时候使用了4种不同类型的变量,在jdk10中前面的类型都可以使用var来代替,JVM会自动推断该变量是什么类型的,例如可以这样写:
var newName = "jack";
var newAge = 10;
var newMoney = 88888888L;
var newObj = new Object();注意:
当然这个var的使用是有限制的,仅适用于局部变量,增强for循环的索引,以及普通for循环的本地变量;它不能使用于方法形参,构造方法形参,方法返回类型等。
jdk11的变化
直接运行
在以前的版本中,我们在命令提示下,需要先编译,生成class文件之后再运行,例如:
javac HelloWorld.java
java HelloWorld在java 11中,我们可以这样直接运行
java HelloWorld.javaString新增方法
strip方法,可以去除首尾空格,与之前的trim的区别是还可以去除unicode编码的空白字符,例如:
char c = '\u2000';//Unicdoe空白字符
String str = c + "abc" + c;
System.out.println(str.strip());
System.out.println(str.trim());
System.out.println(str.stripLeading());//去除前面的空格
System.out.println(str.stripTrailing());//去除后面的空格isBlank方法,判断字符串长度是否为0,或者是否是空格,制表符等其他空白字符
String str = " ";
System.out.println(str.isBlank());repeat方法,字符串重复的次数
String str = "monkey";
System.out.println(str.repeat(4));lambda表达式中的变量类型推断
jdk11中允许在lambda表达式的参数中使用var修饰
函数式接口:
@FunctionalInterface
public interface MyInterface {
void m1(String a, int b);
}测试类:
//支持lambda表达式参数中使用var
MyInterface mi = (var a,var b)->{
System.out.println(a);
System.out.println(b);
};
mi.m1("monkey",1024);jdk12的变化
升级的switch语句
在jdk12之前的switch语句中,如果没有写break,则会出现case穿透现象,下面是对case穿透的一个应用,根据输入的月份打印相应的季节。
int month = 3;
switch (month) {
case 3:
case 4:
case 5:
System.out.println("spring");
break;
case 6:
case 7:
case 8:
System.out.println("summer");
break;
case 9:
case 10:
case 11:
System.out.println("autumn");
break;
case 12:
case 1:
case 2:
System.out.println("winter");
break;
default:
System.out.println("wrong");
break;
}在jdk12之后我们可以省略全部的break和部分case,这样使用
int month = 3;
switch (month) {
case 3,4,5 -> System.out.println("spring");
case 6,7,8 -> System.out.println("summer");
case 9,10,11 -> System.out.println("autumn");
case 12, 1,2 -> System.out.println("winter");
default -> System.out.println("wrong");
}这个是预览功能,如果需要编译和运行的话需要使用下面命令,预览功能在2个版本之后会成为正式版,即如果你使用的是jdk14以上的版本,正常的编译和运行即可。否则需要使用预览功能来编译和运行
编译:
javac --enable-preview -source 12 Test.java
运行:
java --enable-preview Testjdk13的变化
升级的switch语句
jdk13中对switch语句又进行了升级,可以switch的获取返回值
示例:
int month = 3;
String result = switch (month) {
case 3,4,5 -> "spring";
case 6,7,8 -> "summer";
case 9,10,11 -> "autumn";
case 12, 1,2 -> "winter";
default -> "wrong";
};
System.out.println(result);对于jdk15之后的版本可以直接编译和运行,否则需要使用下面命令执行该预览功能
编译:
javac --enable-preview -source 13 Test.java
运行:
java --enable-preview Test文本块的变化
在jdk13之前的版本中如果输入的字符串中有换行的话,需要添加换行符
String s = "Hello\nWorld\nLearn\nJava";
System.out.println(s);jdk13之后可以直接这样写:
String s = """
Hello
World
Learn
Java
""";
System.out.println(s);这样的字符串更加一目了然。
jdk14的变化
instanceof模式匹配
该特性可以减少强制类型转换的操作,简化了代码,代码示例:
public class TestInstanceof{
public static void main(String[] args){
//jdk14之前的写法
Object obj = new Integer(1);
if(obj instanceof Integer){
Integer i = (Integer)obj;
int result = i + 10;
System.out.println(i);
}
//jdk14新特性 不用再强制转换了
//这里相当于是将obj强制为Integer之后赋值给i了
if(obj instanceof Integer i){
int result = i + 10;
System.out.println(i);
}else{
//作用域问题,这里是无法访问i的
}
}
}这个是预览版的功能所以需要使用下面命令编译和运行
编译:
javac --enable-preview -source 14 TestInstanceof.java
运行:
java --enable-preview TestInstanceof友好的空指针(NullPointerException)提示
jdk14中添加了对于空指针异常友好的提示,便于开发者快速定位空指针的对象。示例代码:
class Machine{
public void start(){
System.out.println("启动");
}
}
class Engine{
public Machine machine;
}
class Car{
public Engine engine;
}
public class TestNull{
public static void main(String[] args){
//这里会报出空指针,但是哪个对象是null呢?
new Car().engine.machine.start();
}
}我们在运行上面代码的时候,错误信息就可以明确的指出那个对象为null了。此外,还可以使用下面参数来查看:
java -XX:+ShowCodeDetailsInExceptionMessages TestNull这样编译器会明确的告诉开发者哪个对象是null。
record类型
之前在编写javabean类的时候,需要编写成员变量,get方法,构造方法,toString方法,hashcode方法,equals方法。这些方法通常会通过开发工具来生成,在jdk14中新增了record类型,通过该类型可以省去这些代码的编写。
jdk14编写User
public record User(String name,Integer age){}通过反编译命令可以看到该字节码文件中的内容,User类是继承了Record类型:
javap -p -private user编写测试类:
public class TestUser{
public static void main(String[] args){
User u = new User("jack",15);
System.out.println(u);
System.out.println(u.name());
}
}这个是预览版的功能所以需要使用下面命令编译和运行
编译:
javac --enable-preview -source 14 TestUser.java
运行:
java --enable-preview TestUser记录类型有自动生成的成员,包括:
- 状态描述中的每个组件都有对应的private final字段。
- 状态描述中的每个组件都有对应的public访问方法。方法的名称与组件名称相同。
- 一个包含全部组件的公开构造器,用来初始化对应组件。
- 实现了equals()和hashCode()方法。equals()要求全部组件都必须相等。
- 实现了toString(),输出全部组件的信息。
jdk15的变化
Sealed Classes
密封类和接口,作用是限制一个类可以由哪些子类继承或者实现。
- 如果指定模块的话,sealed class和其子类必须在同一个模块下。如果没有指定模块,则需要在同一个包下。
- sealed class指定的子类必须直接继承该sealed class。
- sealed class的子类要用final修饰。
- sealed class的子类如果不想用final修饰的话,可以将子类声明为sealed class。
Animal类,在指定允许继承的子类时可以使用全限定名
public sealed class Animal
permits Cat, Dog{//多个子类之间用,隔开
public void eat(){}
}Cat类
public final class Cat extends Animal{
public void eat(){
System.out.println("123");
}
}Dog类
public sealed class Dog extends Animal
permits Husky {}Husky类
public final class Husky extends Dog{
}Test类
public class Test{
public static void main(String[] args){
Cat c = new Cat();
c.eat();
Dog d = new Dog();
}
}CharSequence新增的方法
该接口中新增了default方法isEmpty(),作用是判断CharSequence是否为空。
TreeMap新增方法
- putIfAbsent
- computeIfAbsent
- computeIfPresent
- compute
- merge
文本块
文本块由预览版变为正式版
无需配置环境变量
win系统中安装完成之后会自动将java.exe, javaw.exe, javac.exe, jshell.exe这几个命令添加到环境变量中。这部分可以打开环境变量看下。不过还是建议配置环境变量,因为这几个命令不够用
jdk16的变化
这里只介绍一些跟开发关联度较大的特性,除此之外JDK16还更新了许多其他新特性,感兴趣的同学可以去Oracle官网查看
包装类构造方法的警告
使用包装类的构造方法在编译的时候会出现警告,不建议再使用包装类的构造方法。下面代码在javac编译之后会出现警告。
Integer i = new Integer(8);不建议使用包装类作为锁对象,倘若使用包装类作为锁对象,在编译时会出现警告。
Integer i = 8;
synchronized(i){
}新增日时段
在DateTimeFormatter.ofPattern传入B可以获取现在时间对应的日时段,上午,下午等
System.out.println(DateTimeFormatter.ofPattern("B").format(LocalDateTime.now()));InvocationHandler新增方法
在该接口中添加了下面方法
public static Object invokeDefault(Object proxy, Method method, Object... args)该方法可以调用父接口中defalut方法,比如有下面接口
interface Girl{
default void eat(){
System.out.println("cucumber");
}
}实现类
public class Lucy implements Girl{
public void eat(){
System.out.println("banana");
}
}测试类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test{
public static void main(String[] args) {
Girl girl = new Lucy();
//不使用invokeDefault会调用重写的eat方法
Girl proxy1 = (Girl)Proxy.newProxyInstance(girl.getClass().getClassLoader(),girl.getClass().getInterfaces(),
(obj,method,params)->{
Object invoke = method.invoke(girl);
return invoke;
});
proxy1.eat();
//使用invokeDefault会调用父接口中的default方法
Girl proxy2 = (Girl)Proxy.newProxyInstance(Girl.class.getClassLoader(),new Class<?>[]{Girl.class},
(obj,method,params)->{
if (method.isDefault()) {
return InvocationHandler.invokeDefault(obj, method, params);
}
return null;
});
proxy2.eat();
}
}其他
在之前jdk版本中作为预览功能的Record类,模式匹配的instanceof,打包工具jpackage,已成为正式版。jdk16对GC,jvm运行时内存等内容有一些变化,例如:ZGC并发栈处理,弹性meta space。
jdk17的变化
java17是一个LTS(long term support)长期支持的版本,根据计划来看java17会支持到2029年(java8会支持到2030年,OMG),同时Oracle提议下一个LTS版本是java21,在2023年9月发布,这样讲LST版本的发布周期由之前的3年变为了2年。这里只介绍一些跟开发关联度较大的特性,除此之外JDK17还更新了一些其他新特性,感兴趣的同学可以从这里查看:https://www.oracle.com/news/announcement/oracle-releases-java-17-2021-09-14/

switch语法的变化(预览)
在之前版本中新增的instanceof模式匹配的特性在switch中也支持了,即我们可以在switch中减少强转的操作。比如下面的代码:
Rabbit和Bird均实现了Animal接口
interface Animal{}
class Rabbit implements Animal{
//特有的方法
public void run(){
System.out.println(&#34;run&#34;);
}
}
class Bird implements Animal{
//特有的方法
public void fly(){
System.out.println(&#34;fly&#34;);
}
}新特性可以减少Animal强转操作代码的编写:
public class Switch01{
public static void main(String[] args) {
Animal a = new Rabbit();
animalEat(a);
}
public static void animalEat(Animal a){
switch(a){
//如果a是Rabbit类型,则在强转之后赋值给r,然后再调用其特有的run方法
case Rabbit r -> r.run();
//如果a是Bird类型,则在强转之后赋值给b,然后调用其特有的fly方法
case Bird b -> b.fly();
//支持null的判断
case null -> System.out.println(&#34;null&#34;);
default -> System.out.println(&#34;no animal&#34;);
}
}
}该功能在java17中是预览的,编译和运行需要加上额外的参数:
javac --enable-preview -source 17 Switch01.java
java --enable-preview Switch01Sealed Classes
在jdk15中已经添加了Sealed Classes,只不过当时是作为预览版,经历了2个版本之后,在jdk17中Sealed Classes已经成为正式版了。Sealed Classes的作用是可以限制一个类或者接口可以由哪些子类继承或者实现。
伪随机数的变化
增加了伪随机数相关的类和接口来让开发者使用stream流进行操作
- RandomGenerator
- RandomGeneratorFactory
之前的java.util.Random和java.util.concurrent.ThreadLocalRandom都是RandomGenerator接口的实现类。
去除了AOT和JIT
AOT(Ahead-of-Time)是java9中新增的功能,可以先将应用中中的字节码编译成机器码。
Graal编译器作为使用java开发的JIT(just-in-time )即时编译器在java10中加入(注意这里的JIT不是之前java中的JIT,在JEP 317中有说明https://openjdk.java.net/jeps/317)。
以上两项功能由于使用量较少,且需要花费很多精力来维护,因此在java17中被移除了。当然你可以通过Graal VM来继续使用这些功能。
jdk18的变化
除了下面变化之外jdk18还有一些其他小的变化,感兴趣的童鞋可以去oracle官网查看。
从jdk18开始,默认使用UTF-8字符编码。我们可以通过如下参数修改其他字符编码:
-Dfile.encoding=UTF-8 简单的web服务器
可以通过jwebserver命令启动jdk18中提供的静态web服务器,可以利用该工具查看一些原型,做简单的测试。在命令提示符中输入jwebserver命令后会启动,然后在浏览器中输入:http://127.0.0.1:8000/ 即可看到当前命令提示符路径下的文件了。
将被移除的方法
在jdk18中标记了Object中的finalize方法,Thread中的stop方法将在未来被移除。
@snippet注解
以前在文档注释中编写代码时需要添加code标签,使用较为不便,通过@snippet注解可以更方便的将文档注释中的代码展示在api文档中。 |
|