การนำสองอินเตอร์เฟสมาใช้ในคลาสด้วยวิธีเดียวกัน วิธีการอินเทอร์เฟซใดถูกแทนที่


235

สองอินเตอร์เฟสที่มีชื่อเมธอดและลายเซ็นเหมือนกัน แต่ดำเนินการโดยชั้นเดียวแล้ววิธีการรวบรวมจะระบุวิธีการที่เป็นอินเตอร์เฟซที่?

Ex:

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

คำตอบ:


337

หากชนิดใช้สองอินเตอร์เฟสและแต่ละinterfaceวิธีกำหนดว่ามีลายเซ็นเหมือนกันดังนั้นจะมีเพียงวิธีเดียวเท่านั้นและจะไม่แยกความแตกต่าง หากกล่าวว่าทั้งสองวิธีมีประเภทผลตอบแทนที่ขัดแย้งกันมันจะเป็นข้อผิดพลาดในการรวบรวม นี่คือกฎทั่วไปของการสืบทอดวิธีการเอาชนะการซ่อนเร้นและการประกาศและนำไปใช้กับความขัดแย้งที่เป็นไปได้ไม่เพียง แต่ระหว่างinterfaceวิธีที่สืบทอดมา 2 วิธีเท่านั้น แต่ยังinterfaceเป็นclassวิธีการที่ยอดเยี่ยมและหรือแม้แต่ความขัดแย้ง


ตัวอย่างความเข้ากันได้

นี่คือตัวอย่างที่คุณมีinterface Giftซึ่งมีpresent()วิธีการ (ในขณะที่นำเสนอของขวัญ) และยังมีinterface Guestซึ่งนอกจากนี้ยังมีpresent()วิธีการ (ในขณะที่ผู้เข้าพักเป็นปัจจุบันและไม่ขาด)

Presentable johnnyเป็นทั้งและGiftGuest

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-quivalent" ( 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()และ a boolean present()ในหนึ่งประเภทตัวอย่างนี้ส่งผลให้เกิดข้อผิดพลาดในการรวบรวม


สรุป

คุณสามารถสืบทอดวิธีการที่มี@Override- เทียบเท่าภายใต้ข้อกำหนดปกติของวิธีการแทนที่และซ่อน เนื่องจากพวกเขาเป็น @Overrideเทียบเท่าได้อย่างมีประสิทธิภาพมีเพียงหนึ่งวิธีการที่จะใช้และทำให้ไม่มีอะไรที่จะแยกแยะ / เลือกจากเป็น

คอมไพเลอร์ไม่จำเป็นต้องระบุวิธีการใดที่จะใช้อินเทอร์เฟซเพราะเมื่อพวกเขาถูกกำหนดให้เป็น@Override- เทียบเท่ามันเป็นวิธีการเดียวกัน

การแก้ไขความเข้ากันไม่ได้ที่อาจเกิดขึ้นอาจเป็นงานที่ยุ่งยาก แต่นั่นก็เป็นอีกประเด็นหนึ่ง

อ้างอิง


ขอบคุณ - มันมีประโยชน์ อย่างไรก็ตามฉันมีคำถามเพิ่มเติมเกี่ยวกับความเข้ากันไม่ได้ซึ่งฉันได้โพสต์เป็นคำถามใหม่
แก้ไข

2
BTW สิ่งนี้เปลี่ยนแปลงไปเล็กน้อยด้วยการสนับสนุนdefaultวิธีการใน Java 8
Peter Lawrey

คอมโพสิตชั้นเรียนเพื่อแก้ไขความไม่ลงรอยกันที่อาจเกิดขึ้นอาจเป็นเคล็ดลับ :) แต่ฉันไม่เคยมีปัญหาดังกล่าวและยังคงเห็นได้ชัดว่ามันอาจเกิดขึ้น
กุมภ์อำนาจ

1
นี้บทความนำเสนอรูปแบบการออกแบบที่สามารถนำมาใช้เพื่อค่อนข้างจัดการกับสถานการณ์ที่คุณจำเป็นต้องใช้ทั้งสองชนอินเตอร์เฟซพูดและFoo Barโดยทั่วไปคุณต้องให้คลาสของคุณใช้หนึ่งในอินเตอร์เฟสพูดFooและจัดเตรียมBar asBar()วิธีการส่งคืนคลาสภายในที่ใช้Barอินเทอร์เฟซที่สอง ไม่สมบูรณ์แบบเนื่องจากคลาสของคุณไม่ได้เป็น "บาร์" แต่อาจมีประโยชน์ในบางสถานการณ์
Javaru

1
ฉันเป็นนักพัฒนาจาวา แต่ c # ฉลาดกว่านี้: stackoverflow.com/questions/2371178/ …
Amir Ziarati

25

สิ่งนี้ถูกทำเครื่องหมายว่าซ้ำกับคำถามนี้/programming/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.

ว้าว. นี่คือใหม่สำหรับฉัน ทำไมพวกเขาต้องสร้างค่าเริ่มต้นใน java 8?
Erran Morad

1
เพื่ออำนวยความสะดวกในการเพิ่มวิธีการใหม่ ๆ ในอินเทอร์เฟซ (โดยเฉพาะคอลเลกชันอินเทอร์เฟซ) โดยไม่ทำลายรหัสฐาน 60%
Tassos Bassoukos

@BoratSagdiyev เหตุผลที่ใหญ่ที่สุดคือการสนับสนุนการปิดและทำให้มีประโยชน์มากขึ้น ดู Collection.stream () ดูที่ List.sort () docs.oracle.com/javase/8/docs/api/java/util/ ...... พวกเขาได้เพิ่มวิธีการสำหรับรายการทั้งหมดโดยไม่ต้องเปลี่ยนการใช้งานเฉพาะใด ๆ พวกเขาเพิ่ม Collection.removeIf () ซึ่งมีประโยชน์
Peter Lawrey

@TassosBassoukos 1 บอกว่าคุณมีการดำเนินการของตัวเองของรายการตอนนี้คุณสามารถ myList.stream () หรือ myList.sort () มันไม่ได้เปลี่ยนรหัสของคุณ
ปีเตอร์ Lawrey

3
@PeterLawrey: AB จะไม่คอมไพล์เพราะต้องแทนที่hi()(เพื่อแก้ไขความกำกวม) ตัวอย่างเช่นโดยการใช้มันA.super.hi()เพื่อเลือกที่จะใช้มันแบบเดียวกับ A.
JB Nizet

20

เท่าที่คอมไพเลอร์เป็นห่วงทั้งสองวิธีจะเหมือนกัน จะมีการดำเนินการอย่างใดอย่างหนึ่งของทั้งสอง

นี่ไม่ใช่ปัญหาหากทั้งสองวิธีมีความเหมือนกันอย่างมีประสิทธิภาพโดยที่พวกเขาควรจะมีการใช้งานแบบเดียวกัน หากพวกเขาแตกต่างกันตามสัญญา (ตามเอกสารสำหรับแต่ละอินเทอร์เฟซ) คุณจะมีปัญหา


2
มันอธิบายว่าทำไม Java ไม่อนุญาตให้คุณขยายมากกว่าหนึ่งคลาส
Arthur Ronald

1
@ArthurRonald จริง ๆ แล้วมันก็ดูเกี่ยวข้อง อย่างไรก็ตาม IMO ซึ่งเป็นคลาสที่ขยายมากกว่าหนึ่งคลาสสามารถเรียกใช้เป็นปัญหาของไดมอนด์ (ซึ่งเป็นสถานะของวัตถุที่ซ้ำกันในคลาสที่ได้รับมามากที่สุด) และเป็นไปได้มากว่าทำไม Java จึงทำให้ผู้ใช้หลุดพ้นจากปัญหา ในทางกลับกันคลาสที่ใช้มากกว่าหนึ่งคลาสจะไม่สามารถพบปัญหาไดมอนด์ได้เนื่องจากอินเตอร์เฟสไม่ได้ระบุสถานะของวัตถุ และปัญหานั้นเกิดจากข้อ จำกัด ทางด้านไวยากรณ์อย่างสิ้นเชิง - ไม่สามารถที่จะเรียกฟังก์ชันได้อย่างเต็มที่
uvsmtid

13

ไม่มีอะไรจะระบุ อินเทอร์เฟซเฉพาะชื่อเมธอดและลายเซ็นเท่านั้น ถ้าทั้งสองอินเตอร์เฟสมีเมธอดที่มีชื่อและลายเซ็นเหมือนกันคลาสการนำไปใช้นั้นสามารถใช้ทั้งสองวิธีอินเตอร์เฟสด้วยวิธีรูปธรรมเดียว

อย่างไรก็ตามหากสัญญาความหมายของวิธีการเชื่อมต่อทั้งสองนั้นขัดแย้งกันคุณจะสูญเสียไปมาก คุณไม่สามารถใช้อินเทอร์เฟซทั้งสองในชั้นเดียวแล้ว


4

ลองปรับใช้อินเตอร์เฟสเป็นแบบไม่ระบุชื่อ

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

ในอินเทอร์เฟซเราเพิ่งประกาศวิธีคลาสคอนกรีตที่ใช้อินเทอร์เฟซเหล่านี้ทั้งสองเข้าใจว่ามีเพียงวิธีเดียว (ตามที่คุณอธิบายทั้งสองมีชื่อเดียวกันในประเภทกลับ) ดังนั้นไม่ควรมีปัญหากับมันคุณจะสามารถกำหนดวิธีการนั้นในคลาสที่เป็นรูปธรรม

แต่เมื่อสองอินเตอร์เฟสมีเมธอดที่มีชื่อเหมือนกัน แต่ชนิดการส่งคืนที่แตกต่างกันและคุณใช้สองวิธีในคลาสคอนกรีต:

โปรดดูรหัสด้านล่าง:

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 และมันจะรับมัน แต่ก็ยังทำให้เกิดข้อผิดพลาดในการคอมไพล์เวลาที่ return type ไม่เข้ากันกับ method ของ InterfaceB

ดังนั้นจึงไปยุ่งเหยิงสำหรับคอมไพเลอร์

ด้วยวิธีนี้คุณจะไม่สามารถใช้สองอินเทอร์เฟซที่มีวิธีการชื่อเดียวกัน แต่กลับชนิดที่แตกต่างกัน


3

ถ้าพวกเขาทั้งคู่เหมือนกันมันก็ไม่สำคัญ มันใช้ทั้งสองวิธีด้วยวิธีรูปธรรมเดียวต่อวิธีส่วนต่อประสาน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.