วัตถุประสงค์ของคีย์เวิร์ดเริ่มต้นใน Java คืออะไร?


98

อินเทอร์เฟซใน Java คล้ายกับคลาส แต่เนื้อหาของอินเทอร์เฟซสามารถมีได้เฉพาะเมธอดนามธรรมและfinalฟิลด์ (ค่าคงที่)

เมื่อเร็ว ๆ นี้ฉันเห็นคำถามซึ่งมีลักษณะเช่นนี้

interface AnInterface {
    public default void myMethod() {
        System.out.println("D");
    }
}

ตามคำจำกัดความของอินเทอร์เฟซอนุญาตให้ใช้วิธีนามธรรมเท่านั้น ทำไมจึงอนุญาตให้คอมไพล์โค้ดด้านบน? อะไรคือสิ่งที่defaultคำหลัก?

ในทางกลับกันเมื่อฉันพยายามเขียนโค้ดด้านล่างมันก็บอกว่า modifier default not allowed here

default class MyClass{

}

แทน

class MyClass {

}

ใครช่วยบอกจุดประสงค์ของdefaultคีย์เวิร์ดได้ไหม อนุญาตเฉพาะในอินเทอร์เฟซหรือไม่? มันแตกต่างจากdefault(ไม่มีตัวปรับการเข้าถึง) อย่างไร?


4
วิธีการเริ่มต้นในอินเทอร์เฟซถูกเพิ่มใน Java 8 ไม่ใช่ตัวแก้ไขการเข้าถึง แต่เป็นการใช้งานเริ่มต้น
Eran

2
@Eran คุณไม่คิดว่าการแนะนำวิธีการเริ่มต้นที่ละเมิดคำจำกัดความของอินเทอร์เฟซ? : s
ราวี

2
มันเปลี่ยนนิยามอินเตอร์เฟส คำจำกัดความนั้นล้าสมัยแล้ว
Louis Wasserman

2
พวกเขาได้รับการแนะนำให้สนับสนุน lambdas รายละเอียดว่าเหตุใดจึงจำเป็นต้องมีอยู่ในข้อเสนอของกลุ่มฟางสำหรับโครงการแลมด้า
ปรินเตอร์

คำตอบ:


76

เป็นคุณลักษณะใหม่ใน Java 8 ซึ่งช่วยinterfaceให้สามารถใช้งานได้ อธิบายไว้ใน Java 8 JLS-13.5.6 การประกาศวิธีการเชื่อมต่อซึ่งอ่าน (บางส่วน)

การเพิ่มdefaultเมธอดหรือการเปลี่ยนวิธีการจากabstractเป็นdefaultไม่ทำลายความเข้ากันได้กับไบนารีที่มีอยู่ก่อนหน้านี้ แต่อาจทำให้IncompatibleClassChangeErrorไบนารีที่มีอยู่แล้วพยายามเรียกใช้เมธอด ข้อผิดพลาดนี้เกิดขึ้นถ้าเป็นชนิดที่มีคุณสมบัติTเป็นชนิดย่อยของทั้งสองอินเตอร์เฟซIและการJที่ทั้งสองIและJประกาศdefaultวิธีการที่มีลายเซ็นเหมือนกันและผลและค่าIมิได้Jเป็น subinterface ของอื่น ๆ

มีอะไรใหม่ใน JDK 8กล่าว (บางส่วน)

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


17
ดูเหมือนว่าตอนนี้อินเทอร์เฟซและคลาสนามธรรมเกือบจะเหมือนกัน :)
ราวี

16
อินเทอร์เฟซ @jWeaver ยังไม่สามารถมีคอนสตรัคเตอร์ฟิลด์เมธอดส่วนตัวหรือการใช้งานเท่ากับ / hashCode / toString
Louis Wasserman

10
@ Louis Wasserman: ใน Java 9 พวกเขาสามารถมีprivateเมธอดได้
Holger

6
@ Dan Pantry: privateวิธีการไม่ได้เป็นส่วนหนึ่งของอินเทอร์เฟซ แต่อาจใช้เป็นวิธีการช่วยเหลือสำหรับการdefaultใช้งานหรือภายในตัวเริ่มต้นคงที่ โปรดสังเกตว่ามีอยู่แล้วใน Java 8 เมื่อคุณใช้นิพจน์แลมบ์ดาภายในอินเทอร์เฟซระบบprivateจะสร้างเมธอดสังเคราะห์ ดังนั้น Java 9 จึงช่วยให้คุณสามารถใช้คุณลักษณะนี้สำหรับการใช้งานที่ไม่ใช่แบบสังเคราะห์และไม่ใช่แลมบ์ดาได้เช่นกัน…
Holger

14
@jWeaver ความแตกต่างระหว่างการเชื่อมต่อและการเรียนลดรัฐ VS พฤติกรรม อินเทอร์เฟซสามารถมีพฤติกรรมได้ แต่มีเพียงคลาสเท่านั้นที่มีสถานะได้ (ฟิลด์ตัวสร้างและวิธีการเช่นเท่ากับ / hashCode เป็นเรื่องเกี่ยวกับสถานะ)
Brian Goetz

30

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

หากคุณต้องการความน่าเชื่อถือของข้อเรียกร้องที่defaultนำมาใช้เนื่องจาก lambdas โปรดทราบว่าข้อเสนอของStraw manของ Project Lambda โดย Mark Reinhold ในปี 2009 กล่าวถึง 'วิธีการขยาย' เป็นคุณสมบัติบังคับที่ต้องเพิ่มเพื่อรองรับแลมบดัส

นี่คือตัวอย่างที่แสดงให้เห็นถึงแนวคิด:

interface Operator {
    int operate(int n);
    default int inverse(int n) {
        return -operate(n);
    }
}

public int applyInverse(int n, Operator operator) {
    return operator.inverse(n);
}

applyInverse(3, n -> n * n + 7);

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


8
นี่ไม่ถูกต้องจริงๆ Lambdas อาจเป็นสาเหตุที่ใกล้เคียงกัน แต่จริงๆแล้วพวกมันเป็นเพียงฟางที่หักหลังอูฐ แรงจูงใจที่แท้จริงคือการเปิดใช้งานวิวัฒนาการของอินเทอร์เฟซ (ทำให้อินเทอร์เฟซที่มีอยู่สามารถพัฒนาอย่างเข้ากันได้เพื่อรองรับพฤติกรรมใหม่) lambdas อาจเป็นปัจจัยที่นำความต้องการนี้มาก่อน แต่คุณลักษณะนี้มีอยู่ทั่วไปมากกว่านั้น
Brian Goetz

@BrianGoetz: IMHO ทั้ง Java และ. NET จะได้รับประโยชน์อย่างมากเนื่องจากมีวิธีการเริ่มต้นจากการเริ่มต้น หากการดำเนินการทั่วไปบางอย่างสามารถทำได้กับการใช้งานอินเทอร์เฟซใด ๆ โดยใช้เฉพาะสมาชิกอินเทอร์เฟซ แต่การใช้งานบางอย่างน่าจะมีวิธีที่มีประสิทธิภาพมากกว่าอินเทอร์เฟซควรกำหนดวิธีการสำหรับการดำเนินการเหล่านั้นและจัดเตรียมการใช้งานเริ่มต้นสำหรับพวกเขา การไม่สามารถระบุการใช้งานเริ่มต้นได้ทำให้เกิดความกดดันให้อินเทอร์เฟซละเว้นวิธีการดังกล่าวและทำให้ไม่สามารถเพิ่มได้ในภายหลัง
supercat

@BrianGoetz ฉันยอมรับว่าวิธีการเริ่มต้นมีคุณค่าที่สำคัญเกินกว่า lambdas แต่ฉันสนใจในการอ้างอิงใด ๆ ที่คุณสามารถให้คุณค่าที่กว้างขึ้นซึ่งผลักดันให้มีการตัดสินใจรวมไว้ด้วย การอ่านของฉันคือ lambdas เป็นเหตุผลหลัก (ซึ่งเป็นสาเหตุที่ฉันใช้คำว่า 'เป็นหลัก' ในคำตอบของฉัน)
ปรินเตอร์

2
บางทีนี่อาจจะช่วยให้เอกสาร: cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html ส่วนที่ 10 กล่าวอย่างชัดเจนว่า: "จุดประสงค์ของวิธีการเริ่มต้น (ก่อนหน้านี้เรียกว่าวิธีการขยายเสมือนหรือวิธีการป้องกัน) คือการทำให้อินเทอร์เฟซสามารถพัฒนาในลักษณะที่เข้ากันได้หลังจากการเผยแพร่ครั้งแรก" วิธีการที่เป็นมิตรกับแลมบ์ดาจึงถูกอ้างถึงเป็นภาพประกอบของวิวัฒนาการของอินเทอร์เฟซ
Brian Goetz

2
@Kartik คุณกำลังถามคำถามผิด! เราไม่ได้เลือกไวยากรณ์ตาม "ค่าต่ำสุดสัมบูรณ์ที่จำเป็นสำหรับคอมไพเลอร์ในการแยกวิเคราะห์โปรแกรมอย่างถูกต้องคืออะไร"; เราเลือกโดยพิจารณาจาก "สิ่งที่จะทำให้เจตนาของโปรแกรมเมอร์ชัดเจนขึ้นสำหรับผู้อ่านในทันที" เราออกแบบสำหรับผู้ใช้เป็นอันดับแรกและอันดับสองสำหรับคอมไพเลอร์ (และเมื่อพูดถึงผู้ใช้เราออกแบบก่อนสำหรับการอ่านและอันดับที่สองสำหรับการเขียน)
Brian Goetz

17

สิ่งที่ถูกมองข้ามในคำตอบอื่น ๆ คือบทบาทของคำอธิบายประกอบ เมื่อย้อนกลับไปที่ Java 1.5 defaultคีย์เวิร์ดเป็นเครื่องมือในการระบุค่าเริ่มต้นสำหรับฟิลด์คำอธิบายประกอบ

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Processor {
    String value() default "AMD";
}

มีการใช้งานมากเกินไปด้วยการแนะนำ Java 8 เพื่อให้สามารถกำหนดวิธีการเริ่มต้นในอินเทอร์เฟซได้

อย่างอื่นที่ถูกมองข้าม: เหตุผลที่ประกาศdefault class MyClass {}ไม่ถูกต้องเป็นเพราะวิธีการที่เรียนมีการประกาศที่ทั้งหมด ไม่มีข้อกำหนดในภาษาที่อนุญาตให้คำหลักนั้นปรากฏที่นั่น มันไม่ปรากฏสำหรับการประกาศวิธีการอินเตอร์เฟซแม้ว่า


16

แนวคิดใหม่ถูกนำมาใช้ใน Java 8 ซึ่งเรียกว่าวิธีการเริ่มต้น วิธีการเริ่มต้นคือวิธีการที่มีการใช้งานเริ่มต้นและช่วยในการพัฒนาอินเทอร์เฟซโดยไม่ทำลายโค้ดที่มีอยู่ ลองดูตัวอย่าง:

 public interface SimpleInterface {
    public void doSomeWork();

    //A default method in the interface created using "default" keyword

    default public void doSomeOtherWork(){

    System.out.println("DoSomeOtherWork implementation in the interface");
       }
    }

 class SimpleInterfaceImpl implements SimpleInterface{

  @Override
  public void doSomeWork() {
  System.out.println("Do Some Work implementation in the class");
   }

 /*
  * Not required to override to provide an implementation
  * for doSomeOtherWork.
  */

 public static void main(String[] args) {
   SimpleInterfaceImpl simpObj = new SimpleInterfaceImpl();
   simpObj.doSomeWork();
   simpObj.doSomeOtherWork();
      }
   }

และผลลัพธ์คือ:

การใช้งาน Do Some Work ในคลาส
การใช้งาน DoSomeOtherWork ในอินเทอร์เฟซ


3

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

ตัวอย่างเช่น:

interface Test {
    default double getAvg(int avg) {
        return avg;
    }
}
class Tester implements Test{
 //compiles just fine
}

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

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

ข้อเท็จจริงและข้อ จำกัด :

1- อาจประกาศภายในอินเทอร์เฟซเท่านั้นและไม่อยู่ในคลาสหรือคลาสนามธรรม

2- ต้องจัดเตรียมร่างกาย

3- ไม่ถือว่าเป็นสาธารณะหรือนามธรรมเหมือนวิธีการปกติอื่น ๆ ที่ใช้ในอินเทอร์เฟซ


3

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

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

ด้วย Java 8 เราสามารถเพิ่มการใช้งานเริ่มต้นสำหรับวิธีการใหม่โดยใช้defaultคำสำคัญก่อนการใช้งานวิธีการ

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

ตัวอย่าง

public interface YourInterface {
    public void doSomeWork();

    //A default method in the interface created using "default" keyword
    default public void doSomeOtherWork(){

    System.out.println("DoSomeOtherWork implementation in the interface");
       }
    }

    class SimpleInterfaceImpl implements YourInterface{

     /*
     * Not required to override to provide an implementation
     * for doSomeOtherWork.
     */
      @Override
      public void doSomeWork() {
  System.out.println("Do Some Work implementation in the class");
   }

 /*
  * Main method
  */
 public static void main(String[] args) {
   SimpleInterfaceImpl simpObj = new SimpleInterfaceImpl();
   simpObj.doSomeWork();
   simpObj.doSomeOtherWork();
      }
   }

2

คำอธิบายที่ดีมีอยู่ในบทช่วยสอน Java ™ส่วนหนึ่งของคำอธิบายมีดังนี้:

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

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


1

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

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