5

I would like to know what tool can be used to view code after type erasure. For example, I'd like to see what the following code looks like after type erasure. I know that generic type information is stripped prior to final compilation, replacing type references with Object or boundary types, creating bridge methods, etc. I want to see the source code after the erasure process takes place.

  public static void main(String[] args) {
    Predicate<Object> objPredicate = (Object c) -> c.toString().length() > 2 ;
    Predicate<? super Integer> predicate = objPredicate;
    Number n = Integer.valueOf(22);
    System.out.println(predicate.test(n)); //this line does not compile
  }

[If compiling code is required to see an erasure inspection tool work on the example, the non-compiling line can be omitted or n can be cast to Integer]

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
John
  • 741
  • 9
  • 18
  • that's an interesting question, may I ask why you need that information though? if you are simply asking why that code not compile - than iis rather easy to answer via `PECS`. – Eugene Jun 09 '21 at 00:57
  • @Eugene I wanted to study, empirically, how so-called input variables (upper bound) seemingly are declared as output variables (lower bound) in Functional interfaces. And I am often at a loss to predict just how the erasure process works exactly. If I pass String to a Predicate.test method that accepts ? super CharSequence, why should it compile? Why shouldn’t it, if I’m just testing against the CharSequence interface? Looking under the hood is helpful. – John Aug 02 '21 at 04:29
  • I understand. At the same time I disagree, those are simply folowing the PECS rule. how the "erasure works exactly" is in the JLS, not sure what you are missing, to be fair. Its not a very pleasant read, but its there, as the other answer has already showed. This is not something you study empirically, imo. – Eugene Aug 02 '21 at 05:38
  • What is PECS? Is this resource that is more helpful for predicting erasure, than studying the behavior of the compiler directly? I want to check that out. – John Aug 02 '21 at 19:03
  • [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) – John Kugelman Aug 02 '21 at 19:05
  • @John Kugelman Martin Fowler! What a metaphor to live by. I was aware of this and I am building reflection based validation that depends on being able to expect what's going to happen at runtime. And, thanks. – John Aug 06 '21 at 21:44
  • You can have a look at the following https://docs.oracle.com/javase/tutorial/java/generics/capture.html – KRH Jun 11 '23 at 16:28

4 Answers4

3

I know that generic type information is stripped prior to final compilation

This is not quite true, as erasure is a part of the compilation process itself, not something that happens prior to compilation. i.e. erasure is not a transformation that happens on source code.

As such, there is no way (that I know of) to programmatically get a transformed version of the source code, but we can figure out what happens for simple examples like this by following the rules in JLS 4.6:

Type erasure is a mapping from types (possibly including parameterized types and type variables) to types (that are never parameterized types or type variables). We write |T| for the erasure of type T. The erasure mapping is defined as follows:

  • The erasure of a parameterized type (§4.5) G<T1,...,Tn> is |G|.

  • The erasure of a nested type T.C is |T|.C.

  • The erasure of an array type T[] is |T|[].

  • The erasure of a type variable (§4.4) is the erasure of its leftmost bound.

  • The erasure of every other type is the type itself.

Type erasure also maps the signature (§8.4.2) of a constructor or method to a signature that has no parameterized types or type variables. The erasure of a constructor or method signature s is a signature consisting of the same name as s and the erasures of all the formal parameter types given in s.

The return type of a method (§8.4.5) and the type parameters of a generic method or constructor (§8.4.4, §8.8.4) also undergo erasure if the method or constructor's signature is erased.

The erasure of the signature of a generic method has no type parameters.

For your code snippet we get (ignoring trivial erasures):

Predicate<Object> -> Predicate
Predicate<? super Integer> -> Predicate

Yielding:

public static void main(String[] args) {
    Predicate objPredicate = (Object c) -> c.toString().length() > 2 ;
    Predicate predicate = objPredicate;
    Number n = Integer.valueOf(22);
    System.out.println(predicate.test(n)); //this line does not compile
}

Furthermore, the erased signature of the Predicate::test method is:

boolean test​(Object t)
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • This is really helpful in understanding the compilation issue, because it's clear why inference is failing. As regards a tool -- I am hoping that someone knowledgeable in reverse-compilation tools can weigh in on this. I have heard there are tools that allow you to walk bytecode back to source, and it seems plausible they'd allow you to specify pre-generics versions. I'm just not sure which tool, whether there is open source etc, e.g., if anyone has done this. – John Jun 09 '21 at 00:26
1

If you have a very simple type like :

public static void main(String[] args) {
    Predicate<? super Integer> predicate = x -> x > 1;
}

it gets compiled with invokedynamic, like:

invokedynamic #7,  0  // InvokeDynamic #0:test:()Ljava/util/function/Predicate;

and if you look at the BootstrapMethods, the MethodType preserves (almost) all generic information (I don't think it can preserve the fact that there was a wildcard in use):

#41 (Ljava/lang/Integer;)Z

This reads as "takes an Integer, returns a boolean". So, in theory, it could be possible to re-construct the generic information back. I have tried Intellij to reconstruct the types from a .class file - and it does not preserve the generic types. I am not aware of a tool that would do that.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • VERY helpful and led me to the JDK tool `javap`, which creates something if not exactly java source. Note I am simply trying to strip out generics -- not put them back in -- so the Intellj viewer might work. – John Jun 09 '21 at 14:44
0

On github, I found the java-based open source gui tool bytecode viewer which did the job as follows:

 public static void main(String[] var0) {
      Predicate var1 = (var0x) -> {
         return var0x.toString().length() > 2;
      };
      Integer var3 = 22;
      System.out.println(var1.test(var3));
   }

To get this, I set the view menu to Pane 2/Fernflower/Java.

John
  • 741
  • 9
  • 18
0

I show you an example of why is not permitted what you expose in your question. When you put Predicate<? super Integer> the compiler only knows that the 'unknown' type is a supertype of Integer but not which of them. The 'unknown' supertype in this case might be: Integer (the only that the compiler is secure to be), Number, Comparable, Serializable, Object.

Integer max = 10;
Predicate<Comparable<Integer>> comp = integer -> integer.compareTo(max) > 1;
Predicate<? super Integer> predicate = comp;
Number number = 3;

//This doesn't make sense since Number doesn't even implement Comparable 
comp.test(number); //Compilation error
masinger
  • 2,974
  • 2
  • 13
  • 13