探索Java的强大之处:了解新特性,避免落后!

发表时间: 2023-11-12 02:56

前面我们在文章Java已经一路飙到21了,快来尝鲜JDK 9 10 11 的这些实用的API!了解了JDK9-11实用特性,

而从JDK11到JDK17,又是一波重大更新,今天在这里整理了一些日常开发会经常用到的新特性,看了这些,

相信你会有冲动去一个个地码上实验。

Java

关注更新,提升自我,相信这里绝对有值得你收藏的干货 !

1. 增强+增强的Switch表达式

1.1 case L1,L2,ln -> 表达式

这个表达式可以理解为一个语法糖,现在的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*/}

简单总结一下:

  • A. 表达式更直观,好用.
  • B. 语句块的支持,代码更优雅了.
  • C. 多匹配可以直接逗号分隔了,不用再写一长串了.

1.2 使用yield 在switch表达式中返回结果

现在的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*/}

简单总结一下:

  • 简单表达式计算值,可以省略yield 关键词.
  • 如果switch被当作值表达式,则每个分支都必须返回值.

2. 优化版的NPE

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*/}

简单总结一下:

  • 帮你点明是从哪里开始的NPE.
  • 描述更直白, 不用再眯眼看堆栈.

3. 多行文本块 TextBlock

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	这是一个单行 文本的拆分 使用 \ 符号在行尾进行衔接 ......	* */}

简单总结一下:

  • 三个双引号,还好吧》。。
  • 支持 \ 符号来分隔单行文本,也不错!
  • 是个正经的字符串.

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......		另起一行啦...	* */}

简单总结一下:

  • formatted 方法,和String.format方法同等效果。
  • indent 方法,添加缩进,使用场景挺多的.
  • 其它方法还有很多扩展,大家可以进入String类里面看看.

5. instanceof 的Pattern Matching特性

翻译不出一个好的同义词,我们直接用代码来表达...

@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	* */}

简单总结一下:

  • 万年instanceof + 强转的操作终于可以得到优化了.
  • Pattern Matching 变量是代码块内的局部变量哦,要注意.

6. Record(记录)类型

尤记得我们总要定义一些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);	}}

简单总结一下:

  • Record类定义上使用关键词 record .
  • Record类的访问级别范围和正常类无区别.
  • Record类可以定义实例方法和静态方法,也可以定义静态变量,不能再定义实例变量.
  • Record类实现接口时和正常的类实现接口行为一致.

注意:

  • Record类的变量均为: private final ,只能通过构造器初始化.
  • Record类可以被序列化和反序列化,

但是不能自定义行为,像writeObject, readObject等方法定义是无效的.

  • Record类的所有属性的访问默认为和属性名同名方法,同时默认实现了toString()方法.
  • Record类默认实现了全参数的equals和hashCode方法.

我们来测试验证一下:

@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]	* */}

7. Sealed(密封)类型

什么叫密封类型呢,主要的意思就是一个接口或一个抽象类,当然也可以是一个父类,限定它的可实现范围(或可继承范围).
比如: 我定义一个接口来规范应用中导出文件的行为,它的实现是固定范围的,可能就只支持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";	}}}

简单总结一下:

  • Sealed类在定义时必须指定其子类列表,除非其子类定义都在同一个源码文件中.
    像上面的定义,修改如下,是可以正常编译的.
abstract sealed class TestSealed {//......static final class SealedA extends TestSealed {//......}static final class SealedB extends TestSealed {//......}static final class SealedC extends TestSealed {//......}}
  • Sealed类可以允许其一个子类开放成非final类,需要用 no-sealed修饰符修饰.
static non-sealed class SealedD extends TestSealed{	@Override	protected String content() {		return "Render [no-sealed] SealedD Class";	}}
  • Sealed类的子类也可以是Sealed类.

测试验证:

@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 类,来验证其约束性.

Sealed类扩展限制

结语

JDK持续更新,新的特性像雨后春笋一样,节节递增.

每天一点Java新知识,法力不倒退,再战30年 !

上期文章:Java已经一路飙到21了,快来尝鲜JDK 9 10 11 的这些实用的API!

更多Java知识,Spring Boot知识,实用干货,点击关注,持续更新!