Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do parameterized methods resolve <T> if it's not an input parameter?

How are references to << T >> handled by the compiler in the following code, since the method takes no parameters that would allow inference of T? Are any restrictions being placed on what type of object can be placed into the list? Is a cast even taking place on the line where I add the String to the list? My first thought is that without anything to infer T from, T becomes an Object type. Thanks in advance.

public class App {

private <T> void parameterizedMethod()
{
    List<T> list = new ArrayList<>();
    for(int i = 0; i < 10; i++)
    {
        list.add((T)new String()); //is a cast actually occurring here?
    }
}

public App()
{
    parameterizedMethod();
}

public static void main(String[] args) {
    new App();
}
}
like image 899
swingMan Avatar asked May 28 '15 20:05

swingMan


2 Answers

This is initially determined by 18.1.3:

When inference begins, a bound set is typically generated from a list of type parameter declarations P1, ..., Pp and associated inference variables α1, ..., αp. Such a bound set is constructed as follows. For each l (1 ≤ l ≤ p):

  • If Pl has no TypeBound, the bound αl <: Object appears in the set.

  • Otherwise, for each type T delimited by & in the TypeBound, the bound αl <: T[P1:=α1, ..., Pp:=αp] appears in the set; [...].

At the end of inference, the bound set gets "resolved" to the inferred type. Without any additional context, the bound set will only consist of the initial bounds based on the declaration of the type parameter.

A bound with a form like αl <: Object means αl (an inference variable) is Object or a subtype of Object. This bound is resolved to Object.

So in your case, yes, Object is inferred.

If we declared a type bound:

private <T extends SomeType> void parameterizedMethod()

then SomeType will be inferred.


No cast actually happens in this case (erasure). That's why it's "unchecked". A cast only happens when the object is exposed due to e.g.:

<T> T parameterizedMethodWithAResult()
{
    return (T) new String();
}

// the cast happens out here
Integer i = parameterizedMethodWithAResult();
// parameterizedMethodWithAResult returns Object actually,
// and we are implicitly doing this:
Integer i = (Integer) parameterizedMethodWithAResult();

Are any restrictions being placed on what type of object can be placed into the list?

Semantically (compile-time), yes. And note that the restriction is determined outside the method. Inside the method, we don't know what that restriction actually is. So we should not be putting String in a List<T>. We don't know what T is.

Practically (run-time), no. It's just a List and there's no checked cast. parameterizedMethod won't cause an exception...but that only holds for this kind of isolated example. This kind of code may very well lead to issues.

like image 155
Radiodef Avatar answered Oct 27 '22 00:10

Radiodef


Inside the method body, Java provides us no way to get any information about the substitution for T, so how can we do anything useful with T?

Sometimes, T is not really important to the method body; it's just more convenient for the caller

    public static List<T> emptyList(){...}

    List<String> emptyStringList = emptyList();

But if T is important to method body, there must be an out-of-band protocol, not enforceable by the compiler, that both the caller and the callee must obey. For example

class Conf
    <T> T get(String key)

// 
<conf>
    <param name="size" type="int" ...

//
String name = conf.get("name");
Integer size = conf.get("size");

The API uses <T> here just so that the caller doesn't need to do an explicit cast. It is the caller's responsibility to ensure that the correct T is supplied.

In your example, the callee assumes that T is a supertype of String; the caller must uphold that assumption. It would be nice if such constraint can be expressed to the compiler as

<T super String> void parameterizedMethod()
{
    List<T> list
    ...
    list.add( new String() );  // obviously correct; no cast is needed
}

//
this.<Integer>parameterizedMethod();  // compile error

unfortunately, java does not support <T super Foo> ... :) So you need to javadoc the constraint instead

/**   T must be a supertype of String! **/
<T> void parameterizedMethod()

I have an actual API example just like that.

like image 25
ZhongYu Avatar answered Oct 26 '22 23:10

ZhongYu