Неоднозначные перегруженные java-методы с дженериками и varargs - программирование
Подтвердить что ты не робот

Неоднозначные перегруженные java-методы с дженериками и varargs

Я пытаюсь понять, как java имеет дело с двусмысленностями в вызовах функций. В следующем коде вызов method неоднозначен, но method2 нет!!!.

Я чувствую, что оба являются двусмысленными, но почему это компилируется, когда я прокомментирую вызов method? Почему method2 не является двусмысленным?

public class A {
    public static <K> List<K> method(final K arg, final Object... otherArgs) {
        System.out.println("I'm in one");
        return new ArrayList<K>();
    }

    public static <K> List<K> method(final Object... otherArgs) {
        System.out.println("I'm in two");
        return new ArrayList<K>();
    }

    public static <K, V> Map<K, V> method2(final K k0, final V v0, final Object... keysAndValues) {
        System.out.println("I'm in one");
        return new HashMap<K,V> ();
    }

    public static <K, V> Map<K, V> method2(final Object... keysAndValues) {
        System.out.println("I'm in two");
        return new HashMap<K,V>();
    }

    public static void main(String[] args) {
        Map<String, Integer> c = A.method2( "ACD", new Integer(4), "DFAD" );
        //List<Integer> d = A.method(1, "2", 3  );
    }
}

EDIT: Это появилось в комментариях: ряд IDE сообщают, что они так неоднозначны - IntelliJ и Netbeans. Однако он компилируется просто отлично из командной строки/maven.

4b9b3361

Ответ 1

Интуитивно понятный способ проверки того, является ли method1 более конкретным, чем method2, заключается в том, чтобы увидеть, можно ли реализовать method1 путем вызова method2 с теми же параметрами

method1(params1){
    method2(params1);   // if compiles, method1 is more specific than method2
}

Если существуют varargs, нам может понадобиться развернуть vararg, чтобы 2 метода имели одинаковое количество параметров.

Проверьте первые два method() в вашем примере

<K> void method_a(K arg, Object... otherArgs) {
    method_b(arg, otherArgs);   //ok L1
}
<K> void method_b(Object arg, Object... otherArgs) { // extract 1 arg from vararg
    method_a(arg, otherArgs);   //ok L2
}

(типы возврата не используются при определении специфичности, поэтому они опущены)

Оба компилируются, поэтому каждый из них более конкретный, чем другой, следовательно, двусмысленность. То же самое касается ваших method2() s, они более специфичны друг для друга. Поэтому вызов method2() неоднозначен и не должен компилироваться; в противном случае это ошибка компилятора.


Итак, что говорит спецификация; но это правильно? Конечно, method_a выглядит более конкретно, чем method_b. На самом деле, если у нас есть конкретный тип вместо K

void method_a(Integer arg, Object... otherArgs) {
    method_b(arg, otherArgs);   // ok
}
void method_b(Object arg, Object... otherArgs) {
    method_a(arg, otherArgs);   // error
}

то только method_a более специфичен, чем method_b, а не наоборот.

Расхождение возникает из магии вывода типа. L1/L2 вызывает общий метод без явных аргументов типа, поэтому компилятор пытается вывести аргументы типа. Цель алгоритма определения типа - найти аргументы типа, которые компилируют код! Неудивительно, что L1 и L2 компилируются. L2 на самом деле состоит в том, чтобы быть this.<Object>method_a(arg, otherArgs)

Ввод типа пытается угадать, чего хочет программист, но предположение должно быть неправильным иногда. Наше реальное намерение на самом деле

<K> void method_a(K arg, Object... otherArgs) {
    this.<K>method_b(arg, otherArgs);   // ok
}
<K> void method_b(Object arg, Object... otherArgs) {
    this.<K>method_a(arg, otherArgs);   // error
}