Реализация двух интерфейсов в классе с одним и тем же методом. Какой интерфейсный метод переопределен?
Два интерфейса с одинаковыми именами методов и сигнатурами. Но реализованный одним классом, то как компилятор определит, какой метод для какого интерфейса?
Пример:
interface A{
int f();
}
interface B{
int f();
}
class Test implements A, B{
public static void main(String... args) throws Exception{
}
@Override
public int f() { // from which interface A or B
return 0;
}
}
8 ответов
Если тип реализует два интерфейса, и каждый interface
определить метод с идентичной сигнатурой, тогда в действительности есть только один метод, и они не различимы. Если, скажем, два метода имеют конфликтующие типы возврата, то это будет ошибка компиляции. Это общее правило наследования, переопределения методов, скрытия и объявлений, а также применяется к возможным конфликтам не только между 2 унаследованными interface
методы, но и interface
и супер class
метод, или даже просто конфликты из-за стирания типа дженериков.
Пример совместимости
Вот пример, где у вас есть interface Gift
, который имеет present()
метод (например, вручение подарков), а также interface Guest
который также имеет present()
метод (как в, гость присутствует и не отсутствует).
Presentable johnny
это одновременно Gift
и Guest
,
public class InterfaceTest {
interface Gift { void present(); }
interface Guest { void present(); }
interface Presentable extends Gift, Guest { }
public static void main(String[] args) {
Presentable johnny = new Presentable() {
@Override public void present() {
System.out.println("Heeeereee's Johnny!!!");
}
};
johnny.present(); // "Heeeereee's Johnny!!!"
((Gift) johnny).present(); // "Heeeereee's Johnny!!!"
((Guest) johnny).present(); // "Heeeereee's Johnny!!!"
Gift johnnyAsGift = (Gift) johnny;
johnnyAsGift.present(); // "Heeeereee's Johnny!!!"
Guest johnnyAsGuest = (Guest) johnny;
johnnyAsGuest.present(); // "Heeeereee's Johnny!!!"
}
}
Вышеуказанный фрагмент компилируется и запускается.
Обратите внимание, что есть только один @Override
необходимо!!!, Это потому что Gift.present()
а также Guest.present()
являются " @Override
-эквивалент "( JLS 8.4.2).
Таким образом, johnny
имеет только одну реализацию present()
и не важно, как вы относитесь johnny
Будь как Gift
или как Guest
есть только один метод для вызова.
Пример несовместимости
Вот пример, где два унаследованных метода НЕ @Override
-эквивалентно:
public class InterfaceTest {
interface Gift { void present(); }
interface Guest { boolean present(); }
interface Presentable extends Gift, Guest { } // DOES NOT COMPILE!!!
// "types InterfaceTest.Guest and InterfaceTest.Gift are incompatible;
// both define present(), but with unrelated return types"
}
Это еще раз подтверждает, что наследование членов от interface
должны подчиняться общему правилу объявлений членов. Здесь мы имеем Gift
а также Guest
определять present()
с несовместимыми типами возврата: один void
другой boolean
, По той же причине, по которой вы не можете void present()
и boolean present()
в одном типе этот пример приводит к ошибке компиляции.
Резюме
Вы можете наследовать методы, которые @Override
-эквивалентный, с учетом обычных требований метода переопределения и сокрытия. Так как они @Override
-эквивалентно, фактически существует только один метод для реализации, и, таким образом, нечего отличать / выбирать.
Компилятору не нужно определять, какой метод предназначен для какого интерфейса, потому что, как только они определены как @Override
-эквивалентны, это один и тот же метод.
Устранение потенциальных несовместимостей может быть сложной задачей, но это совсем другая проблема.
Рекомендации
- JLS 8.4.2 Подпись метода
- JLS 8.4.8 Наследование, переопределение и сокрытие
- JLS 8.4.8.3 Требования в переопределении и сокрытии
- JLS 8.4.8.4 Методы наследования с переопределенными эквивалентными сигнатурами
- "Для класса возможно наследовать несколько методов с эквивалентными переопределением сигнатурами".
Этот вопрос был отмечен как дубликат этого вопроса https://stackru.com/questions/24401064/understanding-and-solving-the-diamond-problems-in-java
Вам нужна Java 8, чтобы получить проблему множественного наследования, но это все еще не проблема diamon как таковая.
interface A {
default void hi() { System.out.println("A"); }
}
interface B {
default void hi() { System.out.println("B"); }
}
class AB implements A, B { // won't compile
}
new AB().hi(); // won't compile.
Как комментирует JB Nizet, вы можете исправить это, мое превосходство.
class AB implements A, B {
public void hi() { A.super.hi(); }
}
Тем не менее, у вас нет проблем с
interface D extends A { }
interface E extends A { }
interface F extends A {
default void hi() { System.out.println("F"); }
}
class DE implement D, E { }
new DE().hi(); // prints A
class DEF implement D, E, F { }
new DEF().hi(); // prints F as it is closer in the heirarchy than A.
Что касается компилятора, эти два метода идентичны. Там будет одна реализация обоих.
Это не проблема, если два метода эффективно идентичны, поскольку они должны иметь одинаковую реализацию. Если они отличаются по контракту (согласно документации для каждого интерфейса), у вас будут проблемы.
Там нет ничего, чтобы идентифицировать. Интерфейсы только запрещают имя метода и подпись. Если оба интерфейса имеют метод с одинаковым именем и сигнатурой, реализующий класс может реализовать оба метода интерфейса с помощью одного конкретного метода.
Однако, если семантические контракты метода двух интерфейсов противоречат, вы в значительной степени проиграли; тогда вы не можете реализовать оба интерфейса в одном классе.
Хорошо, если они оба одинаковы, это не имеет значения. Он реализует оба из них с одним конкретным методом для каждого метода интерфейса.
Как и в интерфейсе, мы просто объявляем методы, конкретный класс, который реализует эти оба интерфейса, понимает, что существует только один метод (как вы описали, оба имеют одинаковое имя в возвращаемом типе). поэтому не должно быть проблем с ним. Вы сможете определить этот метод в конкретном классе.
Но когда два интерфейса имеют метод с одинаковым именем, но с разным типом возврата, и вы реализуете два метода в конкретном классе:
Пожалуйста, посмотрите на код ниже:
public interface InterfaceA {
public void print();
}
public interface InterfaceB {
public int print();
}
public class ClassAB implements InterfaceA, InterfaceB {
public void print()
{
System.out.println("Inside InterfaceA");
}
public int print()
{
System.out.println("Inside InterfaceB");
return 5;
}
}
когда компилятор получает метод "public void print()", он сначала смотрит в InterfaceA и получает его. Но все равно он выдает ошибку времени компиляции, что возвращаемый тип не совместим с методом InterfaceB.
Так что это идет наперекосяк для компилятора.
Таким образом, вы не сможете реализовать два интерфейса, имеющих метод с одинаковым именем, но с другим типом возвращаемого значения.
Попробуйте реализовать интерфейс как анонимный.
public class MyClass extends MySuperClass implements MyInterface{
MyInterface myInterface = new MyInterface(){
/* Overrided method from interface */
@override
public void method1(){
}
};
/* Overrided method from superclass*/
@override
public void method1(){
}
}
Следующие два подхода также могут быть использованы для реализации обоих дублирующих методов и избежания двусмысленности:
ПОДХОД 1:
-
public class App {
public static void main(String[] args) {
TestInterface1 testInterface1 = new TestInterface1();
TestInterface2 testInterface2 = new TestInterface2();
testInterface1.draw();
testInterface2.draw();
}
}
TestInterface1.java
-
public class TestInterface1 implements Circle {
}
TestInterface2.java
-
public class TestInterface2 implements Rectangle {
}
-
-
-
Выход -
ПОДХОД 2:
App.java
-
public class App {
public static void main(String[] args) {
Circle circle = new Circle() {
};
Rectangle rectangle = new Rectangle() {
};
circle.draw();
rectangle.draw();
}
}
Circle.java
-
public interface Circle extends Drawable {
@Override
default void draw() {
System.out.println("Drawing circle");
}
}
Rectangle.java
-
public interface Rectangle extends Drawable {
@Override
default void draw() {
System.out.println("Drawing rectangle");
}
}
Drawable.java
-
public interface Drawable {
default void draw() {
System.out.println("Drawing");
}
}
Выход -
Drawing circle
Drawing rectangle