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).
Java Generic Interface In similar way, we can create generic interfaces in java. We can also have multiple type parameters as in Map interface. Again we can provide parameterized value to a parameterized type also, for example new HashMap<String, List<String>>(); is valid.
Generic interfaces can inherit from non-generic interfaces if the generic interface is covariant, which means it only uses its type parameter as a return value.
A generic interface is primarily a normal interface like any other. It can be used to declare a variable but assigned the appropriate class. It can be returned from a method. It can be passed as argument.
In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs.
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>
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>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With