Jarmund
Jarmund

Reputation: 3205

Java, How does one re-cast an object based on one of its interfaces?

I have a set of classes that implements interfaces Paintable and Tickable. Another class, World, keeps track of some of these classes by shoving them into an ArrayList<Tickable>.

I have a different class that works on all objects that are Paintable, and a sure way to get all of these objects is to ask world for this with a getPaintables() method, as all objects held by World are both tickable and paintable.

The probles arises from the from when I try to cast Tickable objects as Paintable ones like this:

    for (Paintable p : (ArrayList<Paintable>)world.getPaintables()) {
        // Do stuff
    }

This results in:

    error: incompatible types: ArrayList<Tickable> cannot be converted to ArrayList<Paintable>.

Is it possible to work around this by casting it some other way? I could, ofcourse duplicate the ArrayList in World, one as Paintable, and the other one as Tickable, but this seems cumbersome to me.

I hope that makes sense.

Upvotes: 1

Views: 62

Answers (5)

Mick Mnemonic
Mick Mnemonic

Reputation: 7956

One alternative that doesn't require changing the class / interface hierarchies, is to declare the List container in your World class with a bounded type parameter having multiple bounds. For example:

class World<T extends Object & Paintable & Tickable> {

    private List<T> tickablePaintables = new ArrayList<>();

    /*...*/

    public List<T> getTickablePaintables() {
        return tickablePaintables;
    }
    /*...*/       
}

Then in the client code, you could iterate through the tickable-paintable list returned by World without casting:

public <T extends Object & Paintable & Tickable> void doSomething() {

    World<T> w = new World<>();

    for (Tickable tickable : w.getTickablePaintables()) {
        //...
    }            
    for (Paintable paintable : w.getTickablePaintables()) {
        //...
    }            
}

Due to the way type parameters work in Java, this will unfortunately add some clutter to the class/method declarations (for declaring T).

Upvotes: 1

callOfCode
callOfCode

Reputation: 1026

You don't need typecast at all. Implement Iterable<Paintable> and Iterable<Tickable> on World and then you can simply do either for(Paintable p : world) or for(Tickable t : world) Both will work without typecasting.

Upvotes: 1

McValls
McValls

Reputation: 46

What datatype does return world.getPaintables()? If the return of that method is an ArrayList of Paintable, you should not cast anything. Anyway, you could do something like:

for(Object p : world.getPaintables()) {
    if(p instanceOf Paintable){
       (Paintable)p ... (do something);
    }
}

Hope it helps you.

Upvotes: 0

markspace
markspace

Reputation: 11020

I think the easiest way to "fix" this is just to create a common interface and use that for all your lists. If all your classes implement both Tickable and Paintable as you say, you probably should be doing it this way anyway.

public class TickAndPaint
{
   public static void main(String[] args) {
      ArrayList<TickableAndPaintable> list = new ArrayList<>();
      for( Paintable t : list ) {
      }
      for( Tickable t : list ) {
      }
   }
}

interface Paintable {}
interface Tickable {}
interface TickableAndPaintable extends Paintable, Tickable {}

Upvotes: 1

Michael
Michael

Reputation: 815

I don't get your question entirely, but I'll give it a try.

If world.getPaintables() returns List it's clear that your code does not work.

If world.getPaintables() holds both, the tickable and paintable objects one could do something like Louis said:

for (Object p : (ArrayList<Object>)world.getPaintables()) {
    if (p instanceof Paintable)
        paint();
    else if  (p instanceof Tickable)
        tick();
}

Upvotes: 0

Related Questions