I don't get the actual practical use case of the wildcard character ?
in Java, when using it with extends
or super
.
The official Oracle Java documentation shows an example of adding numbers to a list and sum them up:
private static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number number : list) {
sum = number.doubleValue();
}
return sum;
}
From my understanding List<? extends Number> list
does not mean "a list of instances of Number objects and objects of possible sub-classes of Number" but instead "a list of objects of a single type, which have to be or extend the type Number". That's all fine.
Until I tried this code:
List<Number> numberList = new ArrayList<Number>();
numberList.add(Integer.valueOf(123));
numberList.add(Double.valueOf(2.34));
numberList.add(Float.valueOf(3.45f));
The instances can be added to the Number list through an implicit upcast to number references. And that list of numbers can then be used with the sumOfList method as shown above. But the elements would not be of a single type. So why would anyone use extends
with ?
anyways?
The other thing that I am not getting is the use of the wildcard character with super. It is fine, that you can define the lower bound of a type. The offical Oracle documentation shows this example:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i ) {
list.add(i);
}
}
Thing is, this works great for a simple thing like adding numbers to a list. But the code shown also works with lists like this:
List<Object> list = new ArrayList<Object>();
list.add(Integer.valueOf(123));
list.add(Double.valueOf(2.34));
list.add(new Person("John", "Doe"));
So as long as one would call methods, that would do anything useful with actual numbers like - I dunno - arithmetical operations the code would probably work fine. But when it comes to the John Doe entry, code meant for numbers would fail.
So can anyone clarify the actual practical use of the extends
and super
keywords in combination with the ?
operator?
CodePudding user response:
So why would anyone use
extends
with?
anyways?
Because you may not have a List<Number>
to pass to it. For example, you can pass a List<Integer>
, a List<BigInteger>
etc to sumOfList
.
If you hadn't declared it as accepting a List<? extends Number>
, but instead as accepting a List<Number>
, you could only pass a List<Number>
to it - List<Integer>
isn't a subtype of List<Number>
.
In terms of the addNumbers
method: you simply need some kind of List
to which it is safe to add instances of Integer
(or subtypes [of which there are none, because it is final], or null
).
That's all List<? super Integer>
means: it's safe to add instances of Integer
to that.
It's safe to pass a List<Object>
to that method, because all Integer
s are Object
s, so it's safe to add Integer
s to that list.
Again, if you'd declared the method as accepting a List<Object>
(or a List<Integer>
), that would be all you could pass to it: a List<Object>
(or a List<Integer>
, respectively). You couldn't pass it, say, a List<Number>
.
So can anyone clarify the actual practical use of the
extends
andsuper
keywords in combination with the?
operator?
The practical use of wildcards is to increase the flexibility of API methods, as illustrated above: it allows you to declare a single method that takes a range of related types as inputs.
You can also use wildcards in outputs, but this should be avoided, because the wildcard ends up in the type of the value at the call site, and just generally hangs around looking messy and confusing.
CodePudding user response:
1. So why would anyone use extends with ? anyways?
We can't perform write or delete operations when wildcards are used as Unbounded like "List<?>", because the compiler can't know what type it is, like:
// not allowed
public static void displayList(List<?> list) {
list.add(1);
}
// allowed
public static void displayList(List<? super Integer> list) {
list.add(1);
}
2. So can anyone clarify the actual practical use of the extends and super keywords in combination with the ? operator?
It is a good combination of using some design patterns and principles, the most simple one that comes to my mind is using it with the Interface Segregation Principle.
interface Worker {
void work();
}
interface Sleeping {
void sleep();
}
class Human implements Worker, Sleeping {
@Override
public void work() {}
@Override
public void sleep() {}
}
class Robot implements Worker {
@Override
public void work() {}
}
public static void main(String[] args) {
Human human1 = new Human();
Human human2 = new Human();
List<Human> humans = List.of(human1, human2);
displayIfSleeping(humans);
Robot robot1 = new Robot();
Robot robot2 = new Robot();
List<Robot> robots = List.of(robot1, robot2);
displayIfSleeping(robots); // error
}
public static void displayIfSleeping(List<? extends Sleeping> workers) {
// display only workers who are sleeping
}
That is a good way of explicit showing that if you have to iterate through workers, you only want them to be the ones that can sleep.