问题 什么时候使用instanceof正确的决定?


正如我一直都明白的那样,主要案例在哪里 instanceof 适当的是:

  1. 实施 Object.equals(Object)。所以,如果我正在写一个 List 上课,而不是延伸 AbstractList 无论出于何种原因,我都会实施 equals(o) 通过第一次测试 o instanceof List,然后比较元素。
  2. 对特殊情况进行的重要(算法?)优化  改变语义,但只改变性能。例如, Collections.binarySearch 做了 instanceof RandomAccess 测试,并使用略有不同的二进制搜索 RandomAccess 和非RandomAccess 名单。

我不认为 instanceof 代表这两种情况下的代码气味。但是有没有其他情况下使用它是明智的 instanceof


9376
2018-01-21 23:50


起源



答案:


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

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


6
2018-01-22 00:01



Hmmmmm。记住任何例子吗?我没有遇到任何与我玩过的API的需求。 - Louis Wasserman
@LouisWasserman任何以非OO方式编写的东西,例如,我们有一个第三方API,其中各种FTP类既没有扩展公共基类也没有实现接口。为了对待它们“相同”,我们的快速解决方法是一些简单的检查实例。最后我们在顶部放了一层。世界上有很多不好的API。 - Dave Newton
EW。我想我应该感谢你不必与任何这些野兽打交道。 - Louis Wasserman


答案:


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

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


6
2018-01-22 00:01



Hmmmmm。记住任何例子吗?我没有遇到任何与我玩过的API的需求。 - Louis Wasserman
@LouisWasserman任何以非OO方式编写的东西,例如,我们有一个第三方API,其中各种FTP类既没有扩展公共基类也没有实现接口。为了对待它们“相同”,我们的快速解决方法是一些简单的检查实例。最后我们在顶部放了一层。世界上有很多不好的API。 - Dave Newton
EW。我想我应该感谢你不必与任何这些野兽打交道。 - Louis Wasserman


回答问题的一种方法是回答“Java库何时使用 instanceof?“如果我们假设 番石榴 是一个设计良好的Java库的示例,我们可以看看它使用的位置 instanceof 决定何时可以接受。

如果我们提取Guava源代码jar并grep它,我们会看到 instanceof 被提及439次,涉及122个文件:

$ 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 with instanceof就像在做的那样 Joiner.toString(Object)

  • 做复杂的异常处理

    显然,“正确”的事情是使用a try/catch 阻止(虽然真的,那是在做 instanceof 检查已经),但有时你有更复杂的处理逻辑,值得使用条件块或将处理传递给单独的方法,例如拉出原因或具有特定于实现的处理。可以在中找到一个例子 SimpleTimeLimiter.throwCause()

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

在所有情况下,我都会这么说 instanceof 检查应该只在内部用作实现细节 - 也就是说依赖于任何方法的调用者 instanceof 检查不应该(轻易)告诉你你做了什么。例如, ImmutableList.copyOf() 总是返回一个 ImmutableList。作为它使用的实现细节 instanceof 避免构建新的 ImmutableLists,但没有必要接受提供预期的行为。

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


6
2017-08-27 02:46



对大型,备受推崇的代码库(Guava)的分析很好。 - kevinarpe
实用程序库不提供Java语言使用的良好示例。例如,它使用了多少个GoF设计模式? - Aleksandr Dubinsky
我用Guava作为一个例子 好 代码库,不一定是代表性的。如果还有其他 好 使用的理由 instanceof 我错过了,我会更新问题。目前,我不知道。 - dimo414


你的第一个案例是我不会使用的例子 instanceof 运算符,但看看类是否相等:

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

这将避免一个实例 A extends B 和 B 被认为是平等的

我可以立即想到的其他情况,但我很确定有更多有效的案例

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

4
2018-01-22 00:00



但是在 List 凯斯,你 想 一个例子 B extends A 和 C extends A 被认为是平等的。 - Louis Wasserman
你的第一点有时非常重要。但是,有些应用程序需要子类实例 equal 到父类实例。在这种情况下, instanceof 比检查班级身份更有意义。也, instanceof 相当于最终类的类标识检查。我没有对这两种方法进行基准测试,但如果不这样做也不会感到惊讶 instanceof 在这种情况下,速度要快一点。 - Ted Hopp
@LouisWasserman但是 instanceof 在这种情况下会失败: anA instanceof aB 将会 false。 - Ted Hopp
对不起有困惑 - 让我们清楚这一点。您希望ArrayList.equals(LinkedList)通过,因此ArrayList.equals(o)测试是否为instanceof List。所以我不会写一个B的实例,而是一些B实例的A,例如A是List,B是ArrayList / LinkedList / whatever。 - Louis Wasserman
使用 Class 比较vs. instanceof 在 equals(),我更倾向于使用 instanceof,因为Liskov替代原则。对比 class 会违反原则(无论如何,我认为没有经验法则:无论哪种方式都可以使用) - Adrian Shum


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

但是,您可以稍微概括一下您的语句,如下所示:

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

-1
2018-01-21 23:58