Почему два метода с подписями (примитив, оболочка) и (примитивные, примитивные) вызывают неоднозначность вызова метода (обертка, примитив)?

Это просто упражнение, но я не могу понять двусмысленность:

private static void flipFlop(String str, int i, Integer iRef) {
System.out.println(str + "ciao");
}

private static void flipFlop(String str, int i, int j) {
System.out.println(str + "hello");
}

public static void main(String[] args) {
flipFlop("hello", new Integer(4), 2004);
}

В нем говорится:

Метод flipFlop (String, int, Integer) неоднозначен для типа Тест

Я бы догадался, что второй аргумент был бы распакован на int, и таким образом был бы выбран второй метод flipFlop.

Ответ 1

Если вам нравится захватывающее чтение, вот соответствующая часть спецификации Java Language, которая описывает, как методы разрешаются.

Но в основном ваш третий аргумент может быть интерпретирован как примитив или autoboxed wrapper, и компилятор не может понять, что вы хотите. Оба метода являются "максимально конкретными" для использования терминологии JLS.

Ответ 2

Хорошо, я более подробно рассмотрел JLS, и я считаю, что это должно устранить любые оставшиеся сомнения, которые могут возникнуть.

Это оригинальная проблема:

public class Main {
    private static void flipFlop(int i, Integer iRef) {
        System.out.println("Method 1");
    }

    private static void flipFlop(int i, int j) {
        System.out.println("Method 2");
    }

    public static void main(String[] args) {
        flipFlop(new Integer(4), 2004);
    }
}

Как было указано в другом ответе: это терпит неудачу, потому что компилятор не может решить, какую перегрузку использовать.

Однако вы можете подумать, что это не имеет никакого смысла. В этой ситуации компилятор может решить, какой метод он должен использовать:

public class Main {
    private static void flipFlop(Integer y) {
        System.out.println("ciao");
    }

    private static void flipFlop(int j) {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        flipFlop(new Integer(6));
        flipFlop(6);
    }
}

Рациональность говорит нам, что когда у вас есть значения X + Y и два метода, которые принимают соответственно Y + X и Y + Y, и вы знаете, что X и Y являются взаимозаменяемыми, то это будет означать, что последний метод более конкретный.

Разница между этими двумя параметрами описана в JLS. Я представил весь рабочий процесс ниже, но важно следующее:

Сначала компилятор будет искать методы с равной подписью, а запрещать бокс/распаковку. В нашем втором примере это не вызывает никаких проблем, но в нашем первом примере это не возвращает выполнимый метод, так как ни один из них не принимает Integer в качестве первого параметра.

Если это не удалось, компилятор переходит на второй шаг, где позволяет боксировать/распаковывать. Это должно устранить проблему, с которой мы столкнулись с первым параметром, но теперь вызывает неоднозначность со вторым параметром, поскольку теперь неясно, ссылаетесь ли вы на перегрузку с помощью int или той, которая использует Integer.

Это в конечном итоге приводит к двусмысленному вызову метода.

15.12.2. Время компиляции Шаг 2: определение подписи метода

  • Первая фаза (§15.12.2.2) выполняет разрешение перегрузки без разрешения преобразования бокса или распаковки, или использование вызова метода переменной arity. Если на этом этапе не обнаружен какой-либо применимый метод, обработка продолжается до второй фазы.

  • Вторая фаза (§15.12.2.3) выполняет разрешение перегрузки , разрешая бокс и распаковку, но все же исключает использование вызова метода переменной arity. Если на этом этапе не обнаружен какой-либо применимый метод, обработка продолжается до третьей фазы.

Если на одной из трех фаз тестирования применимости было идентифицировано несколько применимых методов, тогда выбирается наиболее конкретный, как указано в разделе §15.12. 2.5.

15.12.2.5. Выбор наиболее конкретного метода

Метод считается максимально специфичным для вызова метода, если он доступен и применим, и нет другого применимого и доступного метода, который является более конкретным.

Возможно, что ни один из методов не является наиболее конкретным, поскольку существуют два или более метода, которые являются максимально конкретными. В этом случае:

  • Если все максимально специфические методы имеют эквивалентную эквивалентную (§8.4.2), то: (... некоторые правила, чтобы решить, кто выбрал...)

  • В противном случае мы говорим, что вызов метода неоднозначен и возникает ошибка времени компиляции.

Ответ 3

Второй аргумент 2004, который является int, также применим к Integer из-за автоматического бокса, вот почему компилятор не может решить, какой метод использовать.