You can in fact do something like this:
static <T extends Comparable<T>> void f(T... args) {
System.out.println(java.util.Arrays.toString(args));
}
public static void main(String[] args) {
// all one type -- all of these compile!
f(1, 2, 3); // prints "[1, 2, 3]"
f('a', 'b', 'c'); // prints "[a, b, c]"
f("a", "b", "c"); // prints "[a, b, c]"
f(1D, 2D, 3D); // prints "[1.0, 2.0, 3.0]"
// this is not preventable
f(1, (int)'a', 3); // prints "[1, 97, 3]"
// mixture of types -- none of these compile!
//f(1, 'a', 3); // compilation error!
//f(1, '2', "3"); // compilation error!
//f("a", "b", 'c'); // compilation error!
//f(1, 2, 3D); // compilation error!
}
This takes advantage of the fact that:
So to match those types (and possibly others), we bound T as follows:
This does include things e.g. java.util.Date, which implements Comparable<Date>, and countless many other types, but is probably the best that you can do if you also want to allow Integer and Character.
Nonetheless, do keep in mind that Integer, Character, String, are all Object, so in fact a bunch of those mixed together IS a list of one type: Object.
Thankfully, it's NOT the case that Object implements Comparable<Object>; otherwise the above solution wouldn't work.