什么时候使用instanceof正确的决定?
正如我一直都理解的那样,一个适当的instanceof
的主要情况是:
- 实现
Object.equals(Object)
。 因此,如果我正在编写List
类,而不是出于任何原因扩展AbstractList
,我将通过首先测试o instanceof List
,然后比较元素来实现equals(o)
。 - 对特殊情况的重要(算法?)优化,它不会改变语义,只会改变性能。 例如,
Collections.binarySearch
执行instanceof RandomAccess
测试的instanceof RandomAccess
,并对RandomAccess
和非RandomAccess
列表使用稍微不同的二进制搜索。
我不认为instanceof
代表这两种情况下的代码气味。 但是有没有其他情况下使用instanceof
是明智的?
您控件之外的旧代码或API是instanceof
的合法用instanceof
。 (即便如此,我宁愿在它上面写一个OO层,但时间有时会排除这样的重新设计。)
特别是,基于外部类层次结构的工厂似乎是常见的用法。
回答你的问题的一种方法是回答“一个可靠的Java库何时使用instanceof
?” 如果我们假设Guava是一个设计良好的Java库的示例,我们可以看看它使用instanceof
来决定何时可以接受。
如果我们提取Guava源代码jar并grep它,我们会看到在122个文件中提到了instanceof
439次:
$ pwd /tmp/guava-13.0.1-sources $ grep -R 'instanceof' | wc -l 439 $ grep -Rl 'instanceof' | wc -l 122
看看其中一些案例我们可以看到几种模式出现:
-
检查是否平等
这是最常见的用法。 这在某种程度上是特定于实现的,但假设您确实希望基于它扩展/实现的类/接口来测量相等性,您可以使用
instanceof
来确保您使用的对象。 但是,如果子类重写equals()
并且不尊重与父类相同的instanceof
,则这可能会导致奇怪的问题。 各地的例子,但是一个简单的例子是由ImmutableList
使用的Lists.equalsImpl()
。 -
短路不必要的对象构造
您可以使用
instanceof
来检查传入的参数是否可以安全地使用或返回而无需进一步转换它,例如,如果它已经是所需类的实例,或者我们知道它是不可变的。 请参阅CharMatcher.forPredicate()
,Suppliers.memoize()
,ImmutableList.copyOf()
等中的示例。 -
访问实现细节而不暴露不同的行为
这可以在Guava的所有地方看到,但特别是在
com.google.common.collect
包中的静态实用程序类中,例如在Iterables.size()
中,如果可能的话,它会调用Collection.size()
,否则在O(n)
时间内计算迭代器中的项目数。 -
避免调用
toString()
我怀疑这个优点是在非常少的情况下完成的,但假设你确定你做的是正确的事情,你可以避免不必要地将
CharSequence
对象转换为带有instanceof
String
,就像在Joiner.toString(Object)
完成的那样Joiner.toString(Object)
。 -
做复杂的exception处理
显然,“正确”的事情是使用
try/catch
块(尽管实际上,它已经在进行instanceof
检查),但有时候你有更复杂的处理逻辑,值得使用条件块或将处理传递给单独的方法,例如拉出原因或具有特定于实现的处理。 可以在SimpleTimeLimiter.throwCause()
找到一个示例。
看待这种行为突出的一件事就是几乎所有这些都在解决我不应该解决的问题 。 它们在库代码中很有用,例如在Iterables
,但是如果我实现这种行为,我应该问自己是否没有库或实用程序可以解决这个问题。
在所有情况下,我都会说, instanceof
应该只在内部用作实现细节 – 也就是说依赖于instanceof
检查的任何方法的调用者都不应该(轻易地)告诉你你做了什么。 例如, ImmutableList.copyOf()
始终返回ImmutableList
。 作为一个实现细节,它使用instanceof
来避免构造新的ImmutableList
,但这不是可接受地提供预期行为所必需的。
顺便说一句,当我在挖掘Guava的源代码时,看到你的名字路易斯很有趣。 我发誓我不知道!
您的第一个案例是我不使用instanceof
运算符的instanceof
,但请查看类是否相等:
o != null && o.getClass() == this.getClass()
这将避免A extends B
的实例A extends B
和B
被认为是相等的
我可以立即想到的其他情况,但我很确定有更多有效的案例
- 例如,您具有
canCreate
和create
方法的工厂实例,该方法接收作为参数的通用接口。 每个工厂都可以处理接口的特定实现,因此需要一个instanceof
。 仅定义工厂抽象类/接口中的接口允许例如编写复合工厂 - 复合实现(如我的第一个例子中所示)
正如您所提到的, instanceof
的“正确”用法相当有限。 据我所知,你基本上总结了两个主要用途。
但是,您可以稍微概括一下您的语句,如下所示:
- 在必要的演员表之前进行类型检查。
- 实现依赖于非常特定的类实例的特殊情况