Java Generics, Tightly Bounded Parameter Type

240 views Asked by At

I wish to have a method which has a signature like method(T1 t1, T2 t2) such that T2 is-a T1 and/or T1 is-a T2. I do not want the case where T1 and T2 are both a T but where neither is-a the other. I wish for the most allowed type to be bounded above by the highest of either T1 or T2 in the inheritance tree. I am using Java 6.

Below is an attempt to show some desired use cases

class Experiment
{
  static List genericList = new ArrayList();
  static ArrayList arrayList = new ArrayList();

  static class Test1 { }
  static class Test2 extends Test1 { }
  static class Test3 extends Test1 { }

  static <T> T experiment0(T expected, T actual)
  {
    return actual;
  }

  /** Almost works, but want arbitrary ordering. Cannot overload due to erasure. */
  static <T, S extends T> S experiment1(T expected, S actual)
  {
    return actual;
  }

  private static void experimentDriver()
  {
    // good, allowed
    List l = experiment0(genericList, arrayList);

    // bad, allowed; want compile-time error
    Object o = experiment0(new String(), new Integer(0));


    // good, allowed
    Test1 test1 = experiment1(new Test1(), new Test3());
    String string = experiment1(new String(), new String());
    List list = experiment1(genericList, new ArrayList());

    // good, disallowed
    experiment1(new Test2(), new Test3());
    experiment1(new Test3(), new Test2());
    experiment1(new Integer(0), new String());
    experiment1(new ArrayList(), new LinkedList());

    // bad, disallowed; want either order
    experiment1(new Test3(), new Test1());
    experiment1(new ArrayList(), genericList);
  }
}

I know that I can achieve something similar with a signature like experiment(Class<T> clazz, T expected, T actual) but that forces the programmer to explicitly mention the highest allowable type, when I'd like it inferred by the language.

Note that the closer solution can't simply be overloaded because their erasures are the same.

I hope I've provided enough information to convey what I want. I realize this may just be impossible in Java, but I hope it's doable. This is also my first question on this forum, so if I've unknowingly violated any rules I apologize in advance and appreciate your patience!

1

There are 1 answers

0
Ian McLaird On

This became too long for a comment.

From the compiler's perspective, a constraint that might be false isn't a constraint at all, and won't help it figure out what types should be allowed in the various parameter slots, or what you're allowed to do with them. Thus, the language (neither C# nor Java, by the way) has no syntax to define things that way. The reason for allowing the type constraints is that it allows you to treat the variable as though it were that type in the body of the method. Thus:

public static <T extends Collection> void foo(T a) {
    System.out.println(a.size());
}

Will compile and run, because all Collection subclasses have a size() method. The trouble is that <T extends S || S extends T> (or whatever other made-up syntax) doesn't help.

For example, suppose we had this:

public static <T extends List || S extends ArrayList> S bar(T a, S b) {
    // details
}

Can we, in this example, call b.removeRange(...)? We have no idea because S might not be an ArrayList. How about a.size()? Again, we don't know, because T might not be a List. So this isn't any better-defined than if we'd just said

public static <T, S> S bar(T a, S b) {...}

Adding that they possibly derive from each other just adds an additional level of complexity to that example.