前面我们在文章Java已经一路飙到21了,快来尝鲜JDK 9 10 11 的这些实用的API!了解了JDK9-11实用特性,
而从JDK11到JDK17,又是一波重大更新,今天在这里整理了一些日常开发会经常用到的新特性,看了这些,
相信你会有冲动去一个个地码上实验。
关注更新,提升自我,相信这里绝对有值得你收藏的干货 !
这个表达式可以理解为一个语法糖,现在的switch支持更精简的表达式了,再也不用:加break了,冗长又不好看.
@Testvoid testSwitch() {var random = new SecureRandom();for (var i = 0; i < 10; i++) { var age = random.nextInt(60); switch (age / 10) { case 0 -> { /* 语句块优雅支持*/ switch (age) { case 1, 2, 3 -> System.out.print("\t婴幼儿:=" + age); default -> System.out.print("\t儿童:=" + age); } } case 1 -> System.out.print("\t青年:=" + age); case 2 -> System.out.print("\t壮年:=" + age); /*多值Label,直接使用逗号分隔.*/ case 3, 4, 5 -> System.out.print("\t中年:=" + age); default -> System.out.print("\t老年:=" + age); }}/*中年:=32 中年:=36 青年:=18 中年:=55 婴幼儿:=1 壮年:=27 壮年:=26 青年:=15 青年:=17 中年:=37*/}
简单总结一下:
现在的swith还可以被当作一个值表达式使用啦. 简直越来越优秀了,有更多的理由去使用switch表达式了.
@Testvoid testSwitch() {var random = new SecureRandom();/*使用yield返回值*/for (var i = 0; i < 10; i++) { var age = random.nextInt(60); String result = switch (age / 10) { /* 语句块优雅支持*/ case 0 -> { /* switch 整个表达式也可以返回.*/ yield switch (age) { case 1, 2, 3 -> ("婴幼儿:=" + age); default -> ("儿童:=" + age); }; } case 1 -> ("青年:=" + age); case 2 -> ("壮年:=" + age); /*多值Label,直接使用逗号分隔.*/ case 3, 4, 5 -> { var prefix = "中年:="; /*使用yield返回结果*/ yield prefix+age; } default -> ("老年:=" + age); }; System.out.print("\t" + result);}/*中年:=35 婴幼儿:=1 中年:=51 婴幼儿:=1 中年:=32 婴幼儿:=2 壮年:=23 青年:=19 中年:=34 儿童:=5*/}
简单总结一下:
NPE简直是程序员的噩梦,翻找堆栈信息,一路追查,头秃的概率大大增加.
虽然有Optional,但是并不能根本上解决NPE问题.
现在NPE终于进化了,虽然还是NPE,但是有了更精确的NPE描述,帮助你直观地发现NPE根源所在.
@Testvoid testHelpfulNPE() { Integer[] nums = new Integer[5]; /*System.out.println(nums[1].toString());*/ /* java.lang.NullPointerException: Cannot invoke "java.lang.Integer.toString()" because "nums[1]" is null*/ Map<String, Object> map = Map.of(); System.out.println(map.get("key").toString()); /*java.lang.NullPointerException: Cannot invoke "Object.toString()" because the return value of "java.util.Map.get(Object)" is null*/}
简单总结一下:
JAVA总是在别人的屁股后面追,让人又爱又恨,什么时候能遥遥领先一波呀.
其它语言里面方便如草芥的文本块,JAVA现在也终于有了...
@Testvoid testTextBlock() { var text = """ Today , you will miss some magic thing ! Tomorrow , will you still here ? """; System.out.println("#1\n" + text); text = """ \u1234 Test Unicode Chars. \ uvo \n -换行- "-1-1-1--1 "双引号" """; System.out.println("#2\n" + text); text = """ --- String text = \""" A text block inside a text block \"""; --- """; System.out.println("#3\n" + text); text = """ 这是一个单行 \ 文本的拆分 \ 使用 \ 符号在行尾进行衔接 \ ...... """; System.out.println("#4\n" + text); /* #1 Today , you will miss some magic thing ! Tomorrow , will you still here ? #2 ሴ Test Unicode Chars. \ uvo -换行- "-1-1-1--1 "双引号" #3 --- String text = """ A text block inside a text block """; --- #4 这是一个单行 文本的拆分 使用 \ 符号在行尾进行衔接 ...... * */}
简单总结一下:
@Testvoid testTextMethod() { var text = """ 这是一个单行 \ 文本的拆分 \ 使用 \ 符号在行尾进行衔接 \ ......%s - %2d...... 另起一行啦... """; System.out.println(text); text = text.formatted("附加文本", 5); System.out.println(text); text= text.indent(2); System.out.println(text); /* 这是一个单行 文本的拆分 使用 \ 符号在行尾进行衔接 ......%s - %2d...... 另起一行啦... 这是一个单行 文本的拆分 使用 \ 符号在行尾进行衔接 ......附加文本 - 5...... 另起一行啦... 这是一个单行 文本的拆分 使用 \ 符号在行尾进行衔接 ......附加文本 - 5...... 另起一行啦... * */}
简单总结一下:
翻译不出一个好的同义词,我们直接用代码来表达...
@Testvoid testPatternMatching(){ Object target="TextValue"; /*不用再加一句类型强转代码,可以直接匹配变量了.*/ if(target instanceof String strTarget){ System.out.printf("Class: %s, Value: %s\n",strTarget.getClass(),strTarget); } target=12L; if(target instanceof Long longTarget){ System.out.printf("Class: %s, Value: %s\n",longTarget.getClass(),longTarget); } target=true; if(target instanceof Boolean booleanTarget){ System.out.printf("Class: %s, Value: %s\n",booleanTarget.getClass(),booleanTarget); } /* Class: class java.lang.String, Value: TextValue Class: class java.lang.Long, Value: 12 Class: class java.lang.Boolean, Value: true * */}
简单总结一下:
尤记得我们总要定义一些final结构,然后再加一个全参构造器,就只是为了记录数据,作数据输出或传递等用途.
Record类型就是为了满足这一特殊场景,Record类型相对普通的类型定义其在代码量和资源占用等方面都更具优势.
/** * Record 类型可以实现接口,不能继承和被继承,其父类为固定的: java.lang.Record, * 可以实现接口,其等效于 final修饰的类. * @param name 姓名 * @param age 年龄 */record TestRecord(String name,int age) implements Cloneable{ /** * 构造器可以添加逻辑,这个构造器等效于 {@link TestRecord(String name,int age)}. * @param name - * @param age - */ TestRecord { if(age < 1 || age>150){ throw new IllegalArgumentException("年龄不能超过200"); } } /** * Record类型可以添加静态方法 * @param name - * @param age - * @return {@link TestRecord} */ static TestRecord from(String name,int age){ return new TestRecord(name,age); } /** * Record类型可以添加实例方法 * @return - */ String showAge(){ return "年龄:"+this.age; } /** * 实现接口方法. * @return - * @throws CloneNotSupportedException */ @Override protected TestRecord clone() throws CloneNotSupportedException { return new TestRecord(this.name,this.age); }}
简单总结一下:
注意:
但是不能自定义行为,像writeObject, readObject等方法定义是无效的.
我们来测试验证一下:
@Testvoid testRecordClass() throws Exception{ TestRecord record = new TestRecord("测试人员",25); System.out.println(record); record = TestRecord.from("新员工",36); System.out.println(record); System.out.println(record.showAge()); TestRecord cloneOne = record.clone(); System.out.printf("equals: %s ,hashCode: %s - %s, toString: %s",cloneOne.equals(record),cloneOne.hashCode(),record.hashCode(),cloneOne.toString()); /* TestRecord[name=测试人员, age=25] TestRecord[name=新员工, age=36] 年龄:36 equals: true ,hashCode: 797014407 - 797014407, toString: TestRecord[name=新员工, age=36] * */}
什么叫密封类型呢,主要的意思就是一个接口或一个抽象类,当然也可以是一个父类,限定它的可实现范围(或可继承范围).
比如: 我定义一个接口来规范应用中导出文件的行为,它的实现是固定范围的,可能就只支持Excel,Word,Pdf三种,但是因为是接口类,不约束实现范围的话就可能突破这个限制.
abstract sealed class TestSealed permits TestSealed.SealedA, TestSealed.SealedB, TestSealed.SealedC{protected abstract String content();public void render(){ var content = this.content(); System.out.println(content);}static final class SealedA extends TestSealed { @Override protected String content() { return "Render SealedA Class"; }}static final class SealedB extends TestSealed { @Override protected String content() { return "Render SealedB Class"; }}static final class SealedC extends TestSealed { @Override protected String content() { return "Render SealedC Class"; }}}
简单总结一下:
abstract sealed class TestSealed {//......static final class SealedA extends TestSealed {//......}static final class SealedB extends TestSealed {//......}static final class SealedC extends TestSealed {//......}}
static non-sealed class SealedD extends TestSealed{ @Override protected String content() { return "Render [no-sealed] SealedD Class"; }}
测试验证:
@Testvoid testSealedClass(){ TestSealed sealedA = new TestSealed.SealedA(); TestSealed sealedB = new TestSealed.SealedB(); TestSealed sealedC = new TestSealed.SealedC(); TestSealed sealedD = new TestSealed.SealedD(); sealedA.render(); sealedB.render(); sealedC.render(); sealedD.render(); /* Render SealedA Class Render SealedB Class Render SealedC Class Render [no-sealed] SealedD Class * */}
也可以尝试一下去直接扩展 TestSealed 类,来验证其约束性.
JDK持续更新,新的特性像雨后春笋一样,节节递增.
每天一点Java新知识,法力不倒退,再战30年 !
上期文章:Java已经一路飙到21了,快来尝鲜JDK 9 10 11 的这些实用的API!
更多Java知识,Spring Boot知识,实用干货,点击关注,持续更新!