一、jdk版本的选择
历经曲折的Java 9在4次跳票后,终于在2017年9月21日发布。从Java 9这个版本开始,Java 的计划发布周期是6个月,这意味着Java的更新从传统的以特性驱动的发布周期,转变为以时间驱动的发布周期,并逐步地将Oracle JDK原商业特性进行开源。针对企业客户的需求,Oracle将以3年为周期发布长期支持版本(Long Term Support,LTS),最近的LTS版本就是Java 11和Java17了,其他都是过渡版本
在Java 17正式发布之前,Java开发框架Spring率先在官博宣布,Spring Framework 6和Spring Boot 3计划在2022年第四季度实现总体可用性的高端基线:
1、Java 17+(来自 Spring Framework 5.3.x 线中的 Java 8-17)
2、Jakarta EE 9+(来自Spring框架5.3.x 线中的 Java EE 7-8)
3.Spring 官方说明:
https://spring.io/blog/2022/01/20/spring-boot-3-0-0-m1-is-now-available
4.JDK17针对于GC方面作出了优化,以及做了性能的提高
a.在吞吐量方面,Parallel 中 JDK 8 和 JDK 11 差距不大,JDK 17 相较 JDK 8 提升 15% 左右;G1 中 JDK 17 比 JDK 8 提升 18%;ZGC 在 JDK 11引入,JDK 17 对比JDK 11 提升超过 20%
b. 在 GC 延迟方面,JDK 17 的提升更为明显。在 Parallel 中 JDK 17 对比 JDK 8 和JDK 11 提升 40%;在 G1 中,JDK 11 对比 JDK 8 提升 26%,JDK 17 对比 JDK 8 提升接近 60%!ZGC 中 JDK 17 对比 JDK 11 提升超过 40%
由于JDK对性能提升方面都是自动的,所以我们可以直接学习JDK新特性中的语法和API。我们要知道的是下面的语法不都是从JDK17才开始有的,
但是JDK17都支持这些语法和API。
1.查看jdk版本: java -version
2.问题:
如果我们环境变量配置的是jdk8的但是执行java -version出现的jdk17版本
查看path路径上有没有javapath,如果有,干掉
二、接口的私有方法
Java8版本接口增加了两类成员:
公共的默认方法
公共的静态方法
Java9版本接口又新增了一类成员:
私有的方法
为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的
私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。
public interface USB {
private void open(){
System.out.println("静态成员");
}
private static void close(){
System.out.println("私有静态成员");
}
//私有方法能在默认方法中调用
public default void methodDef(){
open();
close();
}
//私有方法能在静态方法中调用
public static void methodSta(){
close();
}
}
public class UsbImpl implements USB{
}
public class Test01 {
public static void main(String[] args) {
UsbImpl usb = new UsbImpl();
usb.methodDef();
USB.methodSta();
}
}
三、钻石操作符与匿名内部类结合
自Java 9之后我们将能够与匿名实现类共同使用钻石操作符,即匿名实现类也支持类型自动推断
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test01 {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",10));
list.add(new Person("李四",8));
list.add(new Person("王五",20));
Collections.sort(list, new Comparator<>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
System.out.println(list);
}
}
Java 8的语言等级编译会报错:“'<>' cannot be used with anonymous classes。”Java 9及以上版本才能编译和运行正常。
四、try..catch升级
之前我们讲过JDK 1.7引入了trywith-resources的新特性,可以实现资源的自动关闭,此时要求:
该资源必须实现java.io.Closeable接口
在try子句中声明并初始化资源对象
该资源对象必须是final的
try(IO流对象1声明和初始化;IO流对象2声明和初始化){
可能出现异常的代码
}catch(异常类型 对象名){
异常处理方案
}
JDK1.9又对trywith-resources的语法升级了
该资源必须实现java.io.Closeable接口
在try子句中声明并初始化资源对象,也可以直接使用已初始化的资源对象
该资源对象必须是final的
IO流对象1声明和初始化;
IO流对象2声明和初始化;
try(IO流对象1;IO流对象2){
可能出现异常的代码
}catch(异常类型 对象名){
异常处理方案
}
public class Test01 {
public static void main(String[] args)throws Exception {
//method01();
method02();
}
/**
* jdk8之后
* 减轻了try中的代码压力
* 而且依然能自动刷新和关流
* @throws Exception
*/
private static void method02()throws Exception {
FileWriter fw = new FileWriter("test/io.txt");
try (fw) {
fw.write("你好1111");
} catch (Exception e) {
e.printStackTrace();
}
}
//jdk8之前
private static void method01() {
try (FileWriter fw = new FileWriter("test/io.txt");) {
fw.write("你好");
} catch (Exception e) {
e.printStackTrace();
}
}
}
五、局部变量类型自动推断
jdk10之前,我们定义局部变量都必须要明确数据的数据类型,但是到了JDK10,出现了一个最为重要的特性,就是
局部变量类型推断
,顾名思义,就是定义局部变量时,不用先确定具体的数据类型了,可以直接根据具体数据推断出所属的数据类型。var 变量名 =值;
public class Test01 {
public static void main(String[] args) {
var i = 10;
System.out.println("i = " + i);
var j = "hello";
System.out.println("j = " + j);
var arr = new int[]{1,2,3,4,5};
for (var element : arr) {
System.out.println(element);
}
}
}
六、switch表达式
switch表达式在Java 12中作为预览语言出现,在Java 13中进行了二次预览,得到了再次改进,最终在Java 14中确定下来。另外,在Java17中预览了switch模式匹配。
传统的switch语句在使用中有以下几个问题。
(1)匹配是自上而下的,如果忘记写break,那么后面的case语句不论匹配与否都会执行。
(2)所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复。
(3)不能在一个case语句中写多个执行结果一致的条件,即每个case语句后只能写一个常量值。
(4)整个switch语句不能作为表达式返回值。
1、Java12的switch表达式
Java 12对switch语句进行了扩展,将其作为增强版的switch语句或称为switch表达式,可以写出更加简化的代码。
允许将多个case语句合并到一行,可以简洁、清晰也更加优雅地表达逻辑分支。
可以使用-> 代替 :
->写法默认省略break语句,避免了因少写break语句而出错的问题。
->写法在标签右侧的代码段可以是表达式、代码块或 throw语句。
->写法在标签右侧的代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个switch结构。
同一个switch结构中不能混用“→”和“:”,否则会有编译错误。使用字符“:”,这时fall-through规则依然有效,即不能省略原有的break语句。":"的写法表示继续使用传统switch语法。
案例需求:
请使用switch-case结构实现根据月份输出对应季节名称。例如,3~5月是春季,6~8月是夏季,9~11月是秋季,12~2月是冬季。
Java12之前写法:
@Test
public void test1() {
int month = 3;
switch (month) {
case 3:
case 4:
case 5:
System.out.println("春季");
break;
case 6:
case 7:
case 8:
System.out.println("夏季");
break;
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
case 12:
case 1:
case 2:
System.out.println("冬季");
break;
default:
System.out.println("月份输入有误!");
}
}
【Java12之后的写法】
public class Test01 {
public static void main(String[] args) {
//method01();
method02();
}
/**
* 如果用 -> 可以省略break,此时不会出现穿透性
*/
private static void method02() {
int month = 5;
switch(month){
case 12,1,2->
System.out.println("冬季");
case 3,4,5->
System.out.println("春季");
case 6,7,8->
System.out.println("夏季");
case 9,10,11->
System.out.println("秋季");
default->
System.out.println("有毛病");
}
}
/**
* 如果用:
* break不写依然会出现穿透性
*/
private static void method01() {
int month = 5;
switch(month){
case 12,1,2:
System.out.println("冬季");
break;
case 3,4,5:
System.out.println("春季");
break;
case 6,7,8:
System.out.println("夏季");
break;
case 9,10,11:
System.out.println("秋季");
break;
default:
System.out.println("有毛病");
break;
}
}
}
2、Java13的switch表达式
Java 13提出了第二个switch表达式预览,引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield语句,switch语句(不返回值)应该使用break语句。
案例需求:判断季节。
public class Test02 {
public static void main(String[] args) {
//method01();
method02();
}
/**
* jdk13之后:
*/
private static void method02() {
int month = 12;
var season = switch (month) {
case 12, 1, 2 -> {
yield "冬季";
}
case 3, 4, 5 -> {
yield "春季";
}
case 6, 7, 8 -> {
yield "夏季";
}
case 9, 10, 11 -> {
yield "秋季";
}
default ->{
yield "有毛病";
}
};
System.out.println("season = " + season);
}
/**
* jdk13之前想要拿到switch的结果,需要定义一个变量
* 然后为其赋值
*/
private static void method01() {
int month = 5;
String season = "";
switch (month) {
case 12, 1, 2:
season = "冬季";
break;
case 3, 4, 5:
season = "春季";
break;
case 6, 7, 8:
season = "夏季";
break;
case 9, 10, 11:
season = "秋季";
break;
default:
season = "有毛病";
break;
}
System.out.println("season = " + season);
}
}
七、文本块
预览的新特性文本块在Java 15中被最终确定下来,Java 15之后我们就可以放心使用该文本块了。
【Java13文本块】
JDK 12引入了Raw String Literals特性,但在其发布之前就放弃了这个特性。这个JEP与引入多行字符串文字(文本块)在意义上是类似的。Java 13中引入了文本块(预览特性),这个新特性跟Kotlin中的文本块是类似的。
现实问题
在Java中,通常需要使用String类型表达HTML,XML,SQL或JSON等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。
文本块就是指多行字符串,例如一段格式化后的XML、JSON等。而有了文本块以后,用户不需要转义,Java能自动搞定。因此,文本块将提高Java程序的可读性和可写性。
目标
简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写Java程序。
增强Java程序中字符串的可读性。
举例
会被自动转义,如有一段以下字符串:
<html>
<body>
<p>Hello, 尚硅谷</p>
</body>
</html>
将其复制到Java的字符串中,会展示成以下内容:
"<html>\n" +
" <body>\n" +
" <p>Hello, 尚硅谷</p>\n" +
" </body>\n" +
"</html>\n";
即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中,就可以使用以下语法了:
"""
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
使用“”“作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。
文本块是Java中的一种新形式,它可以用来表示任何字符串,并且提供更多的表现力和更少的复杂性。
(1)文本块由零个或多个字符组成,由开始和结束分隔符括起来。
- 开始分隔符由三个双引号字符表示,后面可以跟零个或多个空格,最终以行终止符结束。
- 文本块内容以开始分隔符的行终止符后的第一个字符开始。
- 结束分隔符也由三个双引号字符表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。
以下示例代码是错误格式的文本块:
String err1 = """""";//开始分隔符后没有行终止符,六个双引号最中间必须换行
String err2 = """ """;//开始分隔符后没有行终止符,六个双引号最中间必须换行
(2)允许开发人员使用“\n”“\f”和“\r”来进行字符串的垂直格式化,使用“\b”“\t”进行水平格式化。如以下示例代码就是合法的。
String html = """
<html>\n
<body>\n
<p>Hello, world</p>\n
</body>\n
</html>\n
""";
3)在文本块中自由使用双引号是合法的。
String story = """
Elly said,"Maybe I was a bird in another life."
Noah said,"If you're a bird , I'm a bird."
""";
八、 instanceof模式匹配
instanceof的模式匹配在JDK14、15中预览,在JDK16中转正。有了它就不需要编写先通过instanceof判断再强制转换的代码。
public abstract class Animal {
public abstract void eat();
}
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗啃骨头");
}
//特有方法
public void lookDoor(){
System.out.println("狗会看门");
}
}
从JDK14开始,我们不需要单独强转,直接省略强转的过程
public class Test01 {
public static void main(String[] args) {
Dog dog = new Dog();
method(dog);
}
public static void method(Animal animal){
if (animal instanceof Dog dog){
dog.eat();
dog.lookDoor();
}
}
}
九、 Record类
Record类在JDK14、15预览特性,在JDK16中转正。
record是一种全新的类型,它本质上是一个 final类,同时所有的属性都是 final修饰,它会自动编译出get、hashCode 、比较所有属性值的equals、toString 等方法,减少了代码编写量。使用 Record 可以更方便的创建一个常量类。
【注意】
Record只会有一个全参构造
重写的equals方法比较所有属性值
可以在Record声明的类中定义静态字段、静态方法或实例方法(非静态成员方法)。
不能在Record声明的类中定义实例字段(非静态成员变量);
类不能声明为abstract;
不能显式的声明父类,默认父类是java.lang.Record类
因为Record类是一个 final类,所以也没有子类等。
public /*abstract*/ record Person(String name)/* extends Record*/{
//int i;不能声明实例变量
static int i;//可以声明静态变量
//不能声明空参构造
/* public Person(){
}*/
public static void method(){//可以声明静态方法
System.out.println("method");
}
public void method01(){//可以声明实例方法(成员方法)
System.out.println("method01");
}
}
public class Test01 /*extends Person*/{
public static void main(String[] args) {
Person p = new Person("张三");
System.out.println(p.name());
}
}
十、密封类
其实很多语言中都有
密封类
的概念,在Java语言中,也早就有密封类
的思想,就是final修饰的类,该类不允许被继承。而从JDK15开始,针对密封类
进行了升级。Java 15通过密封的类和接口来增强Java编程语言,这是新引入的预览功能并在Java 16中进行了二次预览,并在Java17最终确定下来。这个预览功能用于限制超类的使用,密封的类和接口限制其他可能继承或实现它们的其他类或接口。
【修饰符】 sealed class 密封类 【extends 父类】【implements 父接口】 permits 子类{
}
【修饰符】 sealed interface 接口 【extends 父接口们】 permits 实现类{
}
1) 密封类用 sealed 修饰符来描述,
2)使用 permits 关键字来指定可以继承或实现该类的类型有哪些
3)一个类继承密封类或实现密封接口,该类必须是sealed、non-sealed、final修饰的。
4)sealed修饰的类或接口必须有子类或实现类
public sealed class Animal permits Dog,Cat{
}
public non-sealed class Dog extends Animal{
}
public non-sealed class Cat extends Animal{
}
package com.atguigu.sealed;
import java.io.Serializable;
public class TestSealedInterface {
}
sealed interface Flyable /*extends Serializable*/ permits Bird {
}
non-sealed class Bird implements Flyable{
}
其他
陆续在新版本变化的API有很多,因篇幅问题不能一一列举。
Java 9带来了很多重大的变化,其中最重要的变化是Java平台模块系统的引入。众所周知,Java发展已经超过20年,Java和相关生态在不断丰富的同时也越来越暴露出一些问题。
(1)当某些应用很简单时。夸张地说,如若仅是为了打印一个“helloworld”,那么之前版本的JRE中有一个很重要的rt.jar(如Java 8的rt.jar中有60.5M),即运行一个“helloworld”,也需要一个数百兆的JRE环境,而这在很多小型设备中是很难实现的。
(2)当某些应用很复杂,有很多业务模块组成时。我们以package的形式设计和组织类文件,在起初阶段还不错,但是当我们有数百个package时,它们之间的依赖关系一眼是看不完的,当代码库越来越大,创建越复杂,盘根错节的“意大利面条式代码”的概率会呈指数级增长,这给后期维护带来了麻烦,而可维护性是软件设计和演进过程中最重要的问题。
(3)一个问题是classpath。所有的类和类库都堆积在classpath中。当这些JAR文件中的类在运行时有多个版本时,Java的ClassLoader就只能加载那个类的某个版本。在这种情形下,程序的运行就会有歧义,有歧义是一件非常坏的事情。这个问题总是频繁出现,它被称为“JAR Hell”。
(4)很难真正对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有明确的概念。每个公共类都可以被类路径下的其他类访问到,这样就会在无意中使用了并不想被公开访问的API。
模块就是为了修复这些问题存在的。模块化系统的优势有以下几点。
模块化的主要目的是减少内存的开销。
只需要必要模块,而非全部JDK模块,可简化各种类库和大型应用的开发和维护。
改进Java SE平台,使其可以适应不同大小的计算设备。
改进其安全性、可维护性。用模块管理各个package,其实就是在package外再裹一层,可以通过声明暴露某个package,不声明默认就是隐藏。因此,模块化系统使代码组织上更安全,因为它可以指定哪些部分可以暴露,哪些部分需要隐藏。
更可靠的配置,通过明确指定的类的依赖关系代替以前易错的路径(class-path)加载机制。模块必须声明对其他模块的显示依赖,并且模块系统会验证应用程序所有阶段的依赖关系:编译时、链接时和运行时。假设一个模块声明对另一个模块的依赖,并且第二个模块在启动时丢失,JVM检测到依赖关系丢失,在启动时就会失败。在Java 9之前,当使用缺少的类型时,这样的应用程序只会生成运行时错误而不是启动时错误。
Java 9是一个庞大的系统工程,从根本上带来了一个整体改变,包括JDK的安装目录,以适应模块化的设计。
大家可以发现在Java 9之后,API的组织结构也变了。
原来Java 8的API,包是顶级的封装,Java 8的API结构如图所示。