What is the point of making a method generic when it has only one generic type anyway?

712 Views Asked by At

If a method is defined as:

<T> List<T> transform(List<T> list)

What does the first T add? As it is essentially the same as:

List<T> transform(List<T> list)
5

There are 5 best solutions below

0
On

<T> is the definition. You need to define generic type parameters before you use them, just like you must define variables before you use them.


Remember, T is just a naming convention. That identifier can be anything. It could be <F> or <Foo>.

Suppose they implemented it how you suggest, and the compiler just infers any missing type to be a generic type parameter. Suppose I have a custom class Foo which I forgot to import.

List<Foo> transform(List<Foo> list)

The compiler would say "Ok, I don't know what Foo is, so let's just assume it's a generic parameter". Your method then compiles just fine, when you would much prefer that the compiler had caught that mistake.

Or maybe you think T should be a special case. You would then be taking something that's currently just a convention and making that a special part of the language. You'd have to deal with the possibility of someone creating a real class called T.

So explicit declarations have their uses, even if they may seem verbose sometimes.

1
On
<T> List<T> transform(List<T> list)

As it is essentially the same as:

List<T> transform(List<T> list)

It's not.

In the second case, T has to have been defined on the containing class:

class GenericClass<T> {
  List<T> transform(List<T> list) { ... }
}

and you can then only pass in/get back a list of the same element type as the class:

GenericClass<String> genericClass = new GenericClass<>();
List<String> stringList = genericClass.transform(Arrays.asList(""));  // OK.
List<Integer> intList = genericClass.transform(Arrays.asList(0));  // Error!

However, with the first form, the T isn't defined on the class, so it can be different on each invocation:

class NonGenericClass {
  <T> List<T> transform(List<T> list) { ... }
}

NonGenericClass nonGenericClass = new NonGenericClass();
List<String> stringList = nonGenericClass.transform(Arrays.asList(""));  // OK.
List<Integer> intList = nonGenericClass.transform(Arrays.asList(0));  // OK.
0
On

The difference is the scope of T. It can be defined on the class level or for each individual method.

List<T> transform(List<T> list)

This one returns and accepts List<T> where T is the same for all methods.

<T> List<T> transform(List<T> list)

This method has its own T unrelated to the generic type of its class (if any).

0
On

It is not the same at all.

The <T> declares the variable, the rest is the use of the variable. This straight up fails to compile:

public class Example {
    public List<T> transform(List<T> foo) {}
}

try it.

In case this is what you have:

public class Example<T> {
    public <T> List<T> transform(List<T> foo) {}
}

Then you have 2 unrelated type variables, both unfortunately named T, which is extremely confusing; I strongly suggest you don't do that (the class has defined a and then the method defines its own T, ensuring that all uses of T in that method declaration are assumed to be the defined on the method; the defined by the class is no longer accessible; it has been 'shadowed'. It is in that sense equivalent to this:

public class Example {
    int x;

    public void foo(int x) { }
}

The int x of foo is utterly unrelated to the int x; field, but because they have the same name, you have made it impossible to refer to the field named x. At least for this there's an escape (this.x still refers to the field) - no such luxury with generics, so, do not shadow variables like this.

It is somewhat unlikely you want a type var on the method at all if you have this code; but if you really do, don't use T, but use anything else:

public class Foo<T> {
    public <Y> List<Y> transform(List<Y> foo) {}
}
0
On

As was already said, the following are essentially identical.

<T> List<T> transform(List<T> list)

List<T> transform(List<T> list)

But the former also allows one to further constrain T. For example you can do

<T extends Foo> List<T> transform(List<T> list) {
...   
}

You can only pass a List<T> if T subclasses Foo.