Can the generic type of a generic Java method be used to enforce the type of arguments?

If I understand correctly, one way to do it is to explicitly specify the type of T instead of letting the compiler infer its type to be of the most direct superclass in the case of two objects of different types being passed in as arguments. Take something like this, for example:

public class Test {
    public static void main(String[] args) {
        Test.x(5.0, 5);          // This works since type is inferred to be Number
        Test.<Integer>x(5, 5);   // This works since type is stated to be Integer
        Test.<Integer>x(5.0, 5); // This doesn't; type is stated to be Integer and Double is passed in
    }

    public static <T> void x(T a, T b) {
    }
}

If I understand your question correctly, you want this:

x(10, "x");

to fail at compile time. Now consider doing this:

Integer i = 10;
String s = "x";
Object o1 = i;
Object o2 = s;
x(o1, o2);

In this case they are both objects - the same type. I don't think there is any way to really enforce what you want - when you cast your argument to Object it is always possible to call it with two different types without any warnings/errors.

You can specify the type you want to use by using it like this:

ClassName.<Type>x(obj1, obj2);

And it's proably the only way to do it.


Why this should be problem in the first place is kind of nebulous to me. I suspect you've instead misunderstood something about the ways in which the type system is useful.

What can we do with a <T> void x(T a, T b)? Well, not a whole lot. Inside the body of x, T is the same as Object, so we could only do something like call toString on a and b to print them.

There's really no practical reason a and b must have the same type. Just that they have some type in common, and that type is Object or a subtype of it. In fact, there's no clear reason why <T> void x(T a, T b) actually needs to be generic at all.

  • The method body doesn't care what the actual types of a and b are because it couldn't use them anyway.
  • The call site doesn't care what the actual types of a and b are because x is a void method so it's a black hole.

It's more typical for a method to have a result, like <T> List<T> Arrays.asList(T...):

// This will cause a compile error because
// the type inferred must be compatible
// with the return assignment.
List<Integer> r = Arrays.asList(1, 1.0);

Or a bound:

// We don't care what the actual types of
// a and b are, just that we can call bar()
// on them.
// Note: this method does not need to be generic.
<T extends Foo> void x(T a, T b) {
    a.bar();
    a.bar();
}

Or a bound which asserts some kind of relation:

// We don't care what the actual types of
// a and b are, just that we can compare
// them to each other.
<T extends Comparable<T>> T max(T a, T b) {
    return (a.compareTo(b) < 0) ? b : a;
}

You can explicitly specify the type parameter, when calling the method. For example:

 <String>x("hello", "world");

However, if you don't specify the type-parameter explicitly and rely only on the type inference feature of Java, then I don't think you can, not only in Generics, but in general.

The method parameter's type is not a concrete type, but it's rather something that denotes a set of applicable types (even this set can comprise of just one type, in the case of final classes, for example).

For example, this method:

public void x(Something a) { }

denotes a method, which parameter should be of a type from the set of types, which are compatible with Something (i.e. Something and all its subtypes).

The same applies for Generics.