Instantiating generic type ArrayList<T>

11,419

Solution 1

The code that you mention can compile because the Object "lst" is not actually initialized until the method is called. Since the method knows that it will be getting a var-args argument of type T, it can compile in this scenario. Take the example Wrapper class below for example:

public class Wrapper<T> {


    public static <T> List<T> getList(T... elements){
        List<T> lst = new ArrayList<>();
        for(T element: elements) {
            lst.add(element);
        }
        return lst;
}

}

This code can compile because the method hasn't been called. When the method is called, Type T will be the type that we pass as the var-args argument and the code will have no issue compiling. Lets test this in our main method:

 public static void main( String[] args ){

      System.out.println(Wrapper.getList("Hi", "Hello", "Yo"));

 }

And the output is:

[Hi, Hello, Yo]

However, lets generate a compile-time error to see what the article is talking about within our main method:

Wrapper<T> myWrap = new Wrapper<>();

We are actually trying initialize a generic Object of the Wrapper class in the code above, but is unknown. Since the value for the placeholder will be unknown even when we call the method, it results in a compile-time error, whereas creating a List of type T within the getList method does not cause a compile-time error because it will be initialized with a type when the method is called.

Solution 2

Because T is given as another generic type argument.

It's the whole purpose of generics to make the type parameterizeable. So the caller can specify the type. This can be done in multiple layers: the caller may also be generic and let its caller specify the type.

public static void main(String[] args)
{
  foo(7);
}

public static <T> void foo(T value)
{
  bar(value);
}

public static <U> void bar(U value)
{
  baz(value);
}

public static <V> void baz(V value)
{
  System.out.println(value.getClass().getSimpleName());
}

It prints out

Integer

A parameterized type, such as ArrayList<T>, is not instantiable

Means: You cannot create ArrayList of an unknown T. It must be specified at compile time. But it can be done indirectly, by another generic. In your case, it's another T, which will be specified again by the caller of your generic getList.


The wildcard <?> is something different. It is used to specify compatibility. <?> is the syntax to avoid specification of the type. You can use extends to require a basetype or interface. However, you cannot create instances with wildcards.

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

This wouldn't be possible otherwise. It makes most sense when using it in parameter specifications, for instance:

  public static int foo(List<? extends Comparable> list)
  {
     return list.get(1).compareTo(list.get(2));
  }

It's very confusing of this book. It assumes that <?> somehow solves the problem that a List with unknown T cannot be instantiated. IMHO, this is rubbish. T must be specified to create an instance.

Share:
11,419
seeker27
Author by

seeker27

Updated on June 24, 2022

Comments

  • seeker27
    seeker27 almost 2 years

    I am new to generics and read in a article "A parameterized type, such as ArrayList<T>, is not instantiable — we cannot create instances of them".

    Full quote, from Java in a Nutshell:

    A parameterized type, such as ArrayList<T>, is not instantiable - we cannot create instances of them. This is because <T> is just a type parameter - merely a place-holder for a genuine type. It is only when we provide a concrete value for the type parameter, (e.g., ArrayList<String>), that the type becomes fully formed and we can create objects of that type.

    This poses a problem if the type that we want to work with is unknown at compile time. Fortunately, the Java type system is able to accommodate this concept. It does so by having an explicit concept of the unknown type which is represented as <?>.

    I understand that it should not be instantiable since the concrete (actual) type is not known. If so, why does the below code compiles without an error?

    public class SampleTest {
    
        public static <T> List<T> getList(T... elements) {
    
            List<T> lst = new ArrayList<>(); // shouldn't this line return an error? 
    
            return lst;
        }
    }
    

    I know there is a gap in my understanding of generics here. Can someone point out what am i missing here?

  • DodgyCodeException
    DodgyCodeException over 5 years
    I would have given you a +1 if you had not stated "Means: when you reach this point at runtime" - parameterized types are only looked at during compile time. At runtime, they're all Object, due to type erasure. Also, Foo, Bar etc. should start with small letters by Java convention; Main should be main(String[] args) (otherwise it's just not a main method); and object should be Object. I appreciate you're coming from C# where your answer would have been correct in that language.
  • Stefan Steinegger
    Stefan Steinegger over 5 years
    @DodgyCodeException: Thanks for the feedback. Fixed a few things. You're right with the runtime argument. I wasn't sure how to say it correctly. I just remove this sentences, they were not even required for the point. Would be great if you had some feedback to the <?> part. I'm not sure if I got that correctly.
  • Stefan Steinegger
    Stefan Steinegger over 5 years
    Completely rephrased the wildcard <?> part.
  • newacct
    newacct over 5 years
    "Since the method knows that it will be getting a var-args argument of type T, it can compile in this scenario." The method does not know anything about T at runtime. And it is perfectly fine for the method to create and return an ArrayList<T> even when the method doesn't take any arguments of type T at all: public static <T> List<T> getList(){ List<T> lst = new ArrayList<>(); return lst; }