Shade
Shade

Reputation: 85

How can Iterator<SubClass> be cast to Iterator<SuperClass>?

If SubClass extends SuperClass, Iterator<SubClass> cannot be cast to Iterator<SuperClass>. However, I have the situation that the Interfaces demandIterator<SuperClass and supply Collection<SubClass> and cannot be changed.

I've seen answers regarding lists, because you can't add a SuperClass-Object to a list of SubClass-Objects, but shouldn't it be safe for iterators, since you're only reading? Is there a way to cast this?

public Iterator<SuperClass> list(String keyword) {
    Collection<SubClass> result = service.searchByKeyword(keyword);
    return result.iterator();
}

Upvotes: 1

Views: 111

Answers (4)

Louis Wasserman
Louis Wasserman

Reputation: 198391

There is a solution that doesn't involve any streaming, unsafe casts, or custom iterators, but it does block your ability to call remove.

That is, instead of changing the Iterator, changing the Collection:

Collection<SuperClass> superCollection = Collections.unmodifiableCollection(result);

This is a perfectly safe, built in way to get from a collection of a subtype to one of a supertype, which can then give you the appropriate iterator.

Upvotes: 3

Vedant Kakade
Vedant Kakade

Reputation: 76

It’s true that while generics in Java are invariant—meaning Iterator<SubClass> cannot be directly cast to Iterator<SuperClass>—your reasoning about the safety of iterators in this context is valid. Iterators are only used for reading, so there's no risk of violating type safety by adding elements of the wrong type. Unfortunately, Java’s type system doesn’t account for this in the way it handles generic types, so a direct cast isn’t allowed. To resolve your issue, you can use a wrapper or adapter pattern to convert the Iterator<SubClass> to an Iterator<SuperClass>. A simple way to do this is by using Java’s Stream API or by creating a custom iterator that wraps the original one and performs the type conversion. Here’s an example:

public Iterator<SuperClass> list(String keyword) {
    Collection<SubClass> result = service.searchByKeyword(keyword);
    return result.stream().map(subClass -> (SuperClass) subClass).iterator();
}

This approach uses a stream to map each SubClass element to its SuperClass equivalent and returns the iterator over this transformed collection. It's safe because all SubClass objects are inherently SuperClass objects. This way, you comply with the type requirements of Iterator<SuperClass> while safely handling the original Collection<SubClass>.

Upvotes: 2

You can return upcast(result.iterator()); where upcast is helper method defined as

@SuppressWarnings("unchecked")
<SUPER,SUB extends SUPER> Iterator<SUPER> upcast(Iterator<SUB> subIterator) {
    return (Iterator<SUPER>) subIterator;
}

Using such method ensures that you can apply cast

  1. only to Iterators and not to other classes (for which it is potentially unsafe)
  2. only to element classes in hierarchy (not for example Subclass and String)

Upvotes: 0

k314159
k314159

Reputation: 11246

I would recommend @khelwood's suggestion to just cast to (Iterator). However, if you don't like to see the warnings about usage of raw types, and you don't like suppressing warnings, then you can create an iterator of the required SuperClass type which forwards its methods to the SubClass iterator:

public Iterator<SuperClass> list(String keyword) {
    Collection<SubClass> result = service.searchByKeyword(keyword);

    return new Iterator<>() {
        final Iterator<SubClass> it = result.iterator();

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public SuperClass next() {
            // it.next() is a SubClass, therefore also a SuperClass
            return it.next();
        }
    };
}

It's probably overkill, but it's one way of making the compiler happy.

Upvotes: 4

Related Questions