1
class Thing<T> {
    public Map<String, List<String>> getData() { ... }
}

class Thingamajig {
    void doStuff() {
        Thing myThing = ...;
        List<String> data = myThing.getData().get("some_key");
    }
}

The call to myThing.getData() gives the following error:

incompatible types: java.lang.Object cannot be converted to java.util.List

This is fixed by giving generic arguments to the Thing instance:

void doStuff() {
    Thing<?> myThing = ...;
    List<String> data = myThing.getData().get("some_key");
}

Even a generic wildcard fixes the issue. To me, this makes no sense as getData() is not even referencing the generic type argument.

Josh M.
  • 26,437
  • 24
  • 119
  • 200
  • 2
    When using raw types, all generic information of that class is lost (and with all I mean all! All methods, fields, inner classes etc will have no generic type information). Which also means that `getData()` will return a raw type `Map`, and hence leads to this error – Lino Sep 09 '19 at 21:46
  • 1
    I suggest to read the section: **A raw type is the erasure of that type**, explicitly, I quote: "In simpler terms, when a raw type is used, the constructors, instance methods and non-static fields are also erased." all in all, that answer should clear up anything – Lino Sep 09 '19 at 21:51
  • @Lino -- thanks for the link but I wouldn't consider this a dupe. This is a very specific use case which is also very confusing and unclear. The "dupe" question linked is very general regarding generics / erasure. – Josh M. Sep 10 '19 at 01:32
  • I would also be reluctant to mark this as a duplicate, considering how specific this question is, but to be fair to @Lino the accepted answer does explain this phenomenon in the "_A raw type is the erasure of that type_" section. – Slaw Sep 10 '19 at 02:53
  • 1
    @Slaw cc JoshM, IMO the section I mentioned does explain quite clearly the problem stated in this question, so I don't see why it would not be a valid dupe. But please correct me if I missed something – Lino Sep 10 '19 at 05:57
  • 2
    @Lino My thinking was this is a not-at-all-intuitive, specific consequence of using raw types and thus makes this question sufficiently unique. However, as you mentioned, the answer to the duplicate does explain the problem described in this question—albeit somewhat indirectly—so I'm okay with leaving this marked as a duplicate (especially since a direct answer was already posted before closure). – Slaw Sep 10 '19 at 06:17

1 Answers1

7

When you use the raw type (Thing), you end up with the erasure of the type - effectively, as if generics didn't exist at all. (Raw types primarily exist to allow code written before generics existed to still work.) This is described in section 4.8 of the JLS.

So the raw type of Thing looks like this:

class Thing {
    public Map getData() { ... }
}

So the return type of getData is the raw Map type, and Map.get() returns Object.

Using the wildcard effectively tells the compiler "I know about generics and want to keep using the type with generics; I just don't care about the type argument."

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you. This illustrates one reason why I miss working with C#. – Josh M. Sep 09 '19 at 22:00
  • I find this a strange way to implement generics. I understand, I believe, the reason for type erasure and its effects, but why would using a raw type affect all _unrelated_ generics in the same class? What potential problems does this implementation solve? – Slaw Sep 09 '19 at 22:17
  • 1
    @Slaw: My *guess* is that it simplifies some aspects of language design compared with a sort of "partial erasure". But I don't know the details of the design process. – Jon Skeet Sep 09 '19 at 22:29
  • @Slaw -- agreed, that's why this confused me. I understand type erasure but didn't expect unrelated generic types to be erased. Very strange behavior, IMO, borderline "wrong" -- stuff like this just points out how half-baked Java generics are. But I won't pretend to be smart enough to understand exactly why ... – Josh M. Sep 10 '19 at 01:31
  • 4
    @Slaw the entire idea of supporting raw types is to support pre-Generics code. So if a raw type appears in some code, the same behavior as before the introduction of Generics will be reproduced. That still doesn’t hold for every construct and causes more harm to new code than it solves problems for old code though. And yes, it’s not understandable why authors of new code should have to deal with a compatibility feature serving more than 15 year old code, but that’s how it was designed. The best way to deal with it, is to enable all compiler warnings and avoid raw types everywhere. – Holger Sep 10 '19 at 08:49
  • @Holger The part I'm struggling with is I don't see how this solves _any_ problems for old code (except _possibly_ to avoid compilation warnings?). However, I'm willing to assume that's a failure on my part—I've never had to work with pre-generics legacy code (and I always avoid raw types). That said, the idea that using a raw type causes all generic functionality to be disabled, in the interest of supporting pre-generics behavior for old code, does make some sense just on principle. – Slaw Sep 10 '19 at 16:20