Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast an Iterator of a class to an Iterator of a subclass thereof?

I tried to cast an Iterator of a class to an iterator of a subclass of said class. This gave me an "inconvertible types" error. Why is this not possible and what is the most elegant way to work around it? (Or alternatively, why is it a bad idea if it is?)

Using a for-each loop is not a solution in this case: I'm trying to implement iterator() and the easiest way to do this would be to return the iterator() of one of my class' fields, but that one doesn't have the exact required type. I can't change the signature of my iterator()either.

public interface SomeoneElsesInterface {
    public Iterator<SomeoneElsesInterface> iterator();
}

public abstract class MyAbstractClass implements SomeoneElsesInterface {
    final MyAbstractClass[] things;
    public MyAbstractClass(SomeoneElsesInterface... things) {
         this.things = (MyAbstractClass[]) things;
    }
}

public class MyClass extends MyAbstractClass {
    public MyClass(MyAbstractClass thing1, MyAbstractClass thing2) {
        super(thing1, thing2);
    }

    public Iterator<SomeoneElsesInterface>() {
        return (Iterator<SomeoneElsesInterface>) Arrays.asList(things).iterator();
    }
}

I could, of course, just change the type of things. However, I would need a lot of casts in other places in that case. I do know that my constructor won't be called with objects that are not MyAbstractClasss but I cannot change the interface anyway.

like image 956
Erik Avatar asked Jul 15 '12 17:07

Erik


2 Answers

This looks to be as simple as using explicit type argument specification:

public class MyClass extends MyAbstractClass {
    // ...

    public Iterator<SomeoneElsesInterface> iterator() {
      return Arrays.<SomeoneElsesInterface>asList(things).iterator();
    }
}

The problem is that Arrays#asList() is inferring that you want a list of type List<MyAbstractClass>, which will yield an iterator of type Iterator<MyAbstractClass>. Since Iterator's type parameter is not covariant, you cannot supply an Iterator<MyAbstractClass> where an Iterator<SomeoneElsesInterface> is required. By forcing Arrays#asList() to create a list of type List<SomeoneElsesInterface>, as shown above, you also wind up with the intended iterator type coming back from your call to Iterable#iterator().

The author of SomeoneElsesInterface would have been kinder to specify the return type of its iterator() method as Iterator<? extends SomeoneElsesInterface>.

like image 115
seh Avatar answered Oct 23 '22 13:10

seh


I think from your question you're trying to do something like this:

Iterator<Object> original = ...
Iterator<String> converted = (Iterator<String>)original;

Is that correct?

If so, that is, unfortunately, impossible. The problem is that original can contain objects that are not Strings, so allowing that cast would break the generics contract, i.e. converted could contain something that is not a String.

I don't think there is an elegant workaround for this.

You say the easiest way to implement iterator() is to return an instance field's iterator, so I'm guessing you have something like this:

class IterableThing implements Iterable<Foo> {
    private Collection<Bar> someStuff;

    public Iterator<Foo> iterator() {
        return (Iterator<Foo>)someStuff.iterator();
    }
}

class Bar {
}
class Foo extends Bar {
}

If someStuff can be guaranteed to contain only instances of Foo, then can you declare someStuff to be a Collection<Foo> rather than a Collection<Bar>? If not, then it doesn't really make sense to just return someStuff's iterator because it might contain something that is not a Foo.

I guess you need to think about what guarantees you can actually make. If you can't guarantee that someStuff only contains Foos then you will probably have to maintain your own state, or filter the contents of someStuff on demand.

EDIT: You've updated your question with code. Awesome.

So it looks like you're actually trying to return an iterator over the superclass of the type. That makes things a lot easier.

In your particular case, you can probably solve it with this:

return Arrays.<SomeoneElsesInterface>asList(things).iterator();

It'll generate some warnings, but that's OK because you know that you've guaranteed type safety.

like image 38
Cameron Skinner Avatar answered Oct 23 '22 13:10

Cameron Skinner