12

The below method works flawlessly

public <T> void fromJsonArray(String jsonString,Type tToken) {
  Gson g = new Gson();
  T list = g.fromJson(jsonString,tToken);
  System.out.println(list);
}

But i have not specified what < T > is in this method. how does the compiler assign the value returned by the fromJson method to the variable list whose type i have not specified?

i just tested the validity of the answers stating about <T> being inferred from the return type of the method. It doesn't seem to work out. Please check out the following code. it doesn't even compile

import java.util.*;

class Sample {

  public List<String> getT(String s) {
    List<String>  list = new ArrayList<String>();
    list.add(s);
    return list;
  }

  public <T> void test(){
    T list = getT("test");
    System.out.println(l);
  }

  public static void main(String[] a) {
    new Sample().test();
  }
}

modified the source again and tested it, and it resulted in a compile time error

public <T> List<T> getT(T s) {
  List<T>  list = new ArrayList<T>();
  list.add(s);
  return list;
}

public <T> void test(){
  T list = getT("test"); //incompatible types compilation error here
  System.out.println(list);
}

Sample1.java:13: error: incompatible types T list = getT("test"); ^ required: T found: List where T is a type-variable: T extends Object declared in method test()

Thirumalai Parthasarathi
  • 4,541
  • 1
  • 25
  • 43
  • Maybe it inlines the variable, as it's useless here –  Mar 09 '14 at 08:36
  • A type variable can be any non-primitive type you specify: any class type, any interface type, any array type. – Arjit Mar 09 '14 at 08:44
  • This is a pretty good question. I don't see how it can be inferred from the arguments. `Type` is a non-generic interface with no methods at this time. I wonder if the compiler is able to infer based on the return type. The JLS for type inference is pretty difficult to understand. I don't personally know the answer. – Radiodef Mar 09 '14 at 09:12
  • 1
    @BlackPanther. Ok, how are you invoking that method? `fromJsonArray`? – Rohit Jain Mar 09 '14 at 09:53
  • @RohitJain Since it's void I am wondering if they are invoking it with no type argument (then Object is ultimately inferred for the complete call ...). – Radiodef Mar 09 '14 at 10:13

2 Answers2

13

how does the method infer the type of <T>

It doesn't. Generic methods don't infer their generic types - that's why T is called a type parameter. The caller of the method provides a type argument for T. When it does, it may be inferred by the compiler based on the context of the method call's arguments and target type.

For example:

Set<String> c = Collections.emptySet();

emptySet declares a type parameter T, takes no arguments, and returns a Set<T>. Here, the compiler infers T to be String based on the target type, Set<String>.

Another example:

Collections.singleton("asdf");

singleton declares a type parameter T, takes a T, and returns a Set<T>. Here, there is no target type, but the compiler infers T to be String based on the argument "asdf".

But generic type inference is just a convenience. Without it, we could still use type witnesses to explicitly provide type arguments:

Set<String> c = Collections.<String>emptySet();
Collections.<String>singleton("asdf");

This brings us to your method signature:

public <T> void fromJsonArray(String jsonString, Type tToken)

fromJsonArray declares a type parameter T, but doesn't return anything related to the type T or take arguments related to T. At a call to fromJsonArray, the compiler has no information from which to infer T. Its type argument will default to its upper bound Object unless a type witness is used:

myObj.<String>fromJsonArray(jsonString, tToken);

But this doesn't matter because <String> has no affect on the behavior of the method call or its compilation. T is meaningless* and can be removed from the declaration of fromJsonArray.

how does the compiler assign the value returned by the fromJson method to the variable list whose type i have not specified?

Here is the source of Gson.fromJson(String, Type):

@SuppressWarnings("unchecked")
public <T> T fromJson(String json, Type typeOfT) throws JsonParseException {
    StringReader reader = new StringReader(json);
    T target = (T) fromJson(reader, typeOfT);
    return target;
}

You can see it declares an arbitrary type parameter T and casts the deserialized object to T. This is known as an unchecked cast, because it won't fail fast if it's wrong. That's because T has been erased at runtime. You can see that the code is suppressing a warning about doing this, because it's generally a bad idea. By not restricting what T is based on the method arguments, the Gson code has effectively ceded control over it to the caller. If you wrote:

List<String> list = g.fromJson(jsonString, tToken);

but tToken represented HashSet<String>, you would get a ClassCastException on that line at runtime. Worse, if tToken represented ArrayList<Integer>, it would not even fail on that line, because the JVM would only see a List and allow the assignment to happen. A ClassCastException would be thrown sometime later, once your code tried to treat the list's Integer elements like Strings (and the exception would be confusing to debug).

So to answer your question about the assignment, the compiler lets you assign the result of fromJson to anything you want. It's up to you for it to be correct.

You may ask, Why would Gson do an unchecked cast and allow unsafe code? The answer is that it's a convenience, stemming from language limitations. Their other signature is safer:

public <T> T fromJson(String json, Class<T> classOfT)

But there's no way to represent generic types with Class - no List<String>.class for example. Only a Type can do this, and it's not itself generic. fromJson could have required a TypeToken<T>, but there are other ways to obtain a Type, so that would be restrictive.

Returning Object and forcing the caller to do the unchecked cast would have been more transparent but the Gson developers probably wanted to avoid this "ugliness".

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • Following the code further, I see that that the typeOfT is converted to Class. The code is in TypeUtils class. – Ritesh Mar 10 '14 at 01:45
  • @Ritesh Would you mind linking to that? I don't see it. – Paul Bellora Mar 10 '14 at 01:56
  • [TypeUtils.toRawClass](http://grepcode.com/file/repository.jboss.org/nexus/content/repositories/releases/org.jbpm.jbpm3/gwt-console/1.0.0.Beta2/com/google/gson/TypeUtils.java#TypeUtils.toRawClass%28java.lang.reflect.Type%29) – Ritesh Mar 10 '14 at 02:00
  • @Ritesh Sorry I'm just trying to understand what you mean by "Following the code further" - what's the call hierarchy linking `toRawClass` back to `fromJson`? – Paul Bellora Mar 10 '14 at 02:24
  • I just browsed (or clicked) through the source code at the link you added and found the reference. I am not familiar with the Gson but overall it seems (from the javadocs and from classes such as TypeToken, JsonDeserializationContext, JsonDeserializationVisitor, ParameterizedTypeHandlerMap, and ParameterizedTypeImpl) that a great deal of code is there to provide the feature for any parameterized type (in addition to the simple case of Class) and it is not a type inference. New Type adapters can be registered to customize serialization (see javadoc of com.google.gson.JsonSerializer). – Ritesh Mar 10 '14 at 04:02
  • In short, what you wrote in your answer is absolutely correct. – Ritesh Mar 10 '14 at 04:05
  • @PaulBellora : hi.. thanks for the answer.. i tried out what you said using some sample code and it resulted in compile time incompatible types error.. can you take a look at my edit..? :) – Thirumalai Parthasarathi Mar 10 '14 at 06:14
  • @BlackPanther `getT` is different from `fromJson` in that it takes a `T` and returns a `List`. There is no way to guarantee that the `T` declared by `test` is a `List`. Besides that, passing `"test"` in as an argument of type `T` implies that `T` is `String`. A closer example to what Gson is doing would be `public T getT(String s) { ... }`. – Paul Bellora Mar 10 '14 at 14:25
  • i tried this and it worked.. `public T getT(String s) { T thing = (T)s; return thing; }` guess i have to dive deep into generics and learn of its intricacies.. – Thirumalai Parthasarathi Mar 10 '14 at 15:01
  • @BlackPanther As long as it's clear that such a method isn't safe, since `T` isn't guaranteed to be `String`. It's just a nonsensical example to demonstrate compiler behavior. Gson's unchecked cast makes more sense since the cast object has been created using reflection and there's no alternative for generically typing it. – Paul Bellora Mar 10 '14 at 15:30
  • I completely understood what you were referring.. :) i would like to know more on this topic.. maybe i should go through the JLS on type parameters a little more.. – Thirumalai Parthasarathi Mar 10 '14 at 18:49
3
T list = g.fromJson(jsonString,tToken);

It's inferred from the return type of g.fromJson().

user207421
  • 305,947
  • 44
  • 307
  • 483
  • i have tried it but its not the case i guess... please look at my edit.. thanks for the response..:) – Thirumalai Parthasarathi Mar 09 '14 at 13:31
  • 1
    The return type of g.fromJson is inferred from the method argument 'tToken' (Type) – Ritesh Mar 09 '14 at 14:51
  • @Ritesh: i understand that the return type of the method `fromJson` is inferred from the type parameter, what i would like to know is how that inference helps in inferring the type of `T` in my method – Thirumalai Parthasarathi Mar 09 '14 at 16:33
  • Not sure I got it. By definition, a generic method uses type parameters to express dependencies among the types of one or more arguments and/or its return type. If you see signature of fromJson, you will find that it is using type parameter to indicate that its argument (typeOfT) is deciding the return type T. Your method fromJsonArray is also a generic method- its second argument is typeOfT and its signature is indicating that return type is of same type. – Ritesh Mar 09 '14 at 16:56
  • To be more clear, you can change the signature of fromjsonArray as: fromJsonArray(String, Class) – Ritesh Mar 09 '14 at 16:59
  • 3
    Of course it's the case. Just because you couldn't get your own incorrect example to compile proves nothing. The fromJson() method *does* compile. – user207421 Mar 09 '14 at 22:35
  • i understand that the `fromJson` compiles and so does the one i have given in the question. But what bugs me is that the codes i have given in my edit doesn't compile. can you please point out what i have done wrong in it.. Sorry if i sound dumb or naive... this concept of type inference is really causing an itch in my brain.. i really would like to get this understood.. – Thirumalai Parthasarathi Mar 10 '14 at 06:21
  • i understood a little from @paul's answer.. i guess i have to learn a lot more of the intricacies of generics.. thanks for your answer... :) – Thirumalai Parthasarathi Mar 10 '14 at 15:04