ddimitrov
ddimitrov

Reputation: 3343

Unbound generic return type in Java

I am playing with some ideas about constructing Java APIs and today I spent some time thinking about this code:

public <T> T getField1(Class<?> type, Class<T> retvalType, String fieldName) {
  Object retval = ...; // boring reflection code - value could be dog, could be duck
  return retvalType.cast(retval); // cast to expected - exception is raised here
}

// usage - we manually repeat the LHS type as a parameter
int foo = getField(o, Integer.class, "foo");

This is how I would typically write the API, witht he idea that specifying the retvalType gives me extra type safety. But does it?

Because we have reflection involved, the retval type is unknown. Casting on the second line would reliably catch type mismatch only when retvalType is non-generic. On the other hand, even if we don't do this explicit cast, the Java runtime would cast the generic result when it sets it to a variable - in other words, are there any drawbacks if we rewrite the code above as:

public <T> T getField2(Class<?> type, String fieldName) {
  return ...; // boring reflection code - the value type will be checked when we assign it
}

// usage - exception is raised at the next line if types don't match
int foo = getField(o, "foo");

The advantage of the latter code is that the usage is more compact and we don't have to repeat ourselves. The disadvantage is that type cast exceptions come from non-obvious place (there is no explicit cast at the calls site), but I'm starting to think that that is OK - by specifying type literal, we only repeat what we already know, but ultimately because of reflection the program soundness is still not guaranteed.

Can anybody make a good argument why the first snippet would be preferable to the second?


Edit 1:

One argument is that by having the expected type as a parameter, we can accommodate a need to do non-trivial coercion without changing the API.

For example, the looked-up field may be of type Date, but we may be requesting long, which could be converted internally by doing date.getTime(). Still this seems far-fetched to me and there is risk of mixing responsibilities.

On the other hand, not passing the type limits the input dependencies, which makes the API more robust to changes. (Cue in 'information hiding', 'instability metric', etc).

Upvotes: 5

Views: 485

Answers (1)

ddimitrov
ddimitrov

Reputation: 3343

In conversation with @shmosel it came up that the runtime cast, on which the second version relies, will not necessarily happen when the LHS type is generic type.

For example, consider the code:

class Foo<T> {
  void a1(Object o) {
    // forces to be explicit about the chance of heap polution
    @SuppressWarning("unchecked")
    Set<String> a = (Set<String>) getField1(o, Set.class, "foo");
    T c = (T) getField1(o, Set.class, "foo"); // guaranteed not to compile
  }

  void a2(Object o) {
    // implicit chance of heap polution in case of Set<Date>
    Set<String> a = (Set<String>) getField2(o, "foo");
    T c = getField2(o, "foo"); // will succeed even if foo is Date
  }
}

In other words, by doing the casting in the getField() method, we get a hard guarantee that the value is of the specified class, even if it is not used, or assigned to an Object variable (which happens often in dynamic languages and reflective libraries).

Upvotes: 1

Related Questions