简体   繁体   中英

Interfaces and generics in Java

I have the code:

Set<? extends Notifiable> notifiables;

Notifiable is an interface. I don't understand the difference between the above code and:

Set<Notifiable> notifiables;

If Notifiable was a class then I'd understand the difference, the first code would allow Notifiable and any subclass of Notifiable whereas the second code would only allow Notifiable (and not any subclasses)

As you can't have an instance of an interface, what can I add/etc to the set? It seems to me there are only two options, either anything that implements Notifiable (in which case how is that different to the first code), or only "instances of Notifiable" which can't exist and so therefore nothing (which is pointless and should throw a compile time error).

A Set<Notifiable> can hold instances of classes that implement Notifiable . It's not limited to holding only instances whose concrete type is Notifiable (and you're right, there's no such thing). But a Set<Notifiable> guarantees that it can hold any kind of Notifiable , since it has an add(Notifiable) method that can accept anything that implements the interface.

Suppose that you have some classes called Foo and Bar which both implement Notifiable . If you create a Set<Foo> — that is, a set that's only allowed to contain instances of Foo and its subtypes — you can't pass it to a method that takes a Set<Notifiable> , because that method may add things that aren't Foo instances, such as Bar .

public void addABar(final Set<Notifiable> notifiables) {
    notifiables.add(new Bar());  // OK, since Bar is a subtype of Notifiable
}

public void wontWork() {
  final Set<Foo> foos = new HashSet<>();
  addABar(foos);  // Compile error, can't convert Set<Foo> to Set<Notifiable>
}

But sometimes you want to write a method that can accept things like Set<Foo> and Set<Bar> in addition to Set<Notifiable> . That's where the wildcard comes in. A Set<? extends Notifiable> Set<? extends Notifiable> guarantees that everything in it is some kind of Notifiable , but it doesn't guarantee that every kind of Notifiable can be added to it; it's allowed to be limited to a subtype. You can't call add() on it, because that method is now add(? extends Notifiable) instead of add(Notifiable) and you can't call a method whose argument type is unknown.

You'd typically use this when you don't need to add elements, but you do need to look at the existing elements and call Notifiable interface methods on them, and you want to allow the the caller to pass sets of subtypes such as Set<Foo> .

For example:

public void notifyAll(final Set<? extends Notifiable> notifiables) {
    for (final Notifiable notifiable : notifiables) {
        notifiable.notify();
    }
}

public void example() {
    final Set<Foo> foos = whatever();
    notifyAll(foos);  // OK, since a Set<Foo> is a Set<? extends Notifiable>
}

If notifyAll() took a Set<Notifiable> , you wouldn't be able to pass foos to it.

Let's use a more straightforward example:

Set<? extends Serializable> serializables;

This one declares a variable which can hold a reference to a Set of Integer s, Float s and so on:

 serializables = new HashSet<Serializable>(); // valid
 serializables = new HashSet<Number>(); // this is valid as well
 serializables = new HashSet<Integer>(); // valid

This one on the other hand:

Set<Serializable> serializables;

can only hold a Set of Serializable objects however:

serializables = new HashSet<Serializable>();
serializables = new TreeSet<Serializable>();

so this one will be a compiler error:

List<Serializable> numbers = new ArrayList<Integer>();

Corollary:

If you want a field which can hold any subtype of Notifiable then use:

Set<Notifiable> notifiables = new HashSet<Notifiable>();

if you want to restrict the subtype of Notifiable which can be used then this is the way to go:

Set<? extends Notifiable> notifiables = new HashSet<MyNotifiable>();

Addendum:

This is perfectly legal so you can retrofit your Set later as you see fit:

Set<? extends Notifiable> notifiables = new HashSet<NotifiableA>();
notifiables  = new HashSet<NotifiableB>(); 

For one-level of generics in Collection types, the role of the ? extends ? extends wildcard is not very significant because all Collection methods look like

E get(int index);
boolean add(E e);

which are effectively the same as:

? extends E get(int index);
boolean add(? extends E e);

due to Java sub-type polymorphism rules.

However, the wildcard is significant when the sub-type rules come into play:

    List<Integer> list = Arrays.asList(1);
    List<? extends Number> numSubTypeList = list; // Works
    List<Number> numList = list; // Illegal

Similarly for Multi-level generics:

    Collection<Collection<Number>> col1 = new ArrayList<Collection<Number>>();
    col1.add(list); // Illegal

    Collection<Collection<? extends Number>> col2 = new ArrayList<Collection<? extends Number>>();
    col2.add(list); // Works

However, even col2 's signature is not suitable for generic libraries. Those will probably expect Collection<? extends Collection<? extends Number>> Collection<? extends Collection<? extends Number>> Collection<? extends Collection<? extends Number>> , which col2 is sub-type.

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