简体   繁体   中英

How to write a generic method that takes two arguments of the same types in java?

I was very surprised when I noticed that following code compiles without warnings and prints Integer / String :

public final class GenericsTest {
    private static <T> void method(T arg1, T arg2) {
        System.out.println(arg1.getClass().getSimpleName());
        System.out.println(arg2.getClass().getSimpleName());
    }

    public static void main(String[] args) {
        method(1, "1");
    }
}

I expected a compilation error.

Is there a reason why this code compiles?

What is the correct way to ensure that arguments have the same type?

Edit: What about bounded type parameters? The best I can think of is this:

private static <T, U extends T> void method(T arg1, U arg2) {
    System.out.println(arg1.getClass().getSimpleName());
    System.out.println(arg2.getClass().getSimpleName());
}

Unfortunately, java doesn't allow cyclic constraints. <T extends U, U extends T> doesn't compile. Is this a dead end?

The reason that this compiles is because Java will infer the most specific supertype of the arguments passed in, in this case, Object Serializable & Comparable<? extends Serializable & Comparable<? extends Comparable<?>>> Serializable & Comparable<? extends Serializable & Comparable<? extends Comparable<?>>> Serializable & Comparable<? extends Serializable & Comparable<? extends Comparable<?>>> , after 1 is boxed to Integer and "1" is passed as a String .

Without generics:

private static void method(Number arg1, Number arg2) {

Even without generics, you can pass in an Integer and a Double .

Only if the type in question is final can you do this, without generics:

private static void method(String arg1, String arg2) {
    // Yes, they're both Strings, guaranteed.

There is one edge case with generics that I can think of to ensure that they are the exact type. If you have a final class, and you place an upper bound, then you can restrict it to that same class.

public <T extends MyFinalClass> void method(T arg1, T arg2) {
    // Yes, they're both MyFinalClasses
}

But then you could do the same thing without generics.

public void method(MyFinalClass arg1, MyFinalClass arg2) {
    // Yes, they're both MyFinalClasses
}

You could add the class as an additional parameter.

private static <T> void method(T arg1, T arg2, Class<T> type) {
    // ...
}

Now you have to specify the common type.

You can still call method(1, "1", Object.class); but at least you are explicit about the common type.

It is not possible to do this. Or to look at it another way, two reference arguments are always "the same type" -- Object -- any arguments of reference type are always instances of Object .

T can always be Object , and take any two reference arguments. Even with <T, U extends T> void method(T arg1, U arg2) , both T and U can be Object , and thus take any two arguments again.

The underlying reason for this is that there is no type-safety reason to have such a constraint. One of the major points of inheritance is that it should be possible to treat subclass instances like a superclass instance safely. A reference type of superclass type can point to an instance of that class or subclass freely. Therefore, two reference variables that have the same compile-time type, can always point at runtime to instances of different subclass types, completely safely. So you can never, at compile time, make any statement about the relationship of the actual runtime classes of two instances, other than they are subclasses of the compile-time type. Since it's safe for the two arguments to be instances of different classes at runtime, it can be no less safe to actually pass two arguments of different compile-time types.

This is how I solved this problem - I wanted to create a method that creates a diff of two objects.

public List<Change> getChanges(Object o1, Object o2);

Naturally I wanted both o1 and o2 to be of the same type. I solved this by encapsulating this method inside a parameterised class.

public class DiffGenerator<T> { 
  public List<Change> getChanges(T o1, T o2) { 
  //code
 }
}

This can be used as: List<Change> changes = new DiffGenerator<MyClass>().getChanges(myClassOld, myClassNew);

Worked for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM