Thực hiện hai giao diện trong một lớp với cùng một phương thức. Phương pháp giao diện nào bị ghi đè?


235

Hai giao diện có cùng tên phương thức và chữ ký. Nhưng được thực hiện bởi một lớp duy nhất thì trình biên dịch sẽ xác định phương thức nào cho giao diện nào?

Ví dụ:

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;
  }
}   

Câu trả lời:


337

Nếu một kiểu thực hiện hai giao diện và mỗi giao diện interfacexác định một phương thức có chữ ký giống hệt nhau, thì thực tế chỉ có một phương thức và chúng không thể phân biệt được. Nếu, giả sử, hai phương thức có các kiểu trả về xung đột, thì đó sẽ là một lỗi biên dịch. Đây là quy tắc chung của thừa kế, ghi đè phương thức, ẩn và khai báo, và cũng áp dụng cho các xung đột có thể xảy ra không chỉ giữa 2 interfacephương thức được kế thừa , mà còn là interfacemột classphương thức và siêu phương pháp, hoặc thậm chí chỉ là xung đột do xóa kiểu tổng quát.


Ví dụ tương thích

Đây là một ví dụ trong đó bạn có interface Giftmột present()phương thức (có, tặng quà) và interface Guestcũng có một present()phương thức (như trong, khách có mặt và không vắng mặt).

Presentable johnnylà cả a Giftvà a 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!!!"
    }
}

Đoạn trích trên biên dịch và chạy.

Lưu ý rằng chỉ có một @Override điều cần thiết !!! . Điều này là do Gift.present()Guest.present()là " @Overridetương đương" ( JLS 8.4.2 ).

Như vậy, johnny chỉ có một thực hiện của present(), và nó không quan trọng như thế nào bạn đối xử johnny, cho dù là một Gifthoặc là một Guest, chỉ có một phương pháp để gọi.


Ví dụ không tương thích

Đây là một ví dụ trong đó hai phương thức được kế thừa KHÔNG @Overridetương đương nhau:

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"
}

Điều này tiếp tục nhắc lại rằng kế thừa các thành viên từ một interfacephải tuân theo quy tắc chung của tuyên bố thành viên. Ở đây chúng ta có GiftGuestđịnh nghĩa present()với các kiểu trả về không tương thích: một voidkiểu khác boolean. Vì lý do tương tự mà bạn không thể void present()và một boolean present()trong một loại, ví dụ này dẫn đến lỗi biên dịch.


Tóm lược

Bạn có thể kế thừa các phương thức @Overridetương đương, tuân theo các yêu cầu thông thường của phương thức ghi đè và ẩn. Vì họ @Override -equivalent, hiệu quả chỉ có một phương pháp để thực hiện, và vì thế không có gì để phân biệt / chọn từ là.

Trình biên dịch không phải xác định phương thức nào dành cho giao diện nào, bởi vì một khi chúng được xác định là @Overridetương đương, chúng là cùng một phương thức.

Giải quyết sự không tương thích tiềm năng có thể là một nhiệm vụ khó khăn, nhưng đó lại là một vấn đề khác.

Người giới thiệu


Cảm ơn - điều này rất hữu ích. Tuy nhiên, tôi có một câu hỏi sâu hơn về sự không tương thích, mà tôi đã đăng như một câu hỏi mới
amaidment

2
BTW Điều này thay đổi một chút với sự hỗ trợ của các defaultphương thức trong Java 8.
Peter Lawrey

Các lớp tổng hợp để giải quyết sự không tương thích tiềm năng có thể là mánh khóe :), nhưng, tôi chưa bao giờ gặp vấn đề như vậy, và rõ ràng nó có thể xảy ra.
Sức mạnh Bảo Bình

1
Bài viết này trình bày một mẫu thiết kế có thể được sử dụng để giải quyết phần nào tình huống mà bạn cần triển khai hai Giao diện Va chạm, nói FooBar. Về cơ bản, bạn có lớp của bạn thực hiện một trong các giao diện Foo, và cung cấp một Bar asBar()phương thức để trả về một lớp bên trong thực hiện Bargiao diện thứ hai . Không hoàn hảo vì lớp học của bạn cuối cùng không phải là "Thanh", nhưng nó có thể hữu ích trong một số trường hợp.
Javaru

1
Tôi là một nhà phát triển java nhưng c # thực sự thông minh hơn về điều này: stackoverflow.com/questions/2371178/ Lời
Amir Ziarati

25

Điều này đã được đánh dấu là trùng lặp với câu hỏi này /programming/24401064/under Hiểu-and-solve-the-di Bling-probols-in-java

Bạn cần Java 8 để có được một vấn đề thừa kế, nhưng nó vẫn không phải là một vấn đề diamon như vậy.

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.

Như JB Nizet bình luận, bạn có thể sửa lỗi này.

class AB implements A, B {
    public void hi() { A.super.hi(); }
}

Tuy nhiên, bạn không có vấn đề gì với

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.

ồ Đây là mới với tôi. Tại sao họ phải tạo mặc định trong java 8?
Erran Morad

1
Để tạo điều kiện cho việc thêm các phương thức mới vào các giao diện (cụ thể là các giao diện bộ sưu tập) mà không phá vỡ 60% cơ sở mã.
Tassos Bassoukos

@BoratSagdiyev Lý do lớn nhất là hỗ trợ đóng cửa và làm cho hữu ích hơn. Xem Bộ sưu tập.stream (). Hãy xem List.sort () docs.oracle.com/javase/8/docs/api/java/util/ mẹo Họ đã thêm một phương thức cho tất cả các Danh sách mà không phải thay đổi bất kỳ triển khai cụ thể nào. Họ đã thêm Collection.remove
If

@TassosBassoukos +1 nói rằng bạn đã triển khai Danh sách của riêng mình, bây giờ bạn có thể myList.stream () nó hoặc myList.sort () mà không thay đổi mã của bạn
Peter Lawrey

3
@PeterLawrey: AB sẽ không biên dịch vì nó phải ghi đè hi()(để khắc phục sự mơ hồ). Ví dụ: bằng cách triển khai nó như A.super.hi()chọn thực hiện theo cách tương tự như A.
JB Nizet

20

Theo như trình biên dịch, hai phương thức này giống hệt nhau. Sẽ có một thực hiện của cả hai.

Đây không phải là vấn đề nếu hai phương pháp giống hệt nhau một cách hiệu quả, theo đó chúng nên có cùng một cách thực hiện. Nếu chúng khác nhau theo hợp đồng (theo tài liệu cho từng giao diện), bạn sẽ gặp rắc rối.


2
Nó giải thích tại sao Java không cho phép bạn mở rộng nhiều hơn một lớp
Arthur Ronald

1
@ArthurRonald, thực ra nó chỉ có vẻ liên quan. Tuy nhiên, IMO, lớp mở rộng nhiều hơn một lớp có thể gặp vấn đề Diamond (là trạng thái đối tượng trùng lặp trong lớp có nguồn gốc nhất) và đó rất có thể là lý do khiến Java đưa người dùng thoát khỏi những rắc rối. Mặt khác, lớp thực hiện nhiều hơn một lớp không bao giờ có thể chạy vào Diamond Problem chỉ vì giao diện không cung cấp trạng thái cho các đối tượng. Và vấn đề hoàn toàn là do giới hạn cú pháp - không có khả năng đủ điều kiện gọi hàm.
uvsmtid

13

Không có gì để xác định. Các giao diện chỉ đăng ký một tên phương thức và chữ ký. Nếu cả hai giao diện có một phương thức có cùng tên và chữ ký, lớp triển khai có thể thực hiện cả hai phương thức giao diện với một phương thức cụ thể.

Tuy nhiên, nếu các hợp đồng ngữ nghĩa của hai phương thức giao diện trái ngược nhau, bạn đã mất khá nhiều; bạn không thể thực hiện cả hai giao diện trong một lớp duy nhất.


4

Hãy thử thực hiện giao diện dưới dạng ẩn danh.

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

}

}

4

Như trong giao diện, chúng ta chỉ khai báo các phương thức, lớp cụ thể thực hiện cả hai giao diện này đều hiểu là chỉ có một phương thức (như bạn đã mô tả cả hai đều có cùng tên trong kiểu trả về). vì vậy không nên có vấn đề với nó. Bạn sẽ có thể định nghĩa phương thức đó trong lớp cụ thể.

Nhưng khi hai giao diện có một phương thức có cùng tên nhưng kiểu trả về khác nhau và bạn thực hiện hai phương thức trong lớp cụ thể:

Vui lòng xem mã dưới đây:

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;
  }
}

khi trình biên dịch nhận được phương thức "public void print ()", lần đầu tiên nó nhìn vào InterfaceA và nó nhận được. Nhưng nó vẫn đưa ra lỗi thời gian biên dịch mà kiểu trả về không tương thích với phương thức của InterfaceB.

Vì vậy, nó đi haywire cho trình biên dịch.

Theo cách này, bạn sẽ không thể thực hiện hai giao diện có một phương thức cùng tên nhưng kiểu trả về khác nhau.


3

Chà, nếu cả hai đều giống nhau thì không vấn đề gì. Nó thực hiện cả hai với một phương thức cụ thể cho mỗi phương thức giao diện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.