instanceof/is的优先级原因

19
在C#和Java中,isinstanceof的运算符优先级会导致一些必要但繁琐的括号。例如,你需要写成if (!(bar instanceof Foo))而不是if (!bar instanceof Foo)
那么为什么语言团队决定把!的优先级放在is/instanceof之上呢?尽管在C#中你可以重载operator!,这会在某些情况下导致不同的结果,但这些情况似乎非常罕见(而且在任何情况下都不直观),而检查某个东西是否不是某种类型或其子类型的情况则更加常见。

这个问题应该放在programmers.stackexchange.com上吧? - Adrian
1
@Adrian 我认为这里更合适,而且程序员的描述中没有任何关于语言理论的内容。 - Voo
9个回答

16
在Java中,instanceof是一种关系运算符,它与其他关系运算符具有相同的优先级。
RelationalExpression:
    ShiftExpression
    RelationalExpression < ShiftExpression
    RelationalExpression > ShiftExpression
    RelationalExpression <= ShiftExpression
    RelationalExpression >= ShiftExpression
    RelationalExpression instanceof ReferenceType

从这个角度来看,那两行文本应该遵循相同的结构:

if (!(a instanceof b))
if (!(a < b))

4
当然,那只是技术原因,也就是它的实现方式(我实际上觉得你把“instance of”放入关系运算符中很奇怪),但并不是他们为什么要把它放在那里。 - Voo
RelationExpression是instanceof唯一有意义的类别(在我看来)。另一个选择是创建一个特定的类别。 - assylias

11

以下是我对此事的看法,没有权威来源。

instanceof 是一个非常大的操作符。大多数操作符最多有两个字符。此外,instanceof 必须在变量和它之间加上空格。因为这两点独特之处,当你看到像 !bar instanceof Foo 这样的表达式时,instanceof 看起来自然地将 !barFoo 分开,许多人会觉得如果 !bar 不是子表达式很出乎意料。

类似的思路也可以应用于 is ,另一个论据是遵循 Java 已经做过的事情。


11

阴谋论:

C# 设计师们不希望你使用 is 操作符。过多使用该操作符常常暗示着糟糕的面向对象设计。如果你发现自己经常使用这个操作符,那么很可能是因为你的类层次结构有问题,需要更重度依赖虚方法和模式。Java 的设计师甚至更进一步:他们将操作符命名为instanceof,以使你每次使用时都感到不悦。

实际上这并非不可能。在.NET中,有许多情况下,语言和库设计者会使一些功能难以使用。一些例子: .NET 中的字符编码(应始终使用Unicode),Pascal 中的goto(应避免使用)等。有时是由于糟糕的设计造成的(如.NET中的WPF),但有时也是故意的。


1
这实际上是一个非常好的理由,我同意这并不牵强。Java和C#都极力推动程序员采用特定的编程风格,避免使用这个运算符肯定是其中之一。 - Voo

3
我认为这只是历史原因。如果我没记错,在Java的最早版本中,您甚至不能写if(a instanceof Foo || a instanceof Bar),而不带括号。我想在Java 2左右发生了一些变化。我不知道为什么他们没有将其放在更高的优先级上(例如高于逻辑非)。也许是因为它会干扰类型转换运算符,从而破坏兼容性?
C#似乎只是使用了与Java相同的优先级。
我仍然认为将位和/或的优先级与逻辑and/or保持在同一级别是错误的。像这样编写代码很麻烦:if( (x&(FLAG1|FLAG2)) != 0) …

2

instanceof是一个二元运算符。
!是一个一元运算符。

如果instanceof!的优先级更高,那么会造成非常混乱的情况。
在Python中,我们有**-这两个运算符也存在类似的混乱问题。

-1 ** 2 == -(1 ** 2)  # true

我不知道你怎么看,但在Python中这看起来很荒谬,所以我很高兴他们没有在Java中做同样的事情。

另一个Python示例是:

False is (not None)   # false

并且

False is not None     # true

我认为同样令人困惑的是,这次是因为“is”和“is not”是不同的运算符。

1
显然,在Java中,像!b instanceof SomeType这样的表达式(读作:“否定b,然后检查结果值是否为类型SomeType”)并没有多大意义:

从逻辑上讲,b必须是某种布尔对象(以便!起作用),即使您否定了它的值,它仍将是一个布尔值,并且与之前相同类型,那么为什么要首先对其进行取反呢?

(实际上,您甚至不能这样做:b不能是boolean,因为instanceof需要它是真正的Object,但是,如果bBoolean!b仍将求值为原始的boolean,因此instanceof不起作用。)

因此,我们可以说!b instanceof SomeType在Java中根本没有语义意义。那么我们可以将其重新赋予“检查b是否不是类型SomeType”的含义-难道不是吗?

考虑到这个语法可以在语义上进行更改,但仍未进行更改,这使我得出结论,这不是真正有意的,而是有更实际的原因选择使用较低的优先级来处理 instanceof
一开始,我会怀疑如果你给 instanceof 比一元运算符 ! 更高的优先级,解析将变得复杂。你可能需要检查一下。
另一方面,如果 !b instanceof SomeType 的意思是“检查 b 是否不是类型 SomeType”,这可能仍然会欺骗新手程序员认为 ! 作用于 b,而实际上它是否定 instanceof 的结果,所以最好将 !b instanceof SomeType 留在本质上未定义。

1

因为通过编写if (!bar instanceof Foo)它否定了bar然后查找instanceof。因为这是最左边的语句,我不认为instanceof甚至有优先级。

然后if (!(bar instanceof Foo))它使instanceof成为首要考虑,并否定整个事情。

如果您需要否定bar然后检查实例,则执行((!bar) instanceof Foo)


5
是的,我理解运算符优先级的基本原理。问题是为什么要以这种方式选择运算符优先级。显然,您不必编写5 +(2 * 3)才能获得正确结果11,而不是21 - 这正是因为正确选择了运算符优先级(“正确”显然只适用于整数特定领域)。同样的方法也可以在这里使用 - 或者不行?这就是问题所在。 - Voo
什么是否定一个对象的意思? - asteri
否定一个对象只有在它具有可以隐式转换为布尔值的属性时才能起作用,我本以为会这样。 - Adrian
是的,我也注意到了,但我猜作者更感兴趣的是技术细节而不是实际逻辑。 - Jenny
我想再重复一遍,存在是属于数学运算符的一部分。instanceof 没有这个属性,所以它会在解释器到达时出现。 - Jenny
1
@Jeff operator! 在 C# 中可以任意重载,但是这并不常用,所以为什么要选择这样的顺序呢?声称优先级属于数学运算符是错误的,所有运算符都必须被定义,instanceof也不例外。同样适用于强制类型转换、对象访问(.)或数组成员访问——所有这些在语言规范中都有明确定义的优先级。左结合性仅适用于相同优先级的情况,即使在这种情况下也只有在声明时才适用!例如,赋值优先级级别被定义为右结合。 - Voo

1
  1. instanceof这个单词与基本运算符如+++相比非常长。当你阅读条件时,你就会分心,至少我是这样的。
  2. 它周围有空格,可以增加可读性,但另一方面,你无法像5+6那样与其他操作数连接起来

我相信他们决定说:好吧,优先级较低,因此每个人都必须提供括号以确保发生了什么


0

因为C编程语言犯了错误,而Java盲目地跟随了C。

在C中,!和~具有相同的优先级。实际上,在C中并不重要,因为人们会写a<b而不是!(a>=b)。

但是,Java没有notinstanceof运算符。

你可能还会问为什么/* /* */ */不能正确嵌套。或者为什么Java从0开始计数。或者为什么需要void关键字。或者为什么Java使用可怕的{{}{}}表示法而不是endif(或fi)。这都是C遗留问题。

也许有充分的理由。C程序员会认为所有这些东西都是正确的方法,因为这是他们习惯的方式。而Java的第一项任务是被注意到和使用,不像许多其他已被遗忘的小型编程语言。

感谢Java没有空终止字符串。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接