29

Can you explain why the following works?

public class GenericsTest<T> {

    public void doSomething(T v1, T v2) {

    }

    public static <T> void doSomethingStatic(T v1, T v2) {

    }

    public static <T> void doSomethingStaticList(List<T> v1, List<T> v2)
    {

    }

    public static void main(String[] args) {
        GenericsTest<String> gt = new GenericsTest<>();

        // OK
        gt.doSomething("abc", "abc");

        // Not OK
        gt.doSomething(1, "abc");

        // OK
        doSomethingStatic(1, 2);

        // Still OK
        doSomethingStatic(1, "abc");

        // So why is this not OK?
        List<String> list1=new LinkedList<>();
        List<Integer> list2=new LinkedList<>();
        doSomethingStaticList(list1,list2);
    }
}

T v1, T v2 should be the same type in doSomethingStatic, but I'm still able to pass different types(integer and string).

If doSomethingStatic() takes a common super class by default, why doesn't doSomethingStaticList() work with different types?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tony
  • 459
  • 1
  • 5
  • 18
  • 2
    In `public static void doSomethingStatic(T v1, T v2) {`, the type variable is unnecessary: it's semantically identical to `public static void doSomethingStatic(Object v1, Object v2) {`. – Andy Turner Oct 04 '16 at 10:23
  • To the update: because there is no `T` that would match both types. The only type that would match both lists is the raw type `List`, but there is no valid `List`. – Hulk Oct 04 '16 at 10:41
  • 5
    In the case of the lists, what superclass type would `T` take to make them both valid? Don't forget that generics are invariant, and you can't assign a `List` to a `List`. – Jorn Vernee Oct 04 '16 at 10:42
  • Closely related to http://stackoverflow.com/a/34944269/1743880 – Tunaki Oct 04 '16 at 20:19
  • 2
    @Tony Changing or adding a new question after it has already been answered is not a good idea, because you are unlikely to get an answer. Anyway, in case of lists, in short `List` is not a subclass of `List`. See this: http://stackoverflow.com/questions/2745265 – Jaroslaw Pawlak Oct 05 '16 at 20:06

4 Answers4

23

In non-static case you define T as String when you create instance of GenericsTest. Hence passing an int will give compile error. If you did gt.doSomething(1, 2) it would fail as well.

In static case, you don't define T manually, it is derived from parameters. It will be the first common superclass of both classes - which in this case is Object. You might want to use bounded wildcard, e.g. <T extends Number> or <T extends CharSequence>.

Note that you have two different Ts here:

  • GenericsTest<T>
  • public static <T> void doSomethingStatic(T v1, T v2)

The declaration of generic parameter is whenever you write <T>. You can use different letters in this case to avoid confusion.

Jaroslaw Pawlak
  • 5,538
  • 7
  • 30
  • 57
  • The `` in the static method is complete standalone/separated from the class itself. So T will fall back to the first common supertype of v1 and v2, which is Object in this case. If one does not include `` the compiler would interprete it as the `T` of the class and since the Method is static, this is illegal. – Mad Matts Oct 04 '16 at 10:18
  • @BlueRaja-DannyPflughoeft The question was edited to describe another scenario ~25 minutes after I posted this answer. I gave short explanation + link in the comment on the question. – Jaroslaw Pawlak Oct 06 '16 at 09:09
14

This works because T in your static method is his own type parameter, not the T parameter for instance that used in your member method. Rename it to clarify:

public static class GenericsTest<T> {

    public void doSomething(T v1, T v2) {

    }

    public static <V> void doSomethingStatic(V v1, V v2) {

    }
//...

So in case of doSomething(...) your instance type parameter value is String so it's an error. In case of static doSomethingStatic(...) value of type parameter is different:

GenericsTest.doSomethingStatic(1, "abc"); //ok
GenericsTest.<Object>doSomethingStatic(1, "abc"); //ok
GenericsTest.<String>doSomethingStatic(1, "abc"); //not ok
new GenericsTest<String>().doSomething(1, "abc"); //not ok
Filipp Voronov
  • 4,077
  • 5
  • 25
  • 32
2

First a bit of theory:

  • Generic methods are methods that introduce their own type parameters Java Tutorials - Generic Methods
  • Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable Java Tutorials - Type inference

So what happen:

  • when a generic expression precede the return value then a new generic type variable is "declared". So the T of the class declaration is different (for the compiler) from the T of the method declaration.
  • the compiler apply type inference and in your example it determine that the suitable type to apply method invocation is Object

You can try also this example without static method:

public class GenericsTest<T> {

  public void doSomething(T v1, T v2) {

  }

  public <T> void doSomething2(T v1, T v2) {

  }

  public static void main(String[] args) {
    GenericsTest<String> gt = new GenericsTest<>();

    // ok
    gt.doSomething("abc", "abc");

    // Not ok
    gt.doSomething(1, "abc");

    // ok
    gt.doSomething2(1, 2);

    // Still ok
    gt.doSomething2(1, "abc");

  }

}
Aris2World
  • 1,214
  • 12
  • 22
0

In static case, you don't define T manually, it is derived from parameters. In the case doSomethingStaticList(list1,list2) where list1 is String generic List while list2 is Integer generic List. Compiler inference algorithm won't be able to recognize because List and List doesn't belong to any common type.

Aman Goyal
  • 383
  • 2
  • 9