Inaudible Jive
Inaudible Jive

Reputation: 159

What type is <?> when making instantiating lists?

I have seen in multiple different places people who instantiate a list or ArrayList like:

List<?> l = new ArrayList<>();

What type is ?? Does this mean that it can hold any types in it? If so, why would this be used instead of just and ArrayList?

Upvotes: 7

Views: 161

Answers (3)

ZhongYu
ZhongYu

Reputation: 19682

Let me make this a long bed time story; read it to fall asleep:)


Let's begin with this point -- To invoke a generic method, its type arguments must be supplied. (Unless the method is invoked in a "raw" manner, i.e. in the erased form, which is another topic:)

For example, to invoke Collections.<T>emptyList(), T must be supplied. It can be supplied explicitly by the programmer --

List<String> list = Collections.<String>emptyList();  // T=String

But that is tedious, and kind of dumb. Obviously in this context, T can only be String. It's stupid if the programmer has to repeat the obvious.

That's where type inference is helpful. We can omit the type argument, and the compiler can infer what the programmer intends it to be

List<String> list = Collections.emptyList();  // T=String is implied

Remember, <String> is still supplied, by the programmer, implicitly.


Supposedly, the programmer is the all-knowing dictator of all type arguments, and, the compiler and the programmer have a common understanding on when type arguments can be omitted and inferable from context. When the programmer omits a type argument, he knows the compiler can infer it exactly as he intended, based on a rigorous algorithm (which he masters:) It is not the compiler's discretion to pick and choose type arguments, rather, the programmer does, and conveys it to the compiler.

Realistically, type inference is so complex, few no programmer has any idea what's going on in a lot of cases:) The programmer is more like a dictator making vague commands, and the compiler tries its best to make sense out of it. We mostly write code on intuition, not paying attention to details, and we sort of believe that the code does what we want if the compiler approves it.

In any case, all type arguments are fixed precisely and predictably at compile time. Any omitted type argument is equivalent to an explicitly specified one.

Some type arguments are "undenotable", e.g. a type variable introduced by capture conversion. They can not be explicitly specified, they can only be inferred. (Nevertheless the programmer is supposed to know what they are, even though they cannot be named)


In the previous example, T can only be inferred as String, there's no other choices. But in a lot of cases, there are more candidates for T, and the type inference algorithm must have a strategy to resolve it to one of the candidates. For example, consider this lonely statement

    Collections.emptyList();

T could be any type; T is resolved to Object, because, well, there's no good reason to resolve it to anything else, like Integer or String etc. Object is more special because it's the supertype of all.


Now, let's get to constructors. Formally speaking, constructors are not methods. But they are very much alike in a lot of aspects. Particularly, type inference on constructors is almost the same as on methods. Invoking a constructor of a class CLASS takes the form of new CLASS(args).

Just like methods, a constructor can be generic, with its own type parameters. For example,

class Bar
{
    <T>Bar(T x){ .. }

and type inference works on generic constructors too

    new Bar("abc");  // inferred: T=String

To explicitly supply type arguments for a constructor,

    new <String>Bar("abc");

It's pretty rare though that a constructor is generic.


A generic constructor is different from a generic CLASS! Consider this

class Foo<T>
{
    Foo(T x){ .. }

The class is generic, the constructor is not. To invoke the constructor for class Foo<String>, we do

    new Foo<String>("");   // CLASS = Foo<String>

Method type inference we've been talking about so far does not apply here, because the constructor is not even generic. In Java 5/6, there is no type inference on CLASS, therefore <String> must be explicitly specified. It's stupid, because <String> is obvious in this context. There were workarounds (i.e. using static factory methods), but people were of course very upset and demanded a solution.


In Java 7, this problem is solved by "diamond inference" -

    new Foo<>("");   // inferred: T=String

"diamond" refers to the curious <> operator. It is required; we cannot simply write

     new Foo("");

because that already had a different meaning - invoking the constructor of "raw" Foo.

With diamond inference, we can do things we couldn't in Java 5/6

List<Object> list = new ArrayList<>();  // Java 7. inferred: E=Object
// equivalent to
List<Object> list = new ArrayList<Object>(); // <Object> is required in Java 5/6

Remember, T=Object is still supplied, through diamond inference.


Finally, we come back to your original question

List<?> list = new ArrayList<>();

Here, E=Object is inferred (what else?). The code is equivalent to

List<?> list = new ArrayList<Object>();

Yep, the list object is indeed an ArrayList<Object>, not ArrayList<SomethingElse>.

Also note that the following would be illegal and nonsensical

List<?> list = new ArrayList<?>();
                            ^^^

CLASS in new CLASS(args) must be a concrete type. We can only instantiate an ArrayList of a specific element type.


The declared type List<?> of variable list is too general though. For a local variable, it is the best practice IMO to declare it in its more specific type

ArrayList<Object> list = new ArrayList<>();

Don't use <?> here - it just causes confusion to everybody.

On a related note, a lot of people would argue for "program against interface"

     List<Object> list = new ArrayList<>();
     ^^^^  

That is wrong IMO. Who are we providing abstraction for in a local block? Use the most specific type in implementation for max clarity; use abstract types in interfaces.


zzzzzzzzzz

Upvotes: 1

Rahul Tripathi
Rahul Tripathi

Reputation: 172588

As correctly pointed by Marko, its an unknown restriction on the List type.

The Java docs says that:

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach:

  • If you are writing a method that can be implemented using functionality provided in the Object class.
  • When the code is using methods in the generic class that don't depend on the type parameter. For example, List.size or List.clear. In fact, Class<?> is so often used because most of the methods in Class do not depend on T.

Upvotes: 1

Marko Topolnik
Marko Topolnik

Reputation: 200256

Does this mean that it can hold any types in it?

No. It means that your l variable could be referring to a list parameterized with any type. So it's actually a restriction: you will not be allowed to add any object to l because you have no idea which items it accepts. To give a concrete example, l could be a List<String> or it could be a List<ExecutorService>.

Upvotes: 9

Related Questions