Правила перегрузки Java

Недавно я натолкнулся на два перегруженных вопроса, на которые я не могу найти ответ, и у меня нет среды Java для запуска некоторого тестового кода. Я надеюсь, что кто-то может помочь мне, составив список всех правил, которым следуют java-компиляторы, для перегрузки или поочередно указывать мне на список, который уже существует.

Во-первых, когда два метода отличаются только конечным параметром varargs, при каких обстоятельствах каждый из них вызывается и можете ли вы вызвать метод varargs без аргументов?

private void f(int a) { /* ... */ }
private void f(int a, int... b) { /* ... */ }

f(12); // calls the former? I would expect it to
f(12, (int[])null); // calls latter, but passes null for b? 
  // Can I force the compiler to call the second method in the same fashion
  // as would happen if the first method didn't exist?

Второй вопрос: когда два метода различаются по типам, унаследованным друг от друга, который вызывается? Я ожидаю, что будет вызываться самая производная версия, а при кастинге - другая.

interface A {}
class B implements A {}
class C implements A {}

private void f(A a) {}
private void f(B b) {}

f(new C()); // calls the first method
f(new B()); // calls the second method?
f((A)(new B()); // calls the first method using a B object?

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

Ответы на вопрос(2)

Overloading vs Overriding

Выбор изright implementation Метод выполняется во время выполнения, как вы хорошо заметили, теперь сигнатура вызываемого метода определяется во время компиляции.

Overloading Method Selection at Compile Time

Спецификация языка Java (JLS) в разделе 15.12Выражения вызова метода подробно объясняет процесс, которым следует компилятор, чтобы выбрать правильный метод для вызова.

Там вы заметите, что этоcompile-time задача. JLS говорит в подразделе 15.12.2:

This step uses the name of the method and the types of the argument expressions to locate methods that are both accessible and applicable There may be more than one such method, in which case the most specific one is chosen.

Как правило, методы varargs выбираются последними, если они конкурируют с другими методами-кандидатами, поскольку они считаются менее специфичными, чем те, которые получают тот же тип параметра.

Чтобы проверить природу компиляции этого, вы можете сделать следующий тест.

Объявите такой класс и скомпилируйте его.

public class ChooseMethod {
   public void doSomething(Number n){
    System.out.println("Number");
   }
}

Объявите второй класс, который вызывает метод первого и скомпилируйте его.

public class MethodChooser {
   public static void main(String[] args) {
    ChooseMethod m = new ChooseMethod();
    m.doSomething(10);
   }
}

Если вы вызываете основной, вывод говоритNumber.

Теперь добавьте второй более конкретныйoverloaded метод кChooseMethod и перекомпилируйте его (но не перекомпилируйте другой класс).

public void doSomething(Integer i) {
 System.out.println("Integer");
}

Если вы запустите основной снова, вывод все ещеNumber.

В основном, потому что это было решено во время компиляции. Если вы перекомпилируетеMethodChooser класс (тот, что с основным), и запустите программу еще раз, вывод будетInteger.

Таким образом, если вы хотите принудительно выбрать один из перегруженных методов, тип аргументов должен соответствовать типу параметров во время компиляции, а не только во время выполнения.

Overriding Method Selection at Run time

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

Объявите такой класс и скомпилируйте его.

public class ChooseMethodA {
   public void doSomething(Number n){
    System.out.println("Number A");
   }
}

Затем объявите второй расширяющий класс и скомпилируйте:

public class ChooseMethodB extends ChooseMethodA {  }

И в классе MethodChooser вы делаете:

public class MethodChooser {
    public static void main(String[] args) {
        ChooseMethodA m = new ChooseMethodB();
        m.doSomething(10);
    }
}

И если вы запустите его, вы получите выводNumber A, и это нормально, потому что метод не был переопределен вChooseMethodB и, следовательно, вызываемая реализация является реализациейChooseMethodA.

Теперь добавьте переопределенный метод вMethodChooserB:

public void doSomething(Number n){
    System.out.println("Number B");
}

И перекомпилируйте только этот, и снова запустите метод main.

Теперь вы получаете выводNumber B

Таким образом, реализация была выбрана во время выполнения, а не перекомпиляцииMethodChooser класс был обязательным.

 05 июн. 2012 г., 21:22
@Peter Вы прочитали ссылку, которой я поделился с JLS? Если вы не найдете ответа там, вы не найдете больше нигде.
 Peter Oehlert05 июн. 2012 г., 21:12
На самом деле вопрос, на который я бы хотел ответить, - это то, что определяет «наиболее конкретный». Есть ли нумерованный список, который можно составить, чтобы определить, какая функция будет вызвана.
 05 июн. 2012 г., 22:41
@PeterOehlert Конечно, переваривание занимает некоторое время из-за формального языка, используемого в объяснениях. Конечно, если вы сидите и пробуете несколько строк кода, и с некоторым терпением, через некоторое время вы можете придумать хороший сборник примеров, которыми стоит поделиться. Я думаю, что мне очень хотелось бы их увидеть, и они будут достойны публикации в любом блоге или здесь, в качестве ответа на ваш собственный вопрос.
 15 мая 2014 г., 04:09
Второй жирный заголовок должен бытьOverloading Method Selection at Compile Time.
 Peter Oehlert05 июн. 2012 г., 22:37
@edalorzo Я сделал после того, как сделал комментарий. Это окончательная ссылка на то, что должен делать компилятор, и это действительно ссылка, которую я искал. Это, конечно, не самое простое чтение, и я, вероятно, попытаюсь попытаться придумать более практическую дистилляцию этого, поскольку что-то еще, кажется, не существует.

Ваше предположение верно. Второй звонокf() вызовет метод varargs. Вы можете заставить компилятор вызвать второй метод с помощью:

private void f(int a) {
    f(a, null);
}

Второй вопрос:

Да. Однако вы не можете расширять интерфейс. Если вы изменитеA для абстрактного класса все скомпилируется.

 06 июн. 2012 г., 01:20
Вы всегда можете попробоватьf(a, new int[0]);

Ваш ответ на вопрос