เหตุใดฉันจึงไม่สามารถประกาศวิธีการคงที่ในส่วนต่อประสานได้


150

หัวข้อบอกว่าส่วนใหญ่ - เหตุผลสำหรับความจริงที่ว่าวิธีการคงที่ไม่สามารถประกาศในอินเตอร์เฟซคืออะไร?

public interface ITest {
    public static String test();
}

รหัสด้านบนให้ข้อผิดพลาดต่อไปนี้กับฉัน (ใน Eclipse อย่างน้อย): "ตัวดัดแปลงที่ผิดกฎหมายสำหรับวิธีการเชื่อมต่อ ITest.test (); อนุญาตให้สาธารณะ & นามธรรมเท่านั้น"


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

ฉันเห็นด้วยกับคำตอบที่ได้รับจาก "erickson" stackoverflow.com/questions/512877/…
Maverick

9
สิ่งนี้จะมีใน Java 8 btw
m0skit0

1
@Vadorequest GIYF แต่อย่างไรก็ตามตรวจสอบที่นี่
m0skit0

2
ลิงก์จากเอกสารราชการ: Java SE บทช่วยสอน & ข้อกำหนดภาษา Java 9.2
LoganMzz

คำตอบ:


85

มีปัญหาเล็กน้อยที่เล่นอยู่ที่นี่ ประการแรกคือปัญหาของการประกาศวิธีคงที่โดยไม่ต้องกำหนดมัน นี่คือความแตกต่างระหว่าง

public interface Foo {
  public static int bar();
}

และ

public interface Foo {
  public static int bar() {
    ...
  }
}

ประการแรกเป็นไปไม่ได้เนื่องจากเหตุผลที่Espoกล่าวถึง: คุณไม่รู้ว่าการใช้คลาสใดเป็นคำจำกัดความที่ถูกต้อง

Java สามารถอนุญาตหลัง; และในความเป็นจริงเริ่มต้นใน Java 8 มันไม่!


2
ใช่ - มันไม่ใช่เชิงเทคนิค เหตุผลที่ฉันต้องการคือ ที่สามารถมีวิธีการ "ใช้งาน" คงที่ในส่วนติดต่อที่อ้างอิงวิธี "ส่วนต่อประสาน" อื่น ๆ ในส่วนต่อประสานที่สามารถนำกลับมาใช้ใหม่ได้อย่างง่ายดายโดยการนำคลาสมาใช้ แต่เราสามารถประกาศคลาสแบบสแตติกในอินเทอร์เฟซได้ดังนั้นเราจึงสามารถมีสิ่งต่าง ๆ เช่น MyInterface.Impl.doIt (MyInterface i, Object [] args) {... }
peterk

9
ตั้งแต่ Java 8 คุณสามารถกำหนดวิธีการในstatic วิธีการที่จะต้องinterface public
Olivier Grégoire

4
@ OlivierGrégoire ... และพวกเขาไม่ได้รับมรดกซึ่งเป็นกุญแจสำคัญ
William F. Jameson

1
คำตอบที่ดีแม้ว่า "ROFLMAO xD" ที่เทียบเท่ากันโดยประมาณฉันจะทำให้มันคล้ายกับ "ค่อนข้างคล้าย"
Timo

44

เหตุผลที่คุณไม่สามารถมีวิธีการแบบคงที่ในส่วนติดต่ออยู่ในวิธีที่ Java แก้ไขการอ้างอิงแบบคงที่ Java จะไม่รบกวนการค้นหาอินสแตนซ์ของคลาสเมื่อพยายามเรียกใช้เมธอดแบบสแตติก นี่เป็นเพราะวิธีการคงที่ไม่ได้ขึ้นอยู่กับอินสแตนซ์และด้วยเหตุนี้สามารถดำเนินการได้โดยตรงจากไฟล์คลาส ระบุว่าวิธีการทั้งหมดในส่วนต่อประสานเป็นนามธรรม, VM จะต้องมองหาการใช้งานเฉพาะของส่วนต่อประสานเพื่อหารหัสที่อยู่เบื้องหลังวิธีการคงที่เพื่อให้สามารถดำเนินการได้ วิธีนี้จะขัดแย้งกับวิธีการแก้ปัญหาวิธีการคงที่และจะแนะนำความไม่ลงรอยกันในภาษา


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

ไม่ใช่ว่าทุกประเภทอินเตอร์เฟสใน Java อยู่ในไฟล์ของตัวเองและไม่ควรเป็นไปตาม JLS นอกจากนี้ JLS ไม่ได้กำหนดว่าคลาสจะต้องจัดเก็บไว้ในระบบไฟล์เสมอ
Vlad Gudim

4
@Totophil: การเชื่อมต่อจะต้องไม่อยู่ในไฟล์จาวาเดียว แต่มันจะมีไฟล์คลาสของตัวเองหลังจากรวบรวม นั่นคือสิ่งที่ฉันเขียน
Mnementh

18

ฉันจะตอบคำถามของคุณด้วยตัวอย่าง สมมติว่าเรามีคลาสคณิตศาสตร์พร้อมกับวิธีการแบบคงที่เพิ่ม คุณจะเรียกวิธีนี้ว่า:

Math.add(2, 3);

ถ้า Math เป็นอินเตอร์เฟสแทนคลาสมันจะไม่มีฟังก์ชันที่กำหนดไว้ เช่นนี้การพูดอะไรบางอย่างเช่น Math.add (2, 3) ไม่มีเหตุผล


11

เหตุผลอยู่ในการออกแบบหลักการที่ java ไม่อนุญาตให้มีการสืบทอดหลายรายการ ปัญหาเกี่ยวกับการสืบทอดหลายรายการสามารถแสดงได้โดยตัวอย่างต่อไปนี้:

public class A {
   public method x() {...}
}
public class B {
   public method x() {...}
}
public class C extends A, B { ... }

ตอนนี้จะเกิดอะไรขึ้นถ้าคุณโทรหา Cx () จะใช้ Ax () หรือ Bx () ไหม ทุกภาษาที่มีหลายมรดกต้องแก้ปัญหานี้

การเชื่อมต่ออนุญาตให้ใช้ใน Java การสืบทอดหลายแบบ จำกัด บางประเภท เพื่อหลีกเลี่ยงปัญหาข้างต้นพวกเขาไม่ได้รับอนุญาตให้มีวิธีการ หากเราดูปัญหาเดียวกันกับส่วนต่อประสานและวิธีการคงที่:

public interface A {
   public static method x() {...}
}
public interface B {
   public static method x() {...}
}
public class C implements A, B { ... }

ปัญหาเดียวกันที่นี่จะเกิดอะไรขึ้นถ้าคุณโทรหา Cx ()


เหตุผลใดสำหรับ downvote ความคิดเห็นที่อธิบายจะดี
Mnementh

ฉันไม่ใช่ผู้ลงคะแนน แต่นี่ไม่ถูกต้องสำหรับวิธีการที่ไม่คงที่ด้วยหรือไม่
nawfal

ตกลงนี่คือความเป็นไปได้สองอย่างที่แตกต่างกัน วิธีการสามารถนำมาใช้หรือประกาศเท่านั้น ฉันเข้าใจว่าต้องใช้วิธีการคงที่ ในความหมายนั้นฉันพบปัญหาที่นำเสนอในคำตอบของฉัน หากคุณไม่ทำเช่นนั้นคุณพบปัญหาที่ Espo อธิบายไว้และฉันไม่เข้าใจเพราะฉันคิดว่าจะใช้วิธีการคงที่ คุณไม่สามารถประกาศวิธีนามธรรมแบบคงที่ด้วยเหตุผลนี้ลองใช้งานคอมไพเลอร์จะบ่น
Mnementh

ตกลงลืมส่วนการดำเนินการ คำถามคือทำไมเราไม่ประกาศ ใช่ผู้แปลจะบ่นและทำไมเป็นคำถาม ที่ฉันไม่คิดว่าคุณได้ตอบ
nawfal

1
วิธีจะมีสถานการณ์ใด ๆ ที่เลวร้ายยิ่งกว่าที่มีอินเตอร์เฟซที่Aมีint x(int z);และอินเตอร์เฟซที่Bมีstring x(int x);? ความหมายของx(3)ในอินเตอร์เฟส C คืออะไร?
supercat

7

วิธีการแบบคงที่ไม่ใช่วิธีการแบบอินสแตนซ์ ไม่มีบริบทของอินสแตนซ์ดังนั้นการใช้จากอินเทอร์เฟซจึงสมเหตุสมผลเล็กน้อย


5

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

interface X {
    static void foo() {
       System.out.println("foo");
    }
}

class Y implements X {
    //...
}

public class Z {
   public static void main(String[] args) {
      X.foo();
      // Y.foo(); // won't compile because foo() is a Static Method of X and not Y
   }
}

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


4

มีคำตอบที่ดีมากและรัดกุมคำถามของคุณที่นี่ (มันทำให้ฉันเป็นวิธีที่อธิบายได้อย่างตรงไปตรงมาว่าฉันต้องการลิงก์จากที่นี่)


นี่ไม่ใช่คำตอบสำหรับคำถามที่ดีที่สุดควรเป็นความคิดเห็น
CubeJockey

3

ดูเหมือนว่าวิธีการคงที่ในอินเทอร์เฟซอาจได้รับการสนับสนุนในJava 8ด้วยวิธีแก้ปัญหาของฉันคือกำหนดไว้ในคลาสภายใน

interface Foo {
    // ...
    class fn {
        public static void func1(...) {
            // ...
        }
    }
}

เทคนิคเดียวกันนี้ยังสามารถใช้ในการเพิ่มความคิดเห็น:

public @interface Foo {
    String value();

    class fn {
        public static String getValue(Object obj) {
            Foo foo = obj.getClass().getAnnotation(Foo.class);
            return foo == null ? null : foo.value();
        }
    }
}

ควรเข้าถึงคลาสภายในในรูปแบบInterface.fn...แทนที่จะเป็นจากClass.fn...นั้นคุณสามารถกำจัดปัญหาที่ไม่ชัดเจน


2

อินเทอร์เฟซที่ใช้สำหรับ polymorphism ซึ่งใช้กับวัตถุไม่ใช่ชนิด ดังนั้น (ดังที่ระบุไว้แล้ว) มันไม่มีเหตุผลที่จะมีสมาชิกส่วนต่อประสานแบบคงที่


ในบริบทที่ไตร่ตรองดูเหมือนว่าจะสมเหตุสมผลแล้ว
Cruncher

1

Java 8 เปลี่ยนโลกของคุณคุณสามารถมีวิธีการคงที่ในส่วนต่อประสาน แต่มันบังคับให้คุณต้องดำเนินการตามนั้น

public interface StaticMethodInterface {
public static int testStaticMethod() {
    return 0;
}

/**
 * Illegal combination of modifiers for the interface method
 * testStaticMethod; only one of abstract, default, or static permitted
 * 
 * @param i
 * @return
 */
// public static abstract int testStaticMethod(float i);

default int testNonStaticMethod() {
    return 1;
}

/**
 * Without implementation.
 * 
 * @param i
 * @return
 */
int testNonStaticMethod(float i);

}


0

การรวมกันของการดัดแปลงที่ผิดกฎหมาย: แบบคงที่และนามธรรม

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

หากสมาชิกของคลาสถูกประกาศเป็น abstract คุณต้องประกาศคลาสเป็น abstract และคุณต้องจัดให้มีการใช้งานสมาชิก abstract ในคลาสที่สืบทอด (Sub-Class)

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


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

0

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


วิธีการแบบสแตติกจะสืบทอดมาตลอด แต่ไม่สามารถเขียนทับได้
Akki

0

ด้วยJava 8ตอนนี้อินเตอร์เฟสสามารถมีเมธอดแบบสแตติก

ตัวอย่างเช่น Comparator มีเมธอด naturalOrder () แบบคงที่

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


พระเจ้าช่วย! เรามีโปรแกรมเมอร์ที่จริงจัง (และฉันผู้วิจารณ์) ตอบคำถาม (และฉันแสดงความคิดเห็น) คำถามอายุ 10 ปี
Mohamed Anees

ฉันไม่ได้สังเกตวันที่ :)
Ishara

ฮ่าฮ่า! ไม่มีปัญหา!
Mohamed Anees A

-2

บางทีตัวอย่างรหัสอาจช่วยได้ฉันจะใช้ C # แต่คุณควรทำตามได้

ให้แกล้งทำเป็นว่าเรามีส่วนต่อประสานที่เรียกว่า IPayable

public interface IPayable
{
    public Pay(double amount);
}

ตอนนี้เรามีคลาสคอนกรีตสองคลาสที่ใช้อินเตอร์เฟสนี้:

public class BusinessAccount : IPayable
{
    public void Pay(double amount)
    {
        //Logic
    }
}

public class CustomerAccount : IPayable
{
    public void Pay(double amount)
    {
        //Logic
    }
}

ตอนนี้ให้แกล้งเรามีคอลเลกชันของบัญชีต่าง ๆ การทำเช่นนี้เราจะใช้รายการทั่วไปของประเภท IPayable

List<IPayable> accountsToPay = new List<IPayable>();
accountsToPay.add(new CustomerAccount());
accountsToPay.add(new BusinessAccount());

ตอนนี้เราต้องการจ่าย $ 50.00 ให้กับบัญชีทั้งหมด:

foreach (IPayable account in accountsToPay)
{
    account.Pay(50.00);
}

ดังนั้นตอนนี้คุณจะเห็นว่าอินเตอร์เฟสมีประโยชน์อย่างไม่น่าเชื่ออย่างไร

พวกเขาจะใช้กับวัตถุที่ยกตัวอย่างเท่านั้น ไม่ได้อยู่ในคลาสแบบคงที่

หากคุณชำระเงินแบบคงที่เมื่อวนลูปผ่าน IPayable ในบัญชี ToPay จะไม่มีวิธีการคิดออกว่าควรจะเรียกชำระเงินกับ BusinessAcount หรือ CustomerAccount


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