15

Let's consider a class:

public class Foo<T> {
    public List<String> list = new ArrayList<>();
}

that I'm passing as a parameter to a method.


I have a problem understanding why here the type String isn't resolved:

public void test(Foo t) {
    t.list.get(0).contains("test");
}

and the t.list is treated as List<Object> while here everything works just fine:

public void test(Foo<?> t) {
    t.list.get(0).contains("test");
}

and t.list is List<String>.


The other questions about Type Erasure that have been linked, approach the problem from a different angle. Without knowing the answer to my own question I failed to see the connection and it is why I do not think this question is a duplicate.

Nordar
  • 187
  • 3
  • 14
  • Murat has answered your question, but you are aware that the `` in `Foo` is redundant, right? – Michael Sep 19 '18 at 11:10
  • @Michael yes, it was just for the purpose of giving an example where the `T` isn't used for the actual field type. – Nordar Sep 19 '18 at 11:20
  • @Oleksandr in fact both questions are about the same problem but approach it from a different angle. Facing my problem and not knowing the solution I would be unable to understand the underlying reason behind both. – Nordar Sep 19 '18 at 13:21
  • 1
    @Jakubs The solution is very simple - don't use raw types ;) – Oleksandr Pyrohov Sep 19 '18 at 13:23

2 Answers2

10

When using Foo t, t is a Raw Type so its non-static, non-inherited members, like the list in above code, are also raw types. Here the relevant part of the Java Language Specification (see above link):

To facilitate interfacing with non-generic legacy code, it is possible to use as a type the erasure (§4.6) of a parameterized type (§4.5) or the erasure of an array type (§10.1) whose element type is a parameterized type. Such a type is called a raw type.

More precisely, a raw type is defined to be one of:

  • . . .

  • A non-static member type of a raw type R that is not inherited from a superclass or superinterface of R.

and/or

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

Just declare the list as static and it will be interpreted as a List of String as expected (for testing).

On the other side, the declaration Foo<?> is not a raw type and consequently the list is also not considered a raw type.

An advice later on that page:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

Note: Type Erasure and generated byte-code is the same in both cases...

user85421
  • 28,957
  • 10
  • 64
  • 87
6

That's how Type Erasure works.

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.

Since your method accepts a raw type, the compiler applies the type erasure by erasing the type String and replace it with Object. That's why contains is not recognized since Object does not have that method call.

By supplying <?> you provide a bounded type and that will be used for the type erasure. There the compiler will recognize contains since String has it.

Murat Karagöz
  • 35,401
  • 16
  • 78
  • 107