即将
发布的 Java
第14版不远,这意味着该是该Java版本将包含哪些新语法功能的时候了。 这些语法可能性之一是将使用改进(扩展)的
instanceof
运算符实现
的类型的模式匹配 。
今天,我想与这个新运营商合作,并更详细地考虑其工作特点。 由于按类型进行模式匹配尚未进入主JDK存储库,因此我必须下载
Amber项目的存储库
,该项目正在开发新的Java语法结构,并从该存储库
编译JDK 。
因此,我们要做的第一件事是检查Java版本,以确保我们确实使用JDK 14:
> java -version openjdk version "14-internal" 2020-03-17 OpenJDK Runtime Environment (build 14-internal+0-adhoc.osboxes.amber-amber) OpenJDK 64-Bit Server VM (build 14-internal+0-adhoc.osboxes.amber-amber, mixed mode, sharing)
没错
现在,我们将使用“旧的”
instanceof
运算符编写一小段代码并运行它:
public class A { public static void main(String[] args) { new A().f("Hello, world!"); } public void f(Object obj) { if (obj instanceof String) { String str = (String) obj; System.out.println(str.toLowerCase()); } } }
> java A.java hello, world!
可以用 这是标准类型检查,后跟强制类型转换。 无论我们使用什么版本的Java,至少每天1.0,至少13,我们每天都会编写类似的构造。
但是现在有了Java 14,让我们使用改进的
instanceof
运算符重写代码(以后我将省略重复的代码行):
if (obj instanceof String str) { System.out.println(str.toLowerCase()); }
> java --enable-preview --source 14 A.java hello, world!
太好了 该代码更简洁,简短,安全并且可读性强。 字符串一词有3次重复,一次重复。 请注意,我们不会忘记将参数
--enable-preview --source 14
指定为 新的运算符是
预览功能 。 另外,一个细心的读者可能会注意到我们直接运行A.java源文件,而没有进行编译。 此功能
出现在Java 11中。
让我们尝试编写更复杂的内容,并添加另一个使用刚刚声明的变量的条件:
if (obj instanceof String str && str.length() > 5) { System.out.println(str.toLowerCase()); }
它可以编译和工作。 但是,如果交换条件怎么办?
if (str.length() > 5 && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: cannot find symbol if (str.length() > 5 && obj instanceof String str) { ^
编译错误。 可以预期:
str
变量尚未声明,这意味着它不能使用。
顺便说一下,可变性如何? 变量是否为final? 我们尝试:
if (obj instanceof String str) { str = "World, hello!"; System.out.println(str.toLowerCase()); }
A.java:8: error: pattern binding str may not be assigned str = "World, hello!"; ^
是的,最后一个变量。 这意味着“变量”一词在这里并不完全正确。 并且编译器使用特殊术语“模式绑定”。 因此,我从现在开始建议不要说“变量”,而是说“模式绑定”(不幸的是,“绑定”一词并没有很好地翻译成俄语)。
用可变性和术语进行整理。 让我们进一步尝试。 如果我们设法“破坏”编译器怎么办?
如果您用相同的名称命名变量和模式绑定该怎么办?
if (obj instanceof String obj) { System.out.println(obj.toLowerCase()); }
A.java:7: error: variable obj is already defined in method f(Object) if (obj instanceof String obj) { ^
是合乎逻辑的。 从外部作用域重叠变量不起作用。 这等效于好像我们只是在同一范围内第二次对变量
obj
。
如果是这样:
if (obj instanceof String str && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: illegal attempt to redefine an existing match binding if (obj instanceof String str && obj instanceof String str) { ^
编译器坚固如具体。
您还能尝试什么? 让我们玩一下示波器。 如果在
if
分支中定义了绑定,那么在条件反转的情况下会在
else
分支中定义吗?
if (!(obj instanceof String str)) { System.out.println("not a string"); } else { System.out.println(str.toLowerCase()); }
奏效了。 编译器不仅可靠,而且很聪明。
如果是这样?
if (obj instanceof String str && true) { System.out.println(str.toLowerCase()); }
它再次起作用。 编译器正确地了解到条件可以归结为
obj instanceof String str
的简单
obj instanceof String str
。
真的不可能“破坏”编译器吗?
可能是这样吗?
if (obj instanceof String str || false) { System.out.println(str.toLowerCase()); }
A.java:8: error: cannot find symbol System.out.println(str.toLowerCase()); ^
是的 这已经看起来像个错误。 毕竟,所有三个条件都是绝对相等的:
obj instanceof String str
obj instanceof String str && true
obj instanceof String str || false
另一方面,流量范围规则并不简单,也许这样的情况确实不起作用。 但是,如果仅从人的角度看,那么我认为这是一个错误。
但是,来吧,让我们尝试其他事情。 这项工作会:
if (!(obj instanceof String str)) { throw new RuntimeException(); } System.out.println(str.toLowerCase());
已编译。 很好,因为此代码等效于以下代码:
if (!(obj instanceof String str)) { throw new RuntimeException(); } else { System.out.println(str.toLowerCase()); }
由于这两个选项是等效的,因此程序员希望它们以相同的方式工作。
重叠字段呢?
public class A { private String str; public void f(Object obj) { if (obj instanceof String str) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } } }
编译器不发誓。 这是合乎逻辑的,因为局部变量可能总是与字段重叠。 显然,他们还决定不为模式绑定设置例外。 另一方面,这样的代码非常脆弱。 一个粗心的举动,您可能不会注意到
if
分支如何中断:
private boolean isOK() { return false; } public void f(Object obj) { if (obj instanceof String str || isOK()) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } }
这两个分支现在都使用
str
字段,这是专心的程序员可能不会想到的。 若要尽早检测到此类错误,请在IDE中使用检查,并对字段和变量使用不同的语法突出显示。 我还建议您始终对字段使用
this
限定符。 这将增加更多的可靠性。
还有什么有趣的? 就像“旧”
instanceof
,新
instanceof
永远不会匹配
null
。 这意味着您可以始终依靠模式绑定器永远不能为
null
的事实:
if (obj instanceof String str) { System.out.println(str.toLowerCase());
顺便说一句,使用此属性,您可以缩短这样的链:
if (a != null) { B b = a.getB(); if (b != null) { C c = b.getC(); if (c != null) { System.out.println(c.getSize()); } } }
如果您使用
instanceof
,那么上面的代码可以这样重写:
if (a != null && a.getB() instanceof B b && b.getC() instanceof C c) { System.out.println(c.getSize()); }
在评论中写下您对这种风格的看法。 你会用这个成语吗?
泛型呢?
import java.util.List; public class A { public static void main(String[] args) { new A().f(List.of(1, 2, 3)); } public void f(Object obj) { if (obj instanceof List<Integer> list) { System.out.println(list.size()); } } }
> java --enable-preview --source 14 A.java Note: A.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 3
非常有趣 如果“旧”
instanceof
仅支持
instanceof List
或
instanceof List<?>
,则新
instanceof List<?>
可以使用任何特定类型。 我们正在等待第一个人陷入这样的陷阱:
if (obj instanceof List<Integer> list) { System.out.println("Int list of size " + list.size()); } else if (obj instanceof List<String> list) { System.out.println("String list of size " + list.size()); }
恕我直言,这是一个非常严重的问题。 另一方面,我不知道如何解决。 看来您必须再次依靠IDE中的检查。
结论
通常,新的模式匹配类型非常酷。 改进的
instanceof
运算符使您不仅可以进行类型测试,还可以声明这种类型的现成的活页夹,从而无需进行手工转换。 这意味着代码中的噪音会更少,并且读者更容易辨别有用的逻辑。 例如,大多数
equals()
实现可以写成一行:
public class Point { private final int x, y; … @Override public int hashCode() { return Objects.hash(x, y); } @Override public boolean equals(Object obj) { return obj instanceof Point p && px == this.x && py == this.y; } }
上面的代码可以写得更短。 怎么了使用Java 14中也将包含的
条目 。我们下次再讨论。
另一方面,一些有争议的观点提出了一些小问题:
- 不是完全透明的范围规则(例如
instanceof || false
)。 - 重叠字段。
instanceof
和泛型。
但是,与严肃的要求相比,这些更是挑剔。 总而言之,新
instanceof
运算符的巨大优势绝对值得其添加语言。 而且,如果它仍然离开预览状态并变为稳定的语法,那么最终将Java 8保留为新版本的Java将是一个很大的动力。
PS:我
在Telegram中有一个
频道 ,我
在其中撰写有关Java新闻的文章。 我敦促您订阅它。