I am the founder and CEO of Data Geekery GmbH, located in Zurich, Switzerland. With our company, we have been selling database products and services around Java and SQL since 2013. Ever since my Master's studies at EPFL in 2006, I have been fascinated by the interaction of Java and SQL. Most of this experience I have obtained in the Swiss E-Banking field through various variants (JDBC, Hibernate, mostly with Oracle). I am happy to share this knowledge at various conferences, JUGs, in-house presentations and on our blog. Lukas is a DZone MVB and is not an employee of DZone and has posted 233 posts at DZone. You can read more from them at their website. View Full User Profile

Overload API methods with care – the sequel

11.22.2011
| 2824 views |
  • submit to reddit

I had recently blogged about funny issues that arise when overloading API methods with generics involved:
http://lukaseder.wordpress.com/2011/11/11/overload-api-methods-with-care/

I promised a sequel as I have encountered more trouble than that, so here it is.

The trouble with generics and varargs

Varargs are another great feature introduced in Java 5. While being merely syntactic sugar, you can save quite some lines of code when passing arrays to methods:

// Method declarations with or without varargs
public static String concat1(int[] values);
public static String concat2(int... values);

// The above methods are actually the same.
String s1 = concat1(new int[] { 1, 2, 3 });
String s2 = concat2(new int[] { 1, 2, 3 });

// Only, concat2 can also be called like this, conveniently
String s3 = concat2(1, 2, 3);

That’s well-known. It works the same way with primitive-type arrays as with Object[]. It also works with T[] where T is a generic type!

// You can now have a generic type in your varargs parameter:
public static <T> T[] array(T... values);

// The above can be called "type-safely" (with auto-boxing):
Integer[] ints   = array(1, 2, 3);
String[] strings = array("1", "2", "3");

// Since Object could also be inferred for T, you can even do this:
Object[] applesAndOranges = array(1, "2", 3.0);

The last example is actually already hinting at the problem. If T does not have any upper bound, the type-safety is gone, completely. It is an illusion, because in the end, the varargs parameter can always be inferred to “Object…”. And here’s how this causes trouble when you overload such an API.

// Overloaded for "convenience". Let's ignore the compiler warning
// caused when calling the second method
public static <T> Field<T> myFunction(T... params);
public static <T> Field<T> myFunction(Field<T>... params);

At first, this may look like a good idea. The argument list can either be constant values (T…) or dynamic fields (Field…). So in principle, you can do things like this:

// The outer function can infer Integer for <T> from the inner
// functions, which can infer Integer for <T> from T...
Field<Integer> f1 = myFunction(myFunction(1), myFunction(2, 3));

// But beware, this will compile too!
Field<?> f2 = myFunction(myFunction(1), myFunction(2.0, 3.0));

The inner functions will infer Integer and Double for <T>. With incompatible return types Field<Integer> and Field<Double>, the “intended” method with the “Field<T>…” argument does not apply anymore. Hence method one with “T…” is linked by the compiler as the only applicable method. But you’re not going to guess the (possibly) inferred bound for <T>. These are possible inferred types:

// This one, you can always do:
Field<?> f2 = myFunction(myFunction(1), myFunction(2.0, 3.0));

// But these ones show what you're actually about to do
Field<? extends Field<?>>                       f3 = // ...
Field<? extends Field<? extends Number>>        f4 = // ...
Field<? extends Field<? extends Comparable<?>>> f5 = // ...
Field<? extends Field<? extends Serializable>>  f6 = // ...

The compiler can infer something like Field<? extends Number & Comparable<?> & Serializable> as a valid upper bound for <T>. There is no valid exact bound for <T>, however. Hence the necessary <? extends [upper bound]>.

Conclusion

Be careful when combining varargs parameters with generics, especially in overloaded methods. If the user correctly binds the generic type parameter to what you intended, everything works fine. But if there is a single typo (e.g. confusing an Integer with a Double), then your API’s user is doomed. And they will not easily find their mistake, as no one sane can read compiler error messages like this:

Test.java:58: incompatible types
found   : Test.Field<Test.Field<
          ? extends java.lang.Number&java.lang.Comparable<
          ? extends java.lang.Number&java.lang.Comparable<?>>>>
required: Test.Field<java.lang.Integer>
        Field<Integer> f2 = myFunction(myFunction(1),
                                       myFunction(2.0, 3.0));

 

From http://lukaseder.wordpress.com/2011/11/19/overload-api-methods-with-care-the-sequel/

Published at DZone with permission of Lukas Eder, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: