Java – Java 新特性说明与使用
switch 表达式(Java 12)
传统switch的弊端
传统的switch声明语句(switch statement)在使用中有一些问题:
- 匹配是自上而下的,如果忘记写break, 后面的case语句不论匹配与否都会执行;
- 所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复;
- 不能在一个case里写多个执行结果一致的条件;
- 整个switch不能作为表达式返回值;
Java 12将会对switch声明语句进行扩展,可将其作为增强版的 switch 语句或称为 "switch 表达式"来写出更加简化的代码。
举例:
传统的 switch 代码
public class SwitchTest {
public static void main(String[] args) {
int numberOfLetters;
Fruit fruit = Fruit.APPLE;
switch (fruit) {
case PEAR:
numberOfLetters = 4;
break;
case APPLE:
case GRAPE:
case MANGO:
numberOfLetters = 5;
break;
case ORANGE:
case PAPAYA:
numberOfLetters = 6;
break;
default:
throw new IllegalStateException("No Such Fruit:" + fruit);
}
System.out.println(numberOfLetters);
}
}
enum Fruit {
PEAR, APPLE, GRAPE, MANGO, ORANGE, PAPAYA;
}
如果有编码经验,你一定知道,switch 语句如果漏写了一个 break,那么逻辑往往就跑偏了,这种方式既繁琐,又容易出错。如果换成 switch 表达式,Pattern Matching 机制能够自然地保证只有单一路径会被执行:
public class SwitchTest1 {
public static void main(String[] args) {
Fruit fruit = Fruit.GRAPE;
switch(fruit){
case PEAR -> System.out.println(4);
case APPLE,MANGO,GRAPE -> System.out.println(5);
case ORANGE,PAPAYA -> System.out.println(6);
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
};
}
}
更进一步,下面的表达式,为我们提供了优雅地表达特定场合计算逻辑的方式:
public class SwitchTest2 {
public static void main(String[] args) {
Fruit fruit = Fruit.GRAPE;
int numberOfLetters = switch(fruit){
case PEAR -> 4;
case APPLE,MANGO,GRAPE -> 5;
case ORANGE,PAPAYA -> 6;
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
};
System.out.println(numberOfLetters);
}
}
支持unicode 11(Java 12)
JDK 12版本包括对Unicode 11.0.0的支持。在发布支持Unicode 10.0.0的JDK 11之后,Unicode 11.0.0引入了以下JDK 12中包含的新功能:
- 684 new characters
- 11 new blocks
- 7 new scripts.
其中:
- 684个新字符,包含以下重要内容:
- 66个表情符号字符(66 emoji characters)
- Copyleft符号(Copyleft symbol)
- 评级系统的半星(Half stars for rating systems)
- 额外的占星符号(Additional astrological symbols)
- 象棋中国象棋符号(Xiangqi Chinese chess symbols)
7个新脚本:
- Hanifi Rohingya
- Old Sogdian
- Sogdian
- Dogra
- Gunjala Gondi
- Makasar
- Medefaidrin
11个新块,包括上面列出的新脚本的7个块和以下现有脚本的4个块:
- 格鲁吉亚扩展(Georgian Extended)
- 玛雅数字(Mayan Numerals)
- 印度Siyaq数字(Indic Siyaq Numbers)
- 国际象棋符号(Chess Symbols)
支持压缩数字格式化(Java 12)
NumberFormat 添加了对以紧凑形式格式化数字的支持。紧凑数字格式是指以简短或人类可读形式表示的数字。例如,
在en_US语言环境中,1000可以格式化为“1K”,1000000可以格式化为“1M”,具体取决于指定的样式
NumberFormat.Style。
public void testCompactNumberFormat(){
var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA,
NumberFormat.Style.SHORT);
System.out.println(cnf.format(1_0000));
System.out.println(cnf.format(1_9200));
System.out.println(cnf.format(1_000_000));
System.out.println(cnf.format(1L << 30));
System.out.println(cnf.format(1L << 40));
System.out.println(cnf.format(1L << 50));
}
输出
1万
2万
100万
11亿
1兆
1126兆
String新增方法(Java 12)
transform 链式处理方法
语法:
“xxx”.transform(word -> {})
作用:
提供的函数作为输入提供给特定的String实例,并返回该函数返回的输出。
举例:
var result = "foo".transform(input -> input + " bar");
System.out.println(result); // foo bar
或者
var result = "foo"
.transform(input -> input + " bar")
.transform(String::toUpperCase)
System.out.println(result); // FOO BAR
indent 缩进方法
该方法允许我们调整String实例的缩进。
举例:
private static void testIndent() {
System.out.println("======test java 12 indent======");
String result = "Java\n Python\nC++".indent(3);
System.out.println(result);
}
输出
======test java 12 indent======
Java
Python
C++
换行符 \n 后向前缩进 n 个空格,为 0 或负数不缩进。
Files 新增 mismatch 方法(Java 12)
对两个文件中的内容进行对比,返回两个文件中首次出现不同内容的所在位置,如果两个文件内容完全相同,则返回 -1L
public void testFilesMismatch() throws IOException {
FileWriter fileWriter = new FileWriter("tmp\\a.txt");
fileWriter.write("a");
fileWriter.write("b");
fileWriter.write("c");
fileWriter.close();
FileWriter fileWriterB = new FileWriter("tmp\\b.txt");
fileWriterB.write("a");
fileWriterB.write("1");
fileWriterB.write("c");
fileWriterB.close();
System.out.println(Files.mismatch(Path.of("tmp/a.txt"),Path.of("tmp/b.txt")));
}
对 switch 引入 yield (Java 13)
在JDK 12中引入了Switch表达式作为预览特性。JDK 13提出了第二个switch表达式预览。JEP 354修改了这个特性,它引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield, switch语句(不返回值)应该使用break。
public void testSwitch2(){
String x = "3";
int i = switch (x) {
case "1" -> 1;
case "2" -> 2;
default -> {
yield 3;
}
};
System.out.println(i);
}
或者
public void testSwitch3() {
String x = "3";
int i = switch (x) {
case "1":
yield 1;
case "2":
yield 2;
default:
yield 3;
};
System.out.println(i);
}
在这之后,switch中就多了一个关键字用于跳出switch块了,那就是yield,他用于返回一个值。
和return的区别在于:return会直接跳出当前循环或者方法,而yield只会跳出当前switch块。
文本块(Java 13)
在Java中,通常需要使用String类型表达HTML,XML,SQL或JSON等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。
文本块就是指多行字符串,例如一段格式化后的xml、json等。而有了文本块以后,用户不需要转义,Java能自动搞定。因此,文本块将提高Java程序的可读性和可写性。
"""
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
关于删除前后空格的问题
使用文本块,会对包裹的文本进行删首尾空的操作,对于文本块中的每一行文本后面的空格,文本块都会进行删尾空。
对于文本块前的空格,以【文本块结束符】所在的位置决定
如果文本块中的每一行后存在空格,文本块会直接删除
"""
line1........
line2...
line3......
"""
删除后的效果
"""
line1
line2
line3
"""
对于文本块中的每一行前面的空格删除规则如下:
如果文本块前的每一行前面都有空格如下
"""
........line1
........line2
........line3
...."""
文本块会以【结束符】所在的位置进行计算,如果【结束符】所在的位置前有若干个空格,则删除每一行前面若干个空格
"""
....line1
....line2
....line3
"""
注意:如果需要在每一行后面增加空格,可以使用转义符【\s】表示
如果希望两行内容不作为真正的换行【即不换行】,可以使用【\】来表示不换行
关于文本块的开始和结尾定义
1.文本块的开始必须开始在【开始符】之后的【换行符】也叫【行终止符】
2.文本块的结束不需要【行终止符】
1.正确,"abc"在 """ 的换行之后,换行符即为【行终止符】
"""
abc
"""
2.正确,“abc”在 """ 之前
"""
abc"""
3.错误,“abc” 不在 “”” 的【行终止符】之后
““”abc
“””
文本块拼接
使用一般方式进行拼接
String code = """
public void print(""" + type + """o) {
System.out.println(Objects.toString(o));
}
""";
使用文本替换方式拼接
String code = """
public void print($type o) {
System.out.println(Objects.toString(o));
}
""".replace("$type", type);
使用format格式化文本方式拼接
String code = String.format("""
public void print(%s o) {
System.out.println(Objects.toString(o));
}
""", type);
instanceof 模式匹配 (Java 14)
instanceof 是用于判断某个变量是否为指定对象类型,在Java 中一直都有这样的功能,但在Java14中提出了一种新的匹配,可以省下比较多的代码操作:
判断 obj 是否为 String 类型,如果是,则对 obj 对象进行文本操作
if (obj instanceog String){
// 需要强转类型后再做相应的对象操作
String str = (String) obj;
str.toupcase();
}else{
System.out.println("不是String类型");
}
以往的操作方式需要对对象进行强转
在Java14后,可以通过语法优化实现直接赋值,无需强制转化类型
判断 obj 是否为 String 类型,如果是,则对 obj 对象进行文本操作
if (obj instanceog String str){
// 不再需要强转类型后再做相应的对象操作
System.out.println(str);
}else{
// 根据逻辑上来说,else 中就不能访问 str 变量了
System.out.println("不是String类型");
}
record 结构类
当我们需要创建一个只需数据类(即只是用来存储和读取数据的类)时,我们往往需要在这个类上,定义好 final 常量成员,和 get 方法,以及 toSting 和 hashcode 方法,这样创建的数据类显得异常麻烦,在Java14中提供了一种新特性,可以通过定义 record 类,直接自动化创建以上功能。
public final class User {
private final String name;
private final User partner;
public User(String name, User partner) {
this.name = name;
this.partner = partner;
}
public String getName() {
return name;
}
public User getPartner() {
return partner;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return name.equals(user.name) &&
partner.equals(user.partner);
}
@Override
public int hashCode() {
return Objects.hash(name, partner);
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", partner=" + partner +
'}';
}
}
不使用新特性创建数据类的方法,显得非常麻烦
public record Person(String name,Person partner) { }
使用新特性后,只需要声明 record 类即可达到相同效果。
注意:
1.可以在 Record 声明的类中定义静态字段、静态方法、构造器或实例方法。
2. 不能在 Record 声明的类中定义实例 字段;
类不能声明为 abstract ,因为 Record 会被声明为 final 类型,与 abstract 发生冲突了;
不能声明显式的父类等,因为 Record 类会被继承为 java.lang.Record 父类了,只能单继承。
sealed 密封类(Java 15)
Java提出了一个问题,在以往对于Java类的继承问题上,只允许【可被继承】和【不可被继承】两种状态,默认则是【可被继承】状态的,当某个类不希望被其它类继承时,可以使用【final】修饰符来定义该类不能被继承。
但是,单从【可被继承】和【不可被继承】两种状态,在类继承上会显得比较单一,于是Java15中提出了一个新的慨念,称为【密封类】
密封类并不完全拒绝被继承,也判不完全允许被继承,而是允许特定的子类继承。
这种思想是因为,继承类的诞生不全是为了代码复用,也有作为规范类的用途范围的作用。而sealed 密封类则是作为规范某些子类的用途范围而设的思想。
定义 sealed 类后,该类需要声明指定【允许继承的子类】,则除指定子类允许被继承外,其它子类都不能被继承。
规则:
1.子类若要继承 sealed 父类,则子类必须满足【final】类,阻止后代类再次继承子类。
2.子类若要继承 sealed 父类,则子类必须满足【non-sealed】 类,表示子类可以被任意后代类继承
3.如果子类也需要规范后代类的继承,则子类必须满足【sealed】类,并指定可被继承的孙类
// 祖类是 sealed 类,并指定允许被继承的子类为 Teacher,Student,Worker
public sealed class Person permits Teacher,Student,Worker{ } //人
// 若Teacher 类想继承 Person 类,则必须修饰 final 类
final class Teacher extends Person { }//教师
// 若 Student 类还希望它的指定子类能被继承,也可以修饰为 sealed 类
sealed class Student extends Person permits MiddleSchoolStudent,GraduateStudent{ } //学生
// MiddleSchoolStudent 孙类要继承父类,则必须修饰 sealed 类
final class MiddleSchoolStudent extends Student { } //中学生
// GraduateStudent 孙类要继承父类,则必须修饰 sealed 类
final class GraduateStudent extends Student { } //研究生
// 如果子类希望任意孙类继承它,则需要声明为 non-sealed 类
non-sealed class Worker extends Person { } //工人
// 这样 RailWayWorker 就可以任意继承 声明为 non-sealed 类的 Worker 类了
class RailWayWorker extends Worker{} //铁路工人
共有 0 条评论