简体   繁体   English

合理的`instanceof`? 将其与接口而不是实现类型一起使用

[英]Justified `instanceof`? Using it with an interface but not an implementation type

When a code contains the Java instanceof operator, many people will raise their eyebrows and say it is a no-no.当代码中包含 Java instanceof运算符时,许多人会扬起眉毛说这是一个禁忌。 For example, in this other SO Q&A , the answer said:例如,在另一个 SO Q&A中,答案是:

Note that if you have to use that operator very often it is generally a hint that your design has some flaws.请注意,如果您必须经常使用该运算符,则通常暗示您的设计存在一些缺陷。 So in a well designed application you should have to use that operator as little as possible (of course there are exceptions to that general rule).因此,在设计良好的应用程序中,您应该尽可能少地使用该运算符(当然,该一般规则也有例外)。

However, it does not further elaborate when the use of instanceof is okay, and when it is not.但是,它没有进一步详细说明何时使用instanceof可以,何时不可以。

I put some thinking on this, and articlate the following guideline.我对此进行了一些思考,并阐明了以下指导方针。 I thought this may have been discussed somewhere on the Internet, but I could not find it.我以为这可能已经在 Internet 上的某个地方讨论过,但我找不到它。 Hence this question and asking for your comment:因此,这个问题并征求您的意见:

Using instanceof on an interface is okay;在接口上使用instanceof是可以的; using instanceof on an implementation is not okay在实现上使用instanceof是不行的

Here is an example on the "okay" case.这是一个关于“好的”案例的例子。

Example: A catalog of animals, some (but not all) of them can fly示例:动物目录,其中一些(但不是全部)可以飞行

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();
    }
}

Test Output测试 Output

Cat: meow 
BaldEagle: whistle  (can stay in air for 50.0 km)

Updated 2019-10-09 Adding example for the "not-okay" case:更新于 2019-10-09添加“不正常”案例的示例:

We could have dropped the CanFly interface, and in the showList() method, we apply the instanceof on the concrete implementation BaldEagle -- like this:我们可以删除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());
        });
    }

This approach is not okay because the code is now dependent on implementation, not interface.这种方法不行,因为代码现在依赖于实现,而不是接口。 It prevents, for example, swapping out another implementation representing Bald Eagle (eg BaldEagleImpl )例如,它可以防止换出代表 Bald Eagle 的另一个实现(例如BaldEagleImpl

I think people assume that's there is always a "cleaner" solution to produce the kind of behavior you want.我认为人们认为总是有一个“更清洁”的解决方案来产生你想要的那种行为。

In your example, I would say that the use of Visitor design pattern do exactly the same without the using of instanceOf:在您的示例中,我会说访问者设计模式的使用在不使用 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));
    }
}

Although I do not answer completely to your question, it shows that the same behavior can be accomplished without the use of instanceOf in most of cases, just by thinking in OOP way and using known patterns.虽然我没有完全回答您的问题,但它表明,在大多数情况下,无需使用instanceOf即可完成相同的行为,只需以 OOP 的方式思考并使用已知模式即可。

However, it does not further elaborate when the use of instanceof is okay但是,当使用instanceof没问题的时候就不再详细说明了

This is not a direct answer to your question, but I'd say that instanceof is only appropriate when all other options aren't feasible.这不是您问题的直接答案,但我想说instanceof仅在所有其他选项都不可行时才合适。

Using instanceof on an interface is okay;在接口上使用instanceof是可以的; using instanceof on an implementation is not okay在实现上使用instanceof是不行的

I would re-phrase that as "using instanceof on an interface is less bad than using instanceof on an implementation", but that's just a corollary of the general rule that strong coupling is bad.我将重新表述为“在接口上使用instanceof不如在实现上使用instanceof糟糕”,但这只是强耦合不好的一般规则的必然结果。 There are usually better alternatives.通常有更好的选择。

When you want to use instanceof , you should think about introducing additional interfaces or interface methods or using the visitor pattern (see other answer ) first.当您想使用instanceof时,您应该首先考虑引入额外的接口或接口方法或使用访问者模式(参见其他答案)。 All of these options are cleaner ways to achieve the desired behavior in Java.所有这些选项都是在 Java 中实现所需行为的更简洁的方法。

This isn't always elegant and can require artificial interfaces or cause interface bloat, which is why some other languages support ad-hoc union types and algebraic data types.这并不总是优雅的,并且可能需要人工接口或导致接口膨胀,这就是为什么其他一些语言支持临时联合类型和代数数据类型的原因。 But instanceof is not a good way of emulating either, since Java's type system won't help you make sure that you're handling all possible options.但是instanceof也不是模拟的好方法,因为 Java 的类型系统无法帮助您确保处理所有可能的选项。

To start out, it's important to note that the Object-Oriented Programming paradigm is the source of resistance to type checking such as instanceof .首先,重要的是要注意面向对象的编程范式是对诸如instanceof之类的类型检查的阻力的根源。 Other paradigms do not necessarily share this resistance, and may even encourage type checking.其他范例不一定有这种阻力,甚至可能鼓励类型检查。 So this question is really only relevant if you're trying to do OOP.所以这个问题只有在你尝试做 OOP 时才真正有意义。

If you are trying to do OOP, then you should be leveraging polymorphism as much as you can.如果您尝试执行 OOP,那么您应该尽可能多地利用多态性。 Polymorphism is OOP's primary weapon.多态性是 OOP 的主要武器。 Type checking is the antithesis of polymorphism.类型检查是多态性的对立面。

Certainly type checking of abstractions is preferable to type checking of concrete implementations;当然,抽象的类型检查优于具体实现的类型检查; but that's just restating the Dependency Inversion Principle (depend on abstractions, not concretions).但这只是重申依赖倒置原则(依赖于抽象,而不是具体化)。

In OOP, every usage of type checking can be viewed as a missed opportunity for polymorphism.在 OOP 中,每次使用类型检查都可以被视为错失多态性的机会。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM