0

I have the below class structure:

class A<T,R> {
   B<R> b;
......
}

class B<R> {
   R t;
}

class StringConsumer {
   A<String, String> a;
}

class LongConsumer {
   A<Long, Long> a;
}

Given that I have an instance of one of the consumer classes, is it possible to obtain the type parameter(s) for consumer.a.b? I found that I am able to resolve the same if I declare a as StringA a; where class StringA extends A<String, String> using TypeTools

Erric
  • 684
  • 8
  • 27
  • Possibly related: [Get generic type of java.util.List](https://stackoverflow.com/q/1942644) – Pshemo Jun 01 '19 at 10:31
  • @Marco13: Maybe I am still wrapping my head around type erasure, but since I am able to get the type parameter of the field `a` with just `StringConsumer.class.getDeclaredFields()[0].getGenericType()`, why is not just possible to maybe "apply" them to the type A to get the type of B? Or is it maybe possible to define a subtype at runtime and get it as a work around? – Erric Jun 01 '19 at 11:46
  • @Pshemo: yes quite related but this is slightly different as I am trying to get nested parameters of nested generics. – Erric Jun 01 '19 at 11:47
  • You're right. In this case, the type is retained through the ("explicitly typed") field. This would not be possible if the types were generic as well, as in `MyConsumer` with `T==String` or `T==Long`. Added one possible path as an answer. – Marco13 Jun 01 '19 at 12:30
  • "but this is slightly different as I am trying to get nested parameters of nested generics" I am not sure wat you mean here. From consumer you can get type of `a` as `A` or `A`. Since `A` holds `B b` it means that generic T of `b` field will be same as declared for A. So if you have `A` its internal `b` will be of type `B` (or at least that is what *compiler* will assume, because type erasure will change each T into `Object` and that is what you will see at runtime). – Pshemo Jun 01 '19 at 13:45
  • "So if you have A its internal b will be of type B" -> what if A had multiple type parameters? I'm looking for a reliable way to obtain a typed B (i.e the equivalent of what `StringConsumer.class.getDeclaredFields()[0].getGenericType()` provides for A. – Erric Jun 01 '19 at 14:53
  • "*what if A had multiple type parameters?*" but how does it matter? When you *use* A in any of your consumers you still need to specify *all* of their generic types, like `A` so `StringConsumer.class.getDeclaredFields()[0].getGenericType()` would still work for it, at least for concrete types (you would just need to iterate over all of these types, not only get first one via [0] https://ideone.com/yoBshc). – Pshemo Jun 01 '19 at 16:02
  • Yes, but out of the `n` type parameters of A how do you find which one/ones is/are applied to B? In the above example you are assuming knowledge of source code, which makes reflection entirely pointless in the first place.(See my comment to marcos answer as well) – Erric Jun 01 '19 at 18:13
  • 1
    "but out of the n type parameters of A how do you find which one/ones is/are applied to B" `B` class *also* provides information about its *declared* generic types via `Class#getTypeParameters`. So after knowing them we can check which type was used in which field like https://ideone.com/XV3O2B (notice result of `genericTypes.indexOf(type)` part). Now since we know which generic type will be used in which field we can map actual types to them. – Pshemo Jun 01 '19 at 20:34
  • Cool, this was what I was looking for. If you could post this as an answer I can accept it (Taking the above example and assuming `A` could have multiple parameters and any number of them could be used in `b`) – Erric Jun 01 '19 at 21:48
  • I was also wondering if it would be possible to somehow obtain a refied Class instance of A with the field generics populated from `StringConsumer`? Much akin to how field generic info is populated for `StringConsumer`? – Erric Jun 01 '19 at 21:55
  • I am not really sure if solution from my last comment is correct answer to the question (at least in its current form) so I will refrain from posting it as an answer. But if you really think it is solution you ware looking for, feel free to post it as answer yourself. – Pshemo Jun 02 '19 at 12:02
  • There obviously has been some confusion regarding the goal (or rather the exact conditions under which a certain type should be obtained). E.g. in the question, you showed a single type parameter, but one of the main issues seemed (!?) to be how to obtain the "right" type when there are *multiple* type parameters. You should edit the question to *precisely* make clear what you're trying to achieve. – Marco13 Jun 02 '19 at 22:49
  • @Pshemo: The question being `is it possible to obtain the type parameter(s) for consumer.a.b`, I was able to achieve this thanks to your example. The modified form would just be a wrapper function which takes a field and returns all applicable generics resolved from the declaring class, will post it soon. – Erric Jun 03 '19 at 04:56
  • @Marco: Assuming that the code has only a single parameter is assuming knowledge about the source code, then one might as well have written the 1 liner to obtain the first type parameter of `StringConsumer` and said that it was the type parameter for `B` (or even just returned hardcoded `String.class` for all it matters then). Yet, I have edited the question to include multiple parameters as this was apparently not that obvious. – Erric Jun 03 '19 at 04:56

1 Answers1

1

Since your consumer classes declare the field a with no type variables, all the typing information you need will be present at runtime.

While it's possible to achieve what you're asking using reflection directly, it gets insane fast, so I'd highly recommend using GenTyRef or GeAnTyRef (an enhanced fork that I maintain).

StringConsumer consumer = new StringConsumer(); //Or LongConsumer

Field a = consumer.getClass().getDeclaredField("a");
//typeOfA represents A<String, String>
ParameterizedType typeOfA = (ParameterizedType) GenericTypeReflector.getExactFieldType(a, consumer.getClass());

Type[] args =  typeOfA.getActualTypeArguments();
System.out.println(args[0]); //String
System.out.println(args[1]); //String

Field b = A.class.getDeclaredField("b");
//or if you need it dynamic ((Class)typeOfA.getRawType()).getDeclaredField("b")
//or GenericTypeReflector.erase(typeOfA).getDeclaredField("b")
//typeOfB represents B<String>
ParameterizedType typeOfB = (ParameterizedType) GenericTypeReflector.getExactFieldType(b, typeOfA);
System.out.println(typeOfB.getActualTypeArguments()[0]); //String again

So what you do here is start from StringConsumer, get the type of its field a (typeOfA, which is A<String, String>), then use that information to get the type of field b (typeOfB, which is B<String>). You can go as deep as you need to this way...

kaqqao
  • 10,809
  • 4
  • 50
  • 101
  • Wow! Awesome. Just 1 question though. It's not possible to obtain an instance of `Class` which is identical to `Class` but with generic info populated from `StringConsumer`, right? (Assuming this is why `TypeToken`s are used?) – Erric Jun 03 '19 at 19:28
  • @Erric `Class` itself can not represent a resolved generic type, but its superclass `Type` can. Not exactly getting what you mean by _generic info populated from `StringConsumer`_. Isn't that exactly what I did in my answer? Getting to `B` by starting from `StringConsumer`. I added some more comments. – kaqqao Jun 03 '19 at 19:41
  • Yes, your answer resolved `B`. But like you mentioned I was wondering if `Class` can hold that info (although now I am wondering why one would need `TypeToken` when `ParameterizedType` exists). BTW, TypeTools does't have a similar api, right? – Erric Jun 03 '19 at 20:06
  • @Erric No, not really, from what I see. It serves some of the same use-cases like Ge(A)nTyRef, but seems much less powerful. – kaqqao Jun 03 '19 at 20:09
  • `Class itself can not represent a resolved generic type, but its superclass Type can`. The fact that `Field a` derived from `Class` is what got me wondering if I can get some `Class` similar to `Class` with `Field b` resolved from `StringConsumer` – Erric Jun 03 '19 at 20:13
  • @Erric Oh, you asked about `TypeToken` as well. That's just a trick to get a `Type` instance for a literal value. E.g. you want to represent `B` somehow, so you create an anonymous class that extends a generic one (namely `TypeToken`) so that the type information gets preserved in `Class#getGenericSuperClass`. – kaqqao Jun 03 '19 at 20:17
  • @Erric When dealing with generics, you must not think in terms of `Class` but in terms of `Type`, where `Class` is just one kind of `Type` (a leaf node in the `Type` hierarchy). `Class` describes what fields and methods are declared/accessible, but to get their reified generic types, you need more information that is only representable by `Type`s (`ParameterizedType` in specific, for your examples). – kaqqao Jun 03 '19 at 20:22