2

For some time I thought I have understanding of how type erasure and generic types worked but today I stumbled upon case when generic type is used in class and unexpected type erasures happen even on types not related to generic. Consider following example class:

import java.util.Optional;

class TheClass<T> {
    Optional<Something> getSomething() {
        return Optional.of(new Something());
    }

    class Something {
        int getId() {
            return 1;
        }
    }
}

Notably it has generic that is not used at all. Class usage is following:

public class Main {
    private static TheClass clazz = new TheClass<>();

    public static void main(String[] args) {
        clazz.getSomething().ifPresent(s -> System.out.println(((TheClass.Something) s).getId()));
    }
}

clazz.getSomething().ifPresent produces warning (unchecked call to ifPresent) and inside call to if present cast is needed. If generic T is removed from abstract class then code works as expected without warning and cast.

What is the reason of observed behavior? Why javac seemingly earses all types from TheClass? Is there any workaround to avoid casting arguments and supressing uchecked warning?

iwek
  • 134
  • 1
  • 10
  • You are using a *raw type*: `TheClass`. It expects a type parameter, but you have given none. – MC Emperor Feb 25 '19 at 20:21
  • @MCEmperor look at the method return type. It's independent of the class generic type. – LppEdd Feb 25 '19 at 20:29
  • fixed code to not use raw type. However, it does not matter for the example – iwek Feb 25 '19 at 20:37
  • Just to say it, the answer was here https://stackoverflow.com/questions/19248616/generic-type-lost-for-member-of-raw-type – LppEdd Feb 25 '19 at 21:03

2 Answers2

2

What about this?

public class Main {
    private static TheClass<?> clazz = new TheClass<>();

    public static void main(String[] args) {
        clazz.getSomething().ifPresent(s -> System.out.println(((TheClass.Something) s).getId()));
    }
}

The warning disappears. (And the cast is unnecessary, hence it can be removed.)

A non-static inner class of a raw type is itself a raw type, as noted by the JLS § 4.8.

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

  • The reference type that is formed by taking the name of a generic type declaration without an accompanying type argument list.
  • An array type whose element type is a raw type.
  • A non-static member type of a raw type R that is not inherited from a superclass or superinterface of R.

It's all about TheClass: this is a raw type, because you have defined it as TheClass (without generic type). That means that Optional is raw too. The compiler starts whining about the Optional being raw, besides complaining about the raw type itself.

Community
  • 1
  • 1
MC Emperor
  • 17,266
  • 13
  • 70
  • 106
  • It answers the question, thanks, however I have doubts about argument about non-static inner class. You can move Something to separate class and it still produces warning without wildcard > – iwek Feb 25 '19 at 21:00
  • @iwek That's in your specific case. The compiler always warns about raw types. In the above example, I changed the reference type from a raw type to a generic type. – MC Emperor Feb 25 '19 at 21:05
2

The problem is your clazz static instance is still raw.

The compiler cannot workout the generic type even though you have specified the diamond operator because the left hand side is raw.

The raw classes lose all generics. There are two options, you could do.

  1. Use the specific type, Something in this case (However, you will have to declare it static in because you refer it in the static context). For this exercise, I have made class Something static,
  static class Something {
        int getId() {
            return 1;
        }
  }
    private static TheClass<Something> clazz = new TheClass<>();

    public static void main(String[] args) {
        clazz.getSomething().ifPresent(s -> System.out.println(s.getId()));
    }
  1. Or use wildcard as MC Emperor's answer.
Laksitha Ranasingha
  • 3,654
  • 1
  • 26
  • 33
  • I get it that it is raw, but does it also imply that all types in diamond operators get earsed? Is it why Optional is treated as just Optional? – iwek Feb 25 '19 at 21:04
  • 1
    The diamond operator is to reduce the verbosity. After Java 7, the compiler knows (mostly) how to determine the type using the LHS type declaration. In Java , the generics type signatures get erased before execution. The JVM does not distinguish `List` vs `List. There's a great explanation by John Skeet. https://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java – Laksitha Ranasingha Feb 25 '19 at 21:15
  • Note that with this code the compiler still complains about `Something` being a raw type. – MC Emperor Feb 25 '19 at 21:18
  • @MCEmperor I have updated the answer to include the code snippet which doesn't have any warnings. – Laksitha Ranasingha Feb 25 '19 at 21:22
  • 1
    That's better ;-) – MC Emperor Feb 25 '19 at 21:23