简体   繁体   中英

Instanceof correct usage in java

Instance of is considered to be a bad practice in java. But I don't know how to avoid it in such situation. Let's say I have an abstract class ArtObject and there are some subclasses such as Painting , Engraving , etc. And I have a class Museum which contains an ArrayList of ArtObject s.

For example, I'd like to print information about all Painting objects in the Museum .

Should I do something like this?:

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

Or a better approach exists?

You can add a method isPainting() to ArtObject which is return false; by default and override to return true; in Painting.

Then you can write

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

Or in Java 8 you can write

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

As with many language features that are usually frowned upon, it is often tempting to cheat around using them by implementing a mechanism that somehow “fakes” them. But this doesn't make anything better. If you mean to test the type of an object, then instanceof is just the right thing to use. If you want to avoid it, you'll need to find a way that does not require you to find out the (dynamic) type of an object.

One popular pattern to avoid run-time type inspection is using the visitor pattern which effectively has the overload resolution and dynamic dispatch machinery do the dirty work. If your real-world problem is as simple as the museum example you've posted, then this is clearly overkill. Anyway, here is how it could be done.

For the sake of this example, assume we have Painting s and Drawings , both derived from ArtObject . We define an abstract visitor type that has a method for each ArtObject . Alternatively, you can only specialize for a few and have a fallback “visit anything” method.

public abstract class ArtVisitor {

    public void visit(final Painting painting) {
    }

    public void visit(final Drawing drawing) {
    }

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

I have used an abstract class instead of an interface so I can have empty default implementations of the visit methods.

In the ArtObject interface (or abstract class, for that matter) we need to add an abstract takeVisitor method.

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

Unfortunately, we cannot implement ArtObject.takeVisitor since the this pointer would have static type ArtVisitor , which would require us to do type introspection which is just what we wanted to avoid. Instead, we must – and this is the ugliest thing about the visitor pattern – override it in each of our classes.

(If we had added the fallback ArtVisitor.visit(ArtObject) method, we could have implemented a generic takeVisitor method but we'd still have to override it in our derived classes to make overload resolution based on the type of the this pointer work. In most cases, this would add more confusion (and potential bugs) than benefits.)

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

Now we can build the museum in a straight-forward manner.

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

The output of the above program is:

All ArtObjects:

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


All Paintings:

Mona Lisa (Painting)
The Night Watch (Painting)

One possible alternative is to keep a type in all 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));
        }
    }
}

Another might be to keep a separate Collection for all Painting objects in addition to the artObjects collection.

I don't think there is any problems using instance of. But, if you still don't want to use it, you can create a method indicating the type of ArtObject (eg Type.PAINTING, Type.ENGRAVING).

I usually define predicates in the base class, or add an explicit subclass name as an instance variable.

class ArtObject {

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

then in the subclasses, define these methods as true Alternatively

typedef enum typeOfArt { generic, painting, engraving };

class ArtObject { typeOfArt type = TypeOfArt.generic; }

depending on the complexity of your model, you might avoid creating subclasses entirely if the clutter caused by switch(type) isn't too great.

What if we do something like this?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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