Home > Enterprise >  How does java deal with generic varargs exactly?
How does java deal with generic varargs exactly?

Time:10-16

I have a generic class called Foo<T>.

public class Foo<T> {
    public void doSomething() {
        Class<T> klass = T.class;  // This won't compile
    }
}

After some googling, I find out that since java implements generics by doing type erasure, it has no idea about the runtime type of T and refuses to compile. However, if I try this...

public class Foo<T> {
    public void doSomething(T... ts) {
        Class<T> klass = (Class<T>) ts.getClass().getComponentType();
        System.out.println(klass);
    }
}

...and run the following code...

Foo<String> foo1 = new Foo<>();
foo1.doSomething("foo", "bar");  // class java.lang.String
foo1.doSomething();  // class java.lang.String

Foo foo2 = foo1;
foo2.doSomething("foo", "bar");  // class java.lang.Object
foo2.doSomething();  // class java.lang.Object

Foo<? super String> foo3 = foo1;
foo3.doSomething("foo", "bar");  // class java.lang.Object
foo3.doSomething();  // class java.lang.Object

The first one prints class java.lang.String while the other two prints class java.lang.Object, which does not make a lot of sense to me - how can an object's behavior depend on the variable type it's assigned to? Besides, if java does type erasure, how can it know the type parameter is actually java.lang.String in the first case after all? And, as I can't assign an Object to T, how can java assign an Object[] to T[]? Seems it's breaking type safety!

I suppose java passes the runtime class implicitly here, as it can't be inferred from the arguments because there's no argument at all at the second method call. I wonder what java is doing under the hood and I'd be glad if someone can tell me more about generic varargs.

I'm new to StackOverflow. If I break some rules, please let me know:)

CodePudding user response:

Firstly, varargs are just syntactic sugar for an array argument. So:

clazz.varArgsMethod("A", "B");

is just the same as (and can be invoked as):

clazz.varArgsMethod(new SomeElementType[]{"A", "B"});

where SomeElementType is determined by the compiler.


At the call site:

Foo<String> foo1 = new Foo<>();
foo1.doSomething("foo", "bar");
foo1.doSomething();

the compiler knows that doSomething is a varargs method, and needs a T[] as the varargs argument. Because it knows that T is String for a Foo<String>, it knows that T has to be String. So it compiles this code to:

Foo<String> foo1 = new Foo<>();
foo1.doSomething(new String[]{"foo", "bar"});
foo1.doSomething(new String[]{});

The String[] array type has a String component type. The method is passed instances of String[], which can be queried reflectively for their component type.


It's basically the same for the other two cases: T is determined from the type of the Foo:

  • For raw Foo, T is erased. The erasure of T is Object
  • For Foo<? super String>, the upper bound of T is Object

Hence, it creates and passes an Object[].

CodePudding user response:

...And, as I can't assign an Object to T, how can java assign an Object[] to T[]? Seems it's breaking type safety!

Yes, you are correct. Varargs of generic parameter types actually do violate Java's own type safety rules.

Why?

Because of the fact that arrays are covariant (and reified) whereas generics are invariant (and erased, or non-reified). In the words of Joshua Bloch, "arrays and generics do not play well together." The developers of the Java language made a very deliberate decision to implement the varargs language feature despite the fact that it violates type safety. They concluded that the value of having varargs (which are extensively used by the reflection and annotation facilities) justified their risks and limitations.

In Java, you cannot compile this code:

T[] genericArray = new T[SIZE];

This is illegal because instantiating an array of a non-reified type is not type safe according to Java's own type safety rules. Yet, when a client invokes a method that has a varargs parameter of a generic type, the runtime system will instantiate an array of that generic type! It's not type safe!

If the varargs system is not used carefully, ClassCastExceptions or other bugs can occur in the program. Essentially, an unenforceable contract is implied between the developer and the runtime system:

The array resulting from the generic varargs parameter...

  • will only be read from
  • will never be written to
  • will never be returned as a result
  • will not be passed to another client (unless that client also promises to only read from the array)

In other words, the developer promises to use the resulting generic array only as a variable number of arguments to complete a computation.

  • Related