繁体   English   中英

在java中正确使用的实例

[英]Instanceof correct usage in java

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

例如,我想打印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));
        }
    }
}

还是存在更好的方法?

您可以添加一个方法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);

正如通常不赞成的许多语言特征一样,通常会通过实施某种“伪造”它们的机制来欺骗使用它们。 但这并没有让事情变得更好。 如果你的意思是测试一个对象的类型,然后instanceof只是使用了正确的事情。 如果你想避免它,你需要找到一种方法, 不需要你找出一个对象的(动态)类型。

避免运行时类型检查的一种流行模式是使用访问者模式,其有效地具有重载分辨率并且动态调度机制执行脏工作。 如果您的真实问题与您发布的博物馆示例一样简单,那么这显然是过度的。 无论如何,这是如何做到的。

为了这个例子,假设我们有PaintingDrawings ,都是从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方法,但我们仍然必须在派生类中重写它,以根据this指针的类型工作进行重载解析。在大多数情况下,这会增加更多的混淆(和潜在的错误),而不是好处。)

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)

一种可能的替代方法是在所有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));
        }
    }
}

另一种可能是保持独立的Collection所有的Painting ,除了对象artObjects集合。

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

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

class ArtObject {

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

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

typedef enum typeOfArt { generic, painting, engraving };

class ArtObject {typeOfArt type = TypeOfArt.generic; }

根据模型的复杂性,如果switch(类型)引起的混乱不是太大,您可能会完全避免创建子类。

如果我们这样做会怎么样?

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

暂无
暂无

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

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