[当代码包含Java instanceof
运算符时,很多人会大惊小怪,说这是不行的。例如,在此other SO Q&A中,答案说:
请注意,如果您必须经常使用该运算符,通常暗示您的设计存在一些缺陷。所以在精心设计的应用程序,您应该尽可能少地使用该运算符(当然,该一般规则也有例外)。
但是,当可以使用instanceof
的时候,不进行详细说明。
我对此进行了思考,并阐明了以下准则。我以为这可能已经在Internet上的某个地方讨论过了,但是我找不到它。因此,这个问题并要求您发表评论:
在接口上使用
instanceof
可以;在实现上使用instanceof
不好
这里是“还可以”的例子。
示例:动物目录,其中一些(但不是全部)可以飞翔
Animal.java
public interface Animal {
String getName();
String makeNoise();
}
CanFly.java
public interface CanFly {
float getMaxInAirDistanceKm();
}
Cat.java
public class Cat implements Animal {
@Override
public String getName() {
return "Cat";
}
@Override
public String makeNoise() {
return "meow";
}
}
BaldEgale.java
public class BaldEagle implements Animal, CanFly {
@Override
public String getName() {
return "BaldEagle";
}
@Override
public String makeNoise() {
return "whistle";
}
@Override
public float getMaxInAirDistanceKm() {
return 50;
}
}
Catalog.java
import java.util.ArrayList;
import java.util.List;
public class Catalog {
private List<Animal> animals = new ArrayList<>();
public void putAnimal(Animal animal) {
animals.add(animal);
}
public void showList() {
animals.forEach(animal -> {
StringBuilder sb = new StringBuilder();
sb.append(animal.getName() + ": ");
sb.append(animal.makeNoise() + " ");
// this block exemplifies some processing that is
// specific to CanFly animals
if (animal instanceof CanFly) {
sb.append(String.format(" (can stay in air for %s km)",
((CanFly) animal).getMaxInAirDistanceKm()));
}
System.out.println(sb.toString());
});
}
public static void main(String[] args){
Catalog catalog = new Catalog();
Cat cat = new Cat();
BaldEagle baldEagle = new BaldEagle();
catalog.putAnimal(cat);
catalog.putAnimal(baldEagle);
catalog.showList();
}
}
测试输出
Cat: meow
BaldEagle: whistle (can stay in air for 50.0 km)
更新于2019-10-09添加“不正常”案例的示例:
我们可以删除CanFly
接口,并在showList()
方法中,将instanceof
应用到具体实现BaldEagle
上,如下所示:
public void showList() {
animals.forEach(animal -> {
StringBuilder sb = new StringBuilder();
sb.append(animal.getName() + ": ");
sb.append(animal.makeNoise() + " ");
if (animal instanceof BaldEagle) {
sb.append(String.format(" (can stay in air for %s km)",
((BaldEagle) animal).getMaxInAirDistanceKm()));
}
System.out.println(sb.toString());
});
}
这种方法不可行,因为代码现在依赖于实现而不是接口。例如,它可以防止换出另一个表示Bald Eagle的实现(例如BaldEagleImpl
)
我认为人们认为总会有一种“更清洁”的解决方案来产生您想要的行为。
在您的示例中,我会说,使用Visitor设计模式的过程与不使用instanceOf的情况完全相同:
public interface Animal {
String getName();
String makeNoise();
void accept(AnimalVisitor v);
}
public interface AnimalVisitor() {
void visit(Cat a);
void visit(BaldEagle a);
}
public interface CanFly {
float getMaxInAirDistanceKm();
}
public class Cat implements Animal {
void accept(Visitor v) {
v.visit(this);
}
}
public class BaldEagle implements Animal, CanFly {
void accept(Visitor v) {
v.visit(this);
}
}
public class DisplayVisitor implements AnimalVisitor {
void visit(Cat a) {
//build & display your string
}
void visit(BaldEagle a) {
//build & display your string
}
}
public class Catalog {
private List<Animal> animals = new ArrayList<>();
public void putAnimal(Animal animal) {
animals.add(animal);
}
public void showList() {
DisplayVisitor display = new DisplayVisitor();
animals.forEach(a->a.accept(display));
}
}
尽管我没有完全回答您的问题,但它表明,在大多数情况下,仅通过以OOP方式思考并使用已知模式,无需使用instanceOf
就可以实现相同的行为。
但是,当可以使用
instanceof
时,不再赘述
这不是您问题的直接答案,但是我想说,instanceof
仅在所有其他选择都不可行时才合适。
在接口上使用
instanceof
可以;在实现上使用instanceof
不好
我将其重新表述为“在接口上使用instanceof
的情况要比在实现中使用instanceof
的情况要差”,但这只是强耦合不好的一般规则的推论。通常有更好的选择。
[当您想使用instanceof
时,您应该考虑首先引入其他接口或接口方法或使用访问者模式(请参阅other answer)。所有这些选项都是在Java中实现所需行为的更简洁的方法。
这并不总是很优雅,可能需要人为的接口或导致接口过大,这就是为什么其他一些语言支持即席联合类型和代数数据类型的原因。但是instanceof
也不是一种很好的模拟方法,因为Java的类型系统无法帮助您确保正在处理所有可能的选项。
首先,重要的是要注意,面向对象的编程范式是抵制类型检查(例如instanceof
)的根源。其他范例不一定共享这种阻力,甚至可能鼓励类型检查。因此,这个问题仅在您尝试进行OOP时才有意义。
如果您尝试进行OOP,则应该尽可能多地利用多态性。多态是OOP的主要武器。类型检查是多态性的对立面。
肯定地,抽象的类型检查比具体实现的类型检查要好;但这只是在重述依赖倒置原则(取决于抽象,而不是具体概念)。
在OOP中,类型检查的每种用法都可以看作是错过了多态性的机会。