【问题标题】:When is using instanceof the right decision?什么时候使用 instanceof 是正确的决定?
【发布时间】:2012-01-21 23:50:06
【问题描述】:

正如我一直理解的那样,instanceof 适合的主要情况是:

  1. 实现Object.equals(Object)。因此,如果我正在编写 List 类,并且出于任何原因不扩展 AbstractList,我将通过首先测试 o instanceof List,然后比较元素来实现 equals(o)
  2. 改变语义但只改变性能的特殊情况进行了重要的(算法?)优化。例如,Collections.binarySearch 执行instanceof RandomAccess 测试,并对RandomAccess 和非RandomAccess 列表使用稍微不同的二分搜索。

我不认为instanceof 在这两种情况下代表代码异味。但是还有其他情况可以使用instanceof吗?

【问题讨论】:

    标签: java design-patterns


    【解决方案1】:

    回答您的问题的一种方法是回答“可靠的 Java 库何时使用 instanceof?”如果我们假设 Guava 是一个设计良好的 Java 库的例子,我们可以看看它在哪里使用 instanceof 来决定什么时候可以接受。

    如果我们提取 Guava 源代码 jar 并对其进行 grep,我们会看到 instanceof 在 122 个文件中被提及 439 次:

    $ pwd
    /tmp/guava-13.0.1-sources
    $ grep -R 'instanceof' | wc -l
    439
    $ grep -Rl 'instanceof' | wc -l
    122
    

    查看其中一些案例,我们可以看到出现了几种模式:

    • 检查相等性

      这是最常见的用法。这在某种程度上是特定于实现的,但假设您确实想根据它扩展/实现的类/接口来衡量相等性,您可以使用 instanceof 来确保您使用的对象是 .但是,如果子类覆盖 equals() 并且不遵守与父类相同的 instanceof 要求,这可能会导致奇怪的问题。随处可见的例子,但一个简单的例子是Lists.equalsImpl(),它被ImmutableList 使用。

    • 短路不必要的对象构造

      您可以使用instanceof 来检查传入的参数是否可以安全使用或返回而无需进一步转换,例如它是否已经是所需类的实例,或者我们是否知道它是不可变的。请参阅CharMatcher.forPredicate()Suppliers.memoize()ImmutableList.copyOf() 等中的示例。

    • 在不暴露不同行为的情况下访问实现细节

      这在 Guava 中随处可见,但特别是在 com.google.common.collect 包中的静态实用程序类中,例如在 Iterables.size() 中,如果可能,它会调用 Collection.size(),否则会计算项目数在 O(n) 时间的迭代器中。

    • 为避免调用toString()

      我怀疑这是否值得在极少数情况下完成,但假设您确定自己在做正确的事情,您可以避免不必要地将 CharSequence 对象转换为 Strings 和 @ 987654342@,就像在Joiner.toString(Object) 中完成的一样。

    • 做复杂的异常处理

      显然,“正确”的做法是使用 try/catch 块(虽然实际上,这已经在进行 instanceof 检查),但有时您有更复杂的处理逻辑,值得使用条件块或将处理传递给单独的方法,例如提取原因或具有特定于实现的处理。一个例子可以在SimpleTimeLimiter.throwCause()找到。

    从这种行为中脱颖而出的一点是,几乎所有这些行为都在解决问题我不应该解决。它们在库代码中很有用,例如在Iterables 中,但如果我正在实现这种行为,我可能应该问自己是否没有库或实用程序可以为我解决这个问题。

    在所有情况下,我会说 instanceof 检查应该只在内部用作实现细节 - 也就是说,任何依赖于 instanceof 检查的方法的调用者都不能(很容易) 告诉这就是你所做的。例如,ImmutableList.copyOf() 始终返回 ImmutableList。作为一个实现细节,它使用instanceof 来避免构造新的ImmutableLists,但这对于可接受地提供预期的行为并不是必需的。

    顺便说一句,当我在挖掘 Guava 的源代码时,看到你的名字 Louis 很有趣。我发誓我不知道!

    【讨论】:

    • 实用程序库没有提供 Java 语言使用的良好示例。例如,它使用了多少种 GoF 设计模式?
    • 我用番石榴作为一个代码库的例子,不一定是有代表性的。如果我错过了使用instanceof 的其他 理由,我会更新问题。目前,我不知道。
    【解决方案2】:

    您无法控制的旧代码或 API 是 instanceof 的合法用例。 (即使那样我也宁愿在它上面写一个 OO 层,但时间有时会排除这样的重新设计。)

    特别是,基于外部类层次结构的工厂似乎很常见。

    【讨论】:

    • 嗯。有什么例子吗?我用过的任何 API 都没有遇到过这种需求。
    • @LouisWasserman 任何以非 OO 方式编写的东西,例如,我们有一个第三方 API,其中各种 FTP 类既不扩展公共基类也不实现接口。为了将它们“相同”对待,我们的快速解决方法是进行一些简单的 instanceof 检查。最后我们在上面放了一层。世界上有大量糟糕的 API。
    • Ew。我想我应该庆幸自己不必与那些野兽打交道。
    【解决方案3】:

    您的第一种情况是我不会使用instanceof 运算符的示例,但请查看类是否相等:

    o != null && o.getClass() == this.getClass()
    

    这将避免A extends BB 的实例被视为相等

    我能立即想到的其他案例,但我很确定还有更多有效案例可供选择

    • 工厂实例,例如,您有一个canCreatecreate 方法,它们接收一个通用接口作为参数。每个工厂都可以处理接口的特定实现,因此需要instanceof。仅在工厂抽象类/接口中定义接口允许例如编写复合工厂
    • 复合实现(如我的第一个示例所示)

    【讨论】:

    • 但在List 的情况下,您希望B extends AC extends A 的实例视为相等。
    • 您的第一点有时非常重要。但是,在某些应用程序中,您希望子类实例成为父类实例的equal。在这种情况下,instanceof 比检查类身份更有意义。此外,instanceof 相当于您对最终课程的课程身份检查。我还没有对这两种方法进行基准测试,但如果 instanceof 在这种情况下稍微快一点,我也不会感到惊讶。
    • @LouisWasserman 但是instanceof 在这种情况下会失败:anA instanceof aB 将是false
    • 抱歉给您带来了困惑——让我们澄清一下。您希望 ArrayList.equals(LinkedList) 通过,因此 ArrayList.equals(o) 测试是否为 o instanceof List。所以我不会写 B 的 A 实例,而是 A 的某个 B 实例,例如A 是 List,B 是 ArrayList/LinkedList/whatever。
    • 为了比较 Classequals() 中的 instanceof,我更倾向于使用 instanceof,因为 Liskov 替换原则。比较class会违反原则(反正我认为没有经验法则:任何一种方式都有自己的情况可以使用)
    【解决方案4】:

    正如您所提到的,instanceof 的“正确”用法相当有限。据我所知,你基本上总结了两个主要用途。

    不过,您可以将您的陈述概括如下:

    1. 在必要的转换之前进行类型检查。
    2. 实现依赖于非常特殊的类实例的特殊情况

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-30
      • 2012-03-25
      • 2010-09-19
      • 2012-09-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多