Generic Inheritance and Calling GetMethod (). GetReturnType ()

In my current project, I have classes that are modeled as follows. At some point, a method of type getReturnTypeForGetId() is called for classes A and B Calling a method using A returns Integer as expected, but B returns Serializable .

What am I missing here? Am I bitten by some monstrous eraser, or am I just missing out on some general clobbering context?

EDIT: Adding an overridden getId() method to B fixes the problem, but I would still like to understand what I'm working on.

 import java.io.Serializable; public class WeirdTester { static interface Identifiable<T extends Serializable> { T getId(); void setId(final T id); } static abstract class BaseEntity<T extends Serializable> implements Identifiable<T> { private T id; public T getId() { return id; } public void setId(final T id) { this.id = id; } } static class A implements Identifiable<Integer> { private Integer id; public Integer getId() { return id; } public void setId(final Integer id) { this.id = id; } } static class B extends BaseEntity<Integer> {} @SuppressWarnings("unchecked") private static <T extends Serializable, Q extends Identifiable<T>> Class<T> getReturnTypeForGetId( final Class<Q> clazz) throws Exception { return (Class<T>) clazz.getMethod("getId", (Class[])null).getReturnType(); } public static void main(final String[] args) throws Exception { System.out.println(getReturnTypeForGetId(A.class)); // CONSOLE: "class java.lang.Integer" System.out.println(getReturnTypeForGetId(B.class)); // CONSOLE: "interface java.io.Serializable" } } 
+6
source share
5 answers

Compiled class A has several getId methods. You get a bridge method for a covariant return type (a โ€œfictionโ€ of a language that does not appear on the virtual machine). The specification for Class.getMethod says that it will return a method with the most specific return type (assuming it exists). It does this for A , but for B method is not overridden, so javac avoids synthesizing the unnecessary bridge method.

In fact, for this example, all the information is still present in the class files. (I used to say that this is not erased . This is not true, but erasing does not mean that it is not there!) General information however is a little difficult to extract (it will be in Identifiable.class.getGenericReturnType() , Identifiable.class.getTypeParameters() , BaseEntity.class.getGenericInterfaces , BaseEntity.class.getTypeParameters() and B.getGenericSuperclass (I think!)).

Use javap to see what you have in the class files.

+2
source

In class A, you override getId to return an Integer.

In class B, you do not override getId, so the getId method in B is one of BaseEntity. Due to erasure, this one returns Serializable.

+2
source

The answer is erasing styles. Remember that generics are just a trick, hints of uncompiled Java code. The compiler removes everything associated with them to create bytecode. Therefore, when you use the reflection of the getId method, you only get the original type.

http://download.oracle.com/javase/tutorial/java/generics/erasure.html

But if you ask for the class of the actual object returned by this method (B.getId), without using reflection, because of its construction, you will get Integer.

+1
source

The id in BaseEntity is private and "Serializable or Extends Serializable".

Class B (which extends BaseEntity) knows nothing about this field. If he defined his own identifier and did not override getId () / setId (...), these two methods would continue to use BaseEntity.id

If you add this method to BaseEntity:

 public void setId2(final Serializable id) { this.id = (T) id; } 

it allows you to set BaseEntity.id to any Serializable.

In the next test, you can set the id field, for example. the Float value and all compiled and immutable getId () conveniently return the Float value.

 B b = new B(); b.setId2(2.1F); System.out.println( b.getId() ); //prints out 2.1 

Therefore, if you do what you are doing and ask: โ€œWhat is the return type of the B.getId () method,โ€ then if you did not override the getId () method in class B (which would force it to use the type of the Integer function and return Integer Note that BaseEntity.id will not even be visible to B then!) the reflection response is not integer, but general Serializable. Because any Serializable can really exit the getId () method.

+1
source

Java allows the so-called "narrowing" of return types. This is why your example works in general:

Serializable getId()

can be overridden by any serializable return type, e.g.

Integer getId() , since Integer implements Serializable , so narrowing is allowed in this case.

Since B does not override getId() , its getId() same as the name inherited from BaseEntity . Ad

 class B extends BaseEntity<Integer> 

"erased type" at compile time

 class B extends BaseEntity 

and, voilร , we get the observed result.

-1
source

Source: https://habr.com/ru/post/900297/


All Articles