Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't the java Iterable interface take a generics wildcard? Or: why can't I an overriding iterator() method return an Iterator for a subclass?

I have some classes: SearchResponse, SearchResponseHit, SpecialSearchResponse (extends SearchResponse) and SpecialSearchResponseHit (extends SearchResponseHit).

SearchResponse look something like this:

public class SearchResponse implements Iterable<SearchResponseHit> {

    [...]

    public Iterator<SearchResponseHit> iterator() {
        return searchResponseHits.iterator();
    }
}

This makes it possible for me to use an instance of SearchResponse in a foreach loop, like this:

for (SearchResponseHit hit : mySearchResponse) {
    [...]
}

Now, what I want to do, but can't find out how, is to make this code compile when I have an instance of SpecialSearchResponse:

for (SpecialSearchResponseHit specialHit : mySpecialSearchResponse) {
    [...]
}

This gives me the following compiler error:

Type mismatch: cannot convert from element type SearchResponseHit to SpecialSearchResponseHit

If I try to add this code to SpecialSearchResponse:

public Iterator<SpecialSearchResponseHit> iterator() {
    [...]
}

...I get the error:

The return type is incompatible with Iterable<SearchResponseHit>.iterator()

I have tried changing the method in SearchResponse to:

    public Iterator<? extends SearchResponseHit> iterator() {
        return searchResponseHits.iterator();
    }

...but this gives me the error:

The return type is incompatible with Iterable<SearchResponseHit>.iterator()

Then I tried changing the class definition to:

public class SearchResponse implements Iterable<? extends SearchResponseHit>

...but this gives me this error:

    The type SearchResponse cannot extend or implement Iterable<? extends SearchResponseHit>. A supertype may not specify any wildcard

What is the best (and prettiest) way to solve this? Or do I have to skip the foreach-method (and other functions that use the Iterable interface behind the scenes) and write a getSpecialIterator() method and then use the iterator directly?

Regards /J

like image 355
user1442411 Avatar asked Dec 26 '22 23:12

user1442411


1 Answers

One way would be to declare the various classes in the following way:

public class SearchResponse<T  extends SearchResponseHit> implements Iterable<T> {
    List<T> searchResponseHits;

    public Iterator<T> iterator() {
        return searchResponseHits.iterator();
    }
}

public class SearchResponseHit {}

public class SpecialSearchResponse extends SearchResponse<SpecialSearchResponseHit> {}
public class SpecialSearchResponseHit extends SearchResponseHit {}

That way you can call them like this:

    SearchResponse<SearchResponseHit> sr = new SearchResponse<SearchResponseHit>();
    for (SearchResponseHit h : sr) {}

    SpecialSearchResponse ssr = new SpecialSearchResponse();
    for (SpecialSearchResponseHit h : ssr) {}

But that introduces generics in the SearchResponse class and you can't simply declare SearchResponse sr = new SearchResponse() any longer (without warnings & casts).


UPDATE
Following your comment, you could alternatively create a common superclass that contains the generics boilerplate - you could make it abstract and package private so the user of your classes does not see it:

abstract class AbstractSearchResponse<T  extends SearchResponseHit> implements Iterable<T>{
    List<T> searchResponseHits;

    public Iterator<T> iterator() {
        return searchResponseHits.iterator();
    }
}

public class SearchResponse extends AbstractSearchResponse<SearchResponseHit> { }
public class SpecialSearchResponse extends AbstractSearchResponse<SpecialSearchResponseHit> {}

Now you can call the 2 children as you wanted:

SearchResponse sr = new SearchResponse();
for (SearchResponseHit h : sr) {}

SpecialSearchResponse ssr = new SpecialSearchResponse();
for (SpecialSearchResponseHit h : ssr) {}
like image 174
assylias Avatar answered Feb 01 '23 23:02

assylias