问题
why are these declaration invalid in Java?
List<Number> test = new ArrayList<? extends Number>();
List test = new ArrayList<? extends Number>();
are we not allowed to use wildcard during instantiation. and if the wildcards are only useful for passing them to methods?
and List<Object> test = new ArrayList<Integer>();
is illegal because generics are not covariant correct?
回答1:
The ?
wildcard character means "unknown" not "any". It doesn't make any sense to instantiate a new container of unknown contents, what would you put in there? It can't really be used for anything!
So the declaraion new ArrayList<? extends Number>()
Means "some specific thing that extends number, but I don't know what." It does not mean "anything that extends number."
The List<Number>
you assigned it to would allow both Double and Integer to be added to it, but the actual contents of a List<? extends Number>
might be Float! (or whatever else.)
Consider what would happen in this code if the wildcard worked as an "Any":
List<Integer> listInteger = new ArrayList<Integer>();
listInteger.add(Integer.valueOf(1));
List<? extends Number> listWildCard = listInteger;
listWildCard.add(Double.valueOf(1.0)); //This does not compile
Integer integer = listInteger.get(1);//because this would throw a ClassCastException
Footnote regarding your second example: Declaring a paramaterized type with no type parameter is called using the Raw Type. This is considered a programming error. The syntax is only legal so that code written before java 5 still compiles. Just don't do it if your scenario isn't backward compatability with pre-java 5.
回答2:
To understand why it is not allowed to create objects of wildcard parameterized types, you must first understand what's the use of wildcard parameterized types.
Why wildcards?
As you already know that Java generics are invariant. So a List<Number>
is not a super class of List<Integer>
, even though their type arguments are covariant. So, what if you want such a behaviour in generics too, like having the same reference pointing to different objects? That polymorphic thing, as you would name it. What if you want a single List
reference to refer to list of Integer
, Float
, or Double
?
Wildcards to the rescue:
With wildcards, you can achieve the above mentioned behaviour. So, a List<? extends Number>
can refer to a List<Integer>
, List<Double>
, etc. So, the following declarations are valid:
List<? extends Number> numbers = new ArrayList<Integer>();
numbers = new ArrayList<Double>();
numbers = new ArrayList<Float>();
numbers = new ArrayList<String>(); // This is still not valid (you know why)
So what did we change here? Just the reference type of the numbers
. Note that generics were introduced in Java for enforcing stronger compile time check. So, it's primarily the compiler's job to decide whether the declaration of a parameterized type is conforming to the rule or not. Without wildcard, compiler shows you error for a List<Number>
refering to List<Integer>
.
So, wildcards are just a way to introduce co-variance like behaviour into generics. By using wildcards, you increase the flexibility, or you can say, reduce the restriction that compiler enforces. A List<? extends Number>
reference tells the compiler that the list can refer to a list of Number
or any subtype of Number
(Of course, there are lower bounded wildcards too. But that's not the point here. The differences between them is already discussed in many other answers on SO only).
Major uses of wildcard parameterized type you would see with method parameters, where you want to pass different instantiation of a generic type for a single method parameter:
// Compiler sees that this method can take List of any subtype of Number
public void print(List<? extends Number> numbers) {
// print numbers
}
But at runtime, for creating an object you have to give a concrete type. A wildcard - bounded, or unbounded, is not a concrete type. ? extends Number
could mean anything that is subtype of Number
. So what type of List
would you expect to be created when you create a List<? extends Number>
?
You can consider this case similar to the reason why you can't instantiate an interface
. Because they aren't just concrete.
There is a workaround. Really?
Although this is illegal, you would be surprised to know that there is a workaround, as explained in - Java Generics FAQs. But I really don't think you would ever need that.
回答3:
When you instantiate a parameterized class, the parameter has to be some known, concrete type. It can be parameterized class even with ?
but for inference reasons it has to be concrete. E.g. this is a valid declaration: new ArrayList<List<?>>();
The trick here is that the methods that use the type parameter in the arguments of their signature require the type of the argument to be lower bound. That is, any parameter that you pass in can be cast to the parameter type. Example:
public void fillUp(List<? super T> param)
The fillUp
method takes a collection and fills it with T
type objects. The param
list must be able to handle the T
objects so it is declared that the list can contain types that are ancestors of T
, T
can be safely cast to that type. If T
was not a concrete type, like ? extends Number
, then it would be impossible to exactly define all ancestors of T
.
回答4:
That's not a valid declaration as it's not a known type. You're not specifying a full type here. new ArrayList<Number>
can accept anything that extends Number
by subtyping so your use of ? extends Foo
is not a valid need.
List<Number>
can accept Integer
, Long
, etc. There's no way to do the equivalent of ? super Foo
as it would be semantically meaningless beyond List
or List<Object>
with a strange artificial restriction.
回答5:
Your current definition is not true, The generic type should be same in both sides or should be have inheritance relation.
回答6:
Java generics are not covariant. See, for example, the article Java theory and practice: Generics gotchas by Brian Goetz. Your first example has two problems. First, when you instantiate a type it must be fully specified (including any type parameters). Second, the type parameters must exactly match the left side.
Regarding type covariance (or lack thereof), this is also not legal:
List<Number> test = new ArrayList<Integer>();
despite the fact that Integer
extends Number
. This also explains why the second example is illegal. A raw type is more or less the same as binding the type parameter to Object
, so it would be like:
List<Object> test = new ArrayList<Integer>();
which again fails because generics are not covariant.
As to why the type parameters must be fully specified, the Java Language Specification, §8.1.2 explains the concept:
A generic class declaration defines a set of parameterized types (§4.5), one for each possible invocation of the type parameter section by type arguments.
You can only instantiate an actual type. As long as a type parameter of a generic type is unbound, the generic type itself is incomplete. You need to tell the compiler which specific parameterized type (among the set defined by the generic class) is being instantiated.
As to why generics are not covariant, this was intended to prevent the following sorts of errors:
List<Integer> iTest = new ArrayList<Integer>();
List<Number> test = iTest;
test.add(Double.valueOf(2.5));
Integer foo = iTest.get(0); // Oops!
回答7:
Do not get confused by the Java inheritance concept and wildcard (e.g. ? here) syntex of Java generic concept. Both are not the same and none of the inheritance rule applies to java generic concept.
Hence Number
is not same as ? extends Number
Please note that Java Generic wildcard is intended to tell the compiler about the intended object use. At runtime, it does not exist at all!
If you see generic in java just as a tool to prevent you to make mistakes, you should not go wrong in understanding this concept.
来源:https://stackoverflow.com/questions/18622554/are-we-allowed-to-use-wildcard-during-instantiation