Java 对正的'instanceof`?将其与接口而不是实现类型一起使用
当一个代码包含JavaJava 对正的'instanceof`?将其与接口而不是实现类型一起使用,java,design-patterns,instanceof,Java,Design Patterns,Instanceof,当一个代码包含Javainstanceof操作符时,许多人会扬起眉毛说这是一个否定词。例如,在这个例子中,答案是: 请注意,如果必须经常使用该运算符,则通常 提示您的设计存在一些缺陷。所以在一个精心设计的 应用程序您应该尽可能少地使用该运算符 (当然,这条一般规则也有例外) 但是,它没有进一步说明instanceof的使用何时可以,何时不可以 我对此进行了一些思考,并提出了以下指导方针。我想这可能在互联网上的某个地方讨论过,但我找不到。因此,提出这个问题并征求您的意见: 在接口上使用instan
instanceof
操作符时,许多人会扬起眉毛说这是一个否定词。例如,在这个例子中,答案是:
请注意,如果必须经常使用该运算符,则通常
提示您的设计存在一些缺陷。所以在一个精心设计的
应用程序您应该尽可能少地使用该运算符
(当然,这条一般规则也有例外)
但是,它没有进一步说明instanceof
的使用何时可以,何时不可以
我对此进行了一些思考,并提出了以下指导方针。我想这可能在互联网上的某个地方讨论过,但我找不到。因此,提出这个问题并征求您的意见:
在接口上使用instanceof
是可以的;在实现上使用instanceof
是不正确的
下面是一个“OK”案例的例子
示例:一个动物目录,其中一些(但不是全部)可以飞行
Animal.java
public interface Animal {
String getName();
String makeNoise();
}
public interface CanFly {
float getMaxInAirDistanceKm();
}
public class Cat implements Animal {
@Override
public String getName() {
return "Cat";
}
@Override
public String makeNoise() {
return "meow";
}
}
public class BaldEagle implements Animal, CanFly {
@Override
public String getName() {
return "BaldEagle";
}
@Override
public String makeNoise() {
return "whistle";
}
@Override
public float getMaxInAirDistanceKm() {
return 50;
}
}
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();
}
}
CanFly.java
public interface Animal {
String getName();
String makeNoise();
}
public interface CanFly {
float getMaxInAirDistanceKm();
}
public class Cat implements Animal {
@Override
public String getName() {
return "Cat";
}
@Override
public String makeNoise() {
return "meow";
}
}
public class BaldEagle implements Animal, CanFly {
@Override
public String getName() {
return "BaldEagle";
}
@Override
public String makeNoise() {
return "whistle";
}
@Override
public float getMaxInAirDistanceKm() {
return 50;
}
}
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.java
public interface Animal {
String getName();
String makeNoise();
}
public interface CanFly {
float getMaxInAirDistanceKm();
}
public class Cat implements Animal {
@Override
public String getName() {
return "Cat";
}
@Override
public String makeNoise() {
return "meow";
}
}
public class BaldEagle implements Animal, CanFly {
@Override
public String getName() {
return "BaldEagle";
}
@Override
public String makeNoise() {
return "whistle";
}
@Override
public float getMaxInAirDistanceKm() {
return 50;
}
}
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();
}
}
BaldEgale.java
public interface Animal {
String getName();
String makeNoise();
}
public interface CanFly {
float getMaxInAirDistanceKm();
}
public class Cat implements Animal {
@Override
public String getName() {
return "Cat";
}
@Override
public String makeNoise() {
return "meow";
}
}
public class BaldEagle implements Animal, CanFly {
@Override
public String getName() {
return "BaldEagle";
}
@Override
public String makeNoise() {
return "whistle";
}
@Override
public float getMaxInAirDistanceKm() {
return 50;
}
}
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();
}
}
Catalog.java
public interface Animal {
String getName();
String makeNoise();
}
public interface CanFly {
float getMaxInAirDistanceKm();
}
public class Cat implements Animal {
@Override
public String getName() {
return "Cat";
}
@Override
public String makeNoise() {
return "meow";
}
}
public class BaldEagle implements Animal, CanFly {
@Override
public String getName() {
return "BaldEagle";
}
@Override
public String makeNoise() {
return "whistle";
}
@Override
public float getMaxInAirDistanceKm() {
return 50;
}
}
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();
}
}
更新2019-10-09添加“不正常”案例示例:
我们可以放弃CanFly
接口,在showList()
方法中,我们在具体实现BaldEagle
上应用instanceof
——如下所示:
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());
});
}
这种方法不行,因为代码现在依赖于实现,而不是接口。例如,它可以防止替换另一个代表BaldEagle的实现(例如,
BaldEagleImpl
)我认为人们认为总是有一个“更干净”的解决方案来产生你想要的行为
在您的示例中,我想说,在不使用instanceOf的情况下,Visitor设计模式的使用与此完全相同:
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));
}
}
公共界面动物{
字符串getName();
字符串makeNoise();
无效接受(AnimalVisitor v);
}
公共接口AnimalVisitor(){
无效访问(a类);
无效访问(BaldEagle a);
}
公共接口可以飞{
浮点getMaxInAirDistanceKm();
}
公营猫科动物{
无效接受(访客v){
v、 访问(本);
}
}
公共级秃鹰器动物,能飞{
无效接受(访客v){
v、 访问(本);
}
}
公共类DisplayVisitor实现AnimalVisitor{
无效访问(a类){
//构建并显示您的字符串
}
无效访问(秃顶角a){
//构建并显示您的字符串
}
}
公共类目录{
私有列表动物=新的ArrayList();
公共动物(动物){
动物。添加(动物);
}
公开作废展示清单(){
DisplayVisitor display=新的DisplayVisitor();
动物。forEach(a->a.accept(display));
}
}
虽然我没有完全回答你的问题,但它表明,在大多数情况下,只要以面向对象的方式思考并使用已知的模式,不使用instanceOf
,就可以完成相同的行为
但是,它没有进一步说明何时可以使用instanceof
这不是对你问题的直接回答,但我想说的是,instanceof
仅在所有其他选项都不可行时才适用
在接口上使用instanceof
是可以的;在实现上使用instanceof
是不正确的
我想将其重新表述为“在接口上使用instanceof
比在实现上使用instanceof
更糟糕”,但这只是强耦合是坏的一般规则的推论。通常有更好的选择
当您想要使用instanceof
时,您应该考虑引入额外的接口或接口方法,或者首先使用访问者模式(请参阅)。所有这些选项都是在Java中实现所需行为的更简洁的方法
这并不总是优雅的,可能需要人工接口或导致接口膨胀,这就是为什么其他一些语言支持特殊的联合类型和代数数据类型。但是
instanceof
也不是一种很好的模拟方式,因为Java的类型系统不能帮助您确保处理所有可能的选项。首先,需要注意的是,面向对象编程范式是抵制类型检查的根源,例如instanceof
。其他范例不一定有这种阻力,甚至可能鼓励类型检查。所以这个问题只有在你尝试OOP的时候才有意义
如果您正在尝试OOP,那么您应该尽可能多地利用多态性。多态性是OOP的主要武器。类型检查是多态性的对立面
当然,抽象的类型检查比具体实现的类型检查更可取;但这只是重申依赖倒置原则(依赖于抽象,而不是具体)
在OOP中,类型检查的每一次使用都会被视为错失了多态性的机会。你为什么要把这个答案作为一个新问题而不是回答你链接到的原始问题呢?@Elias——我觉得另一个问题是如何使用它(即确保用法在语法上是正确的)虽然我的问题更多的是关于设计的问题(对于所有语法正确的用例,什么是合理的用法,什么不是),但很难(也可能是错误的)有一个通用规则来说明它是否“正常”
instanceof
是绝对必要的一个例子始终是元编程。当您必须处理甚至可能在运行时加载的动态类型系统时,您必须进行一些类型调查。比如说