„…the only benefit I can see to using an upper bounded wildcard vs a bounded generic…“
If your use case only ever calls for you working with one Thing
at a time, then the simple usage scenario you outline is all you'll ever need.
But eventually you'll have a use case where you will need to work with a heterogeneous assortment of Things
. That's when you'll need to pull slightly more advanced polymorphism out from your toolbox.
„…Is there a more important benefit that I'm missing?…“
One super important benefit that it sounds like you're missing is a substitutability relationship between types; called covariance.
For example, since it's legal to do this:
Integer[] intAry = {2,4,6,8};
Number[] numAry = intAry;
Then intuitively, it seems like you should be able to do this:
List<Integer> intList = List.of(8,6,7,5,3,0,9);
List<Number> numList = intList; // but this fails to compile
A wildcard with an upper bound effectively makes collections covariant:
List <? extends Number> numList = intList;
Since Integer
extends Number
:
Thing<Number> numThing = new Thing<>();
Thing<Integer> intThing = new Thing<>();
Then intuitively, it seems like you should be able to do this:
numThing = intThing; // but this fails to compile
A wildcard with an upper bound effectively makes Things
more intuitive:
Thing<? extends Number> numThing = new Thing<>();
numThing = intThing; /* That makes sense! */
Same deal with methods. With this declaration:
public static void use(Thing<Number> oneThing){
/*...*/
}
This would fail to compile:
Thing<Integer> intThing = new Thing<>();
use(intThing); /* error: no suitable method found for use(Thing<Integer>) */
Wild cards with upper bounds makes it possible to use Things
the way you intuitively would think they'd be used:
public static void use(Thing<? extends Number> anyThing){
/* ...*/
}
...
Thing<Integer> intThing = new Thing<>();
use(intThing); /* Perfectly fine! */
„…applies to bounded generics…This method's generic will accept…an upper bounded wildcard vs a bounded generic…“
The things you've incorrectly called „generics“ are actually called either type parameters, type variables or type arguments; depending on the context.