Статический метод в классе имеет такую же подпись, как метод по умолчанию в интерфейсе
У меня есть сценарий ниже:
class C {
static void m1() {}
}
interface I {
default void m1() {}
}
//this will give compilation error : inherited method from C cannot hide public abstract method in I
class Main extends C implements I {
}
Ниже приведены мои вопросы:
-
Мне известно, что метод экземпляра переопределяет методы по умолчанию, но что, если статические методы в классе имеют такую же подпись, как метод по умолчанию в интерфейсе?
-
Если статический метод m1()
в class C
будет общедоступным, тогда ошибка компиляции будет:
статический метод m1() конфликтует с абстрактным методом в I.
поэтому, когда модификатор доступа был по умолчанию, он пытался скрыть, и когда он является общедоступным, он конфликтует. почему это различие? какова концепция этого?
Ответы
Ответ 1
В конечном счете это сводится к тому, что когда у вас есть что-то вроде этого:
class Me {
public static void go() {
System.out.println("going");
}
}
Они оба будут разрешены:
Me.go();
Me meAgain = new Me();
meAgain.go(); // with a warning here
Интерстинг заключается в том, что это тоже будет работать:
Me meAgain = null;
meAgain.go();
Лично я все еще вижу это как недостаток дизайна, который нельзя отменить из-за совместимости - но я хочу, чтобы компилятор не разрешил мне обращаться к статическому методу из экземпляра.
Ваш первый вопрос не связан с java-8 per se, это было так, как прежде, чем java-8:
interface ITest {
public void go();
}
class Test implements ITest {
public static void go() { // fails to compile
}
}
методы по умолчанию просто следуют тому же правилу. Почему это происходит, на самом деле очень подробно описано на переполнении стека, но основная идея заключается в том, что потенциально это может вызвать путаницу в том, какой метод вызывать (представить ITest
будет класс, который Test
будет расширяться, и вы делаете ITest test = new Test(); test.go()
; → какой метод вы вызываете?)
Я думаю, что по тем же причинам это также не допускается (что является в основном вашим вторым вопросом, иначе у вас будет статический и нестатический метод с теми же сигнатурами)
static class Me {
static void go() {
}
void go() {
}
}
Интересно, что это своего рода фиксированное (я думаю, что они поняли, что было бы очень плохо повторить ту же ошибку) в ссылках метода:
static class Mapper {
static int increment(int x) {
return x + 1;
}
int decrement(int x) {
return x - 1;
}
}
Mapper m = new Mapper();
IntStream.of(1, 2, 3).map(m::increment); // will not compile
IntStream.of(1, 2, 3).map(m::decrement); // will compile
Ответ 2
Отвечая на ваш первый вопрос:
И "статический метод в классе", и "метод по умолчанию в интерфейсе" доступны для класса Main
, и, следовательно, если они имеют одну и ту же подпись, это создаст неоднозначность.
Например:
class C{
static void m1(){System.out.println("m1 from C");}
}
public class Main extends C{
public static void main(String[] args) {
Main main=new Main();
main.m1();
}
}
Выход: m1 from C
Аналогично,
interface I{
default void m1(){System.out.println("m1 from I");}
}
public class Main implements I{
public static void main(String[] args) {
Main main=new Main();
main.m1();
}
}
Выход: m1 from I
Как вы можете видеть, к ним можно получить доступ аналогичным образом. Таким образом, это также является причиной конфликта при реализации я и расширении C.
Отвечая на ваш второй вопрос:
Если ваш класс и интерфейсы находятся в одном пакете, модификатор по умолчанию и открытого доступа должен работать аналогичным образом.
Кроме того, m1()
в C
является статическим, который нельзя переопределить и, следовательно, его нельзя рассматривать как реализацию m1()
в I
и, следовательно, проблема компиляции.
Надеюсь, что это поможет!
Ответ 3
Я отвечу на ваш первый вопрос, так как второй уже ответил
Мне известно, что метод экземпляра переопределяет методы по умолчанию, но что, если статические методы в классе имеют одну и ту же подпись, что и метод по умолчанию в интерфейсе?
Я предполагаю, что вы используете JDK 1.8 и, следовательно, путаницу. Модификатор default
в методе интерфейса не говорит о своих спецификациях доступа. Вместо этого он упоминает, что сам интерфейс должен реализовать этот метод. Спецификация доступа к этому методу остается общедоступной. Начиная с JDK8, интерфейсы позволяют указывать методы с моделями по умолчанию, чтобы позволить расширять интерфейсы обратно совместимым способом.
В вашем интерфейсе вам нужно было дать default void m1() {}
, чтобы компиляция была успешной. Обычно мы просто определяем их абстрактным образом, как void m1();
в интерфейсе. Вы должны реализовать метод, потому что вы указали метод по умолчанию. Надеюсь, вы понимаете.
Ответ 4
Поскольку методы класса в java также могут быть вызваны с использованием переменных экземпляра, эта конструкция приведет к двусмысленности:
Main m = new Main();
m.m1();
Неясно, должен ли последний оператор вызывать метод класса C.m1()
или метод экземпляра I.m1()
.