Статический метод в классе имеет такую ​​же подпись, как метод по умолчанию в интерфейсе

У меня есть сценарий ниже:

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().