【问题标题】:Instanceof correct usage in javajava中Instanceof的正确用法
【发布时间】:2014-09-30 18:46:57
【问题描述】:

Instance of 在 java 中被认为是一种不好的做法。但我不知道在这种情况下如何避免它。 假设我有一个抽象类ArtObject,还有一些子类,例如PaintingEngraving 等。我有一个类Museum,其中包含ArrayListArtObjects。

例如,我想打印Museum 中所有Painting 对象的信息。

我应该这样做吗?:

public void printAllPaintingsInfo() {
    for (int i = 0; i < artObjects.size(); i++) {
        if (artObjects.get(i) instanceof Painting) {
            System.out.println(artObjects.get(i));
        }
    }
}

还是有更好的方法?

【问题讨论】:

  • 什么是 artObjects?List ,array 还是 Iterable 的一些实例?
  • @KumarAbhinav:为什么在使用 instanceOf 时这很重要?
  • 你可以通过双重调度来解决它(例如使用 visitor 模式)。但对于这种情况,这可能是矫枉过正。
  • 谢谢大家的回答,我学到了很多有趣的方法来解决这个问题。但我认为在这种情况下最有效的是使用 instanceof :)

标签: java oop instanceof


【解决方案1】:

您可以将方法isPainting() 添加到ArtObject,默认为return false;,并在绘画中覆盖return true;

那你就可以写了

public void printAllPaintingsInfo() {
    for (ArtObject ao : artObjects)
        if (ao.isPainting())
            System.out.println(ao);
}

或者在 Java 8 中你可以编写

artObjects.filter(ArtObject::isPainting).forEach(System.out::println);

【讨论】:

  • 这是一个很好的解决方案,如果我们不希望对每个子类进行此类比较。否则ArtObject 将被isPaintingisSculptureisEngravingisModernPainting 等方法污染。
  • 您可以使用返回类型枚举的单个方法来完成。无论如何,这依赖于能够修改 ArtObject 类。
【解决方案2】:

与许多通常不受欢迎的语言功能一样,通过实施一种以某种方式“伪造”它们的机制来欺骗使用它们通常很诱人。但这并没有使任何事情变得更好。如果您意思是测试对象的类型,那么instanceof 是正确的选择。如果你想避免它,你需要找到一种方法,而不是要求你找出对象的(动态)类型。

避免运行时类型检查的一种流行模式是使用 visitor 模式,该模式有效地使重载解决方案和动态调度机制完成了肮脏的工作。如果您的实际问题与您发布的博物馆示例一样简单,那么这显然是矫枉过正。无论如何,这是可以做到的。

为了这个例子,假设我们有Paintings 和Drawings,它们都派生自ArtObject。我们定义了一个抽象的访问者类型,它对每个ArtObject 都有一个方法。或者,您可以只专注于少数几个,并有一个备用的“访问任何东西”方法。

public abstract class ArtVisitor {

    public void visit(final Painting painting) {
    }

    public void visit(final Drawing drawing) {
    }

    // public void visit(final ArtObject anything) {
    // }
}

我使用了抽象类而不是接口,因此我可以使用 visit 方法的空默认实现。

ArtObject 接口(或抽象类,就此而言)中,我们需要添加一个抽象takeVisitor 方法。

public abstract class ArtObject {

    private final String name;

    protected ArtObject(final String name) {
        this.name = name;
    }

    public abstract void takeVisitor(ArtVisitor visitor);

    @Override
    public final String toString() {
        return String.format("%s (%s)",
                             this.name,
                             this.getClass().getCanonicalName());
    }
}

不幸的是,我们无法实现 ArtObject.takeVisitor,因为this 指针将具有静态类型ArtVisitor,这需要我们进行类型自省,而这正是我们想要避免的。相反,我们必须——这是访问者模式最丑陋的地方——在我们的每个类中重写它。

(如果我们添加了备用ArtVisitor.visit(ArtObject) 方法,我们可以实现一个通用的takeVisitor 方法,但我们仍然必须在派生类中重写它以根据@987654336 的类型进行重载决策@ 指针起作用。在大多数情况下,这会增加更多的混乱(和潜在的错误)而不是好处。)

public final class Painting extends ArtObject {

    public Painting(final String name) {
        super(name);
    }

    @Override
    public void takeVisitor(final ArtVisitor visitor) {
        visitor.visit(this);
    }

    public void aMethodOnlyPaintingsHave() {
        // whatever...
    }
}
public final class Drawing extends ArtObject {

    public Drawing(final String name) {
        super(name);
    }

    @Override
    public void takeVisitor(final ArtVisitor visitor) {
        visitor.visit(this);
    }
}

现在我们可以直接建造博物馆了。

import java.util.List;
import java.util.ArrayList;

public final class Museum {

    private final List<ArtObject> artworks;

    public Museum() {
        this.artworks = new ArrayList<ArtObject>();
        artworks.add(new Painting("Mona Lisa"));
        artworks.add(new Drawing("Madame Palmyre with Her Dog"));
        artworks.add(new Painting("The Night Watch"));
    }

    public void printAllWorks() {
        for (final ArtObject work : this.artworks) {
            System.out.println(work);
        }
    }

    public void printAllPaintings() {
        final ArtVisitor paintingsPrinter = new ArtVisitor() {

            @Override
            public void visit(final Painting painting) {
                System.out.println(painting);
                // Note: We don't need any unsafe downcast here!
                painting.aMethodOnlyPaintingsHave();
            }
        };
        for (final ArtObject work : this.artworks) {
            work.takeVisitor(paintingsPrinter);
        }
    }

    public static void main(final String[] args) {
        final Museum myMuseum = new Museum();
        System.out.println("All ArtObjects:\n");
        myMuseum.printAllWorks();
        System.out.println("\n\nAll Paintings:\n");
        myMuseum.printAllPaintings();
    }
}

上述程序的输出为:

All ArtObjects:

Mona Lisa (Painting)
Madame Palmyre with Her Dog (Drawing)
The Night Watch (Painting)


All Paintings:

Mona Lisa (Painting)
The Night Watch (Painting)

【讨论】:

    【解决方案3】:

    一种可能的替代方法是在所有 ArtObjects 中保留一个类型

    // Pseudocode
    class ArtObject {
      Enum artType = { Painting, Engraving }
    }
    
    class Painting implements ArtObject {
      Painting() { artType = Painting }
    }
    
    public void printAllPaintingsInfo() {
        for(int i = 0; i < artObjects.size(); i++) {
            if(artObjects.artType == Painting) {
                System.out.println(artObjects.get(i));
            }
        }
    }
    

    另一种方法可能是为除了 artObjects 集合之外的所有 Painting 对象保留一个单独的 Collection

    【讨论】:

    • 这为每个对象添加了一个字段,这似乎效率低下。如果你有一个 Painting 的子类型,它是 PortraitPainting
    • 我认为将isPainting 添加到ArtObject 是相当低效的。
    • 它不知何故错过了为什么应该首先避免instanceof。当我们没有继承时,类型字段在 C 中很有用。在 Java 中,类型信息已经存在于类中。为什么要复制它?
    • @PeterLawrey 我们不知道对 OP 的期望是什么。我只是提供了几个使用 instanceof 的替代方案。
    • @5gon12eder 是的。我没有从整体上看这个问题,而是回答了前几句话:“Instance of 在java中被认为是一种不好的做法。但我不知道在这种情况下如何避免它。”
    【解决方案4】:

    我认为使用 instance of 没有任何问题。但是,如果您仍然不想使用它,您可以创建一个指示 ArtObject 类型的方法(例如 Type.PAINTING、Type.ENGRAVING)。

    【讨论】:

      【解决方案5】:

      我通常在基类中定义谓词,或者添加一个显式的子类名称作为 一个实例变量。

      class ArtObject {
      

      boolean isPainting() { return(false): } boolean isEngraving() { return(false); } }

      然后在子类中,将这些方法定义为 true 或者

      typedef enum typeOfArt { generic, painting, engraving };
      

      类 ArtObject { typeOfArt type = TypeOfArt.generic; }

      根据模型的复杂性,您可能会避免创建子类 完全是因为 switch(type) 造成的混乱不是太大。

      【讨论】:

        【解决方案6】:

        如果我们这样做会怎样?

        public abstract class ArtObject{
        
          //Classes extending this class will need to override and implement this method
          abstract public Type getType();
        
        }
        
        
        public class Painting extends ArtObject {
            /*
              Other code probably
            */
        
            @Override
            public Type getType(){
               return Type.Painting;
            }
        }
        
        enum Type {
           Painting, Museum;
        }
        
        public void printAllPaintingsInfo() {
            for (int i = 0; i < artObjects.size(); i++) {
                if (artObjects.get(i).getType() == Type.Painting) {
                    System.out.println(artObjects.get(i));
                }
            }
        }
        

        【讨论】:

        • 这意味着您必须维护一个额外的enum 用于打字。这有什么好处?
        • 你也可以取消它,而是返回一个字符串或其他东西。
        • 无论如何,他必须与某事或其他人相匹配。保持枚举可能是有利和干净的。这只是我的看法。
        猜你喜欢
        • 1970-01-01
        • 2012-07-24
        • 1970-01-01
        • 1970-01-01
        • 2016-03-18
        • 2016-04-02
        • 1970-01-01
        • 2011-11-23
        • 2011-09-03
        相关资源
        最近更新 更多