อินเทอร์เฟซ Marker ใน Java?


136

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

แต่เมื่อเร็ว ๆ นี้ฉันได้เรียนรู้ว่าจริงๆแล้วมันไม่มีส่วนเกี่ยวข้องกับคอมไพเลอร์หรือ JVM ยกตัวอย่างเช่นในกรณีของSerializableอินเตอร์เฟซที่วิธีการwriteObject(Object)ของObjectOutputStreamไม่บางอย่างเช่นinstanceOf Serializableการตรวจสอบว่าระดับการดำเนินการSerializableและพ่นNotSerializableExceptionตาม ทุกอย่างได้รับการจัดการในโค้ดและดูเหมือนว่าจะเป็นรูปแบบการออกแบบดังนั้นฉันคิดว่าเราสามารถกำหนดอินเทอร์เฟซเครื่องหมายของเราเองได้

ตอนนี้ข้อสงสัยของฉัน:

  1. คำจำกัดความของอินเทอร์เฟซเครื่องหมายที่กล่าวถึงข้างต้นในจุดที่ 1 ผิดหรือไม่? เราจะกำหนดอินเทอร์เฟซ Marker ได้อย่างไร?

  2. และแทนที่จะใช้ตัวinstanceOfดำเนินการทำไมเมธอดถึงไม่เป็นแบบwriteObject(Serializable)นั้นเพื่อให้มีการตรวจสอบประเภทเวลาคอมไพล์แทนรันไทม์?

  3. คำอธิบายประกอบดีกว่าอินเทอร์เฟซมาร์กเกอร์อย่างไร


8
Serializableเนื่องจากคำอธิบายประกอบเป็นเรื่องไร้สาระและ@NonNullเนื่องจากอินเทอร์เฟซเป็นเรื่องไร้สาระ ฉันจะบอกว่า: คำอธิบายประกอบคือเครื่องหมาย + ข้อมูลเมตา BTW: Forefunner of Annotations คือ XDoclet ซึ่งเกิดใน Javadoc และถูกฆ่าโดย Annotations
Grim

คำตอบ:


119
  1. คำจำกัดความของอินเทอร์เฟซเครื่องหมายที่กล่าวถึงข้างต้นในจุดที่ 1 ผิดหรือไม่? - ถูกต้องในส่วนที่ (1) อินเทอร์เฟซของเครื่องหมายต้องว่างเปล่าและ (2) การนำไปใช้นั้นมีจุดมุ่งหมายเพื่อบ่งบอกถึงการปฏิบัติพิเศษบางประการของคลาสการนำไปใช้งาน ส่วนที่ไม่ถูกต้องคือแสดงนัยว่า JVM หรือคอมไพเลอร์จะปฏิบัติต่ออ็อบเจ็กต์ของคลาสนั้นแตกต่างกัน: คุณมีความถูกต้องในการสังเกตว่าเป็นโค้ดของไลบรารีคลาส Java ที่ถือว่าอ็อบเจ็กต์เหล่านี้เป็นแบบโคลนนิ่งสามารถต่ออนุกรมได้ ฯลฯ แต่ก็มี ไม่เกี่ยวอะไรกับคอมไพเลอร์หรือ JVM
  2. แทนที่จะใช้ตัวดำเนินการ instanceOf ทำไมเมธอดจึงไม่เป็นเช่นwriteObject(Serializable)นั้นเพื่อให้มีการตรวจสอบประเภทเวลาคอมไพล์ - สิ่งนี้ช่วยให้คุณหลีกเลี่ยงการสร้างมลพิษให้กับรหัสของคุณด้วยชื่อของอินเทอร์เฟซเครื่องหมายเมื่อต้องการ "ธรรมดาObject" ตัวอย่างเช่นถ้าคุณสร้างคลาสที่ต้องทำให้เป็นอนุกรมและมีสมาชิกของอ็อบเจ็กต์คุณจะถูกบังคับให้ทำการแคสต์หรือสร้างอ็อบเจกต์ของคุณSerializableในเวลาคอมไพล์ สิ่งนี้ไม่สะดวกเนื่องจากอินเทอร์เฟซไม่มีฟังก์ชันใด ๆ
  3. คำอธิบายประกอบดีกว่าการเชื่อมต่อมาร์กเกอร์อย่างไร - ช่วยให้คุณบรรลุจุดประสงค์เดียวกันในการถ่ายทอดข้อมูลเมตาเกี่ยวกับคลาสให้กับผู้บริโภคโดยไม่ต้องสร้างประเภทแยกต่างหาก คำอธิบายประกอบมีประสิทธิภาพมากขึ้นเช่นกันทำให้โปรแกรมเมอร์ส่งผ่านข้อมูลที่ซับซ้อนมากขึ้นไปยังชั้นเรียนที่ "ใช้" ข้อมูลนั้น

14
วิธีที่ฉันเข้าใจมาโดยตลอดก็คือคำอธิบายประกอบเป็น 'Marker Interfaces 2.0' ประเภทหนึ่ง: ต่อเนื่องได้ตั้งแต่ Java 1.1 คำอธิบายประกอบถูกเพิ่มใน 1.5
blagae

และเนื่องจากwriteObjectเป็นแบบส่วนตัวนั่นหมายความว่าตัวอย่างเช่นคลาสไม่จำเป็นต้องเรียกใช้ superclass
ratchet freak

15
สิ่งหนึ่งที่ควรทราบก็คือมีการเพิ่มคำอธิบายประกอบลงในภาษาหลายปีหลังจากที่ห้องสมุดมาตรฐานได้รับการออกแบบในตอนแรก หากคำอธิบายประกอบเป็นภาษาตั้งแต่เริ่มต้นเป็นที่น่าสงสัยว่า Serializable น่าจะเป็นอินเทอร์เฟซอาจเป็นคำอธิบายประกอบ
Theodore Norvell

ในกรณีที่Cloneableไม่ชัดเจนว่าเป็นไลบรารีหรือการรักษา JVM ของอินสแตนซ์ที่มีการเปลี่ยนแปลง
Holger

"[... ] โดยไม่ต้องสร้างประเภทแยกต่างหาก" - ฉันจะบอกว่านี้เป็นสิ่งที่แยกพวกเขา: อินเตอร์เฟซที่เครื่องหมายไม่แนะนำประเภทในขณะที่คำอธิบายประกอบ (ชนิดของ) กวน
aioobe

22

มันเป็นไปไม่ได้ที่จะบังคับใช้SerializableในwriteObjectเพราะเด็กของชั้นไม่ใช่ serializable สามารถ serializable แต่กรณีของพวกเขาอาจจะกลับไป upcasted ระดับผู้ปกครอง ด้วยเหตุนี้การอ้างอิงถึงสิ่งที่ไม่ต่ออนุกรม (เช่นObject) ไม่ได้หมายความว่าอินสแตนซ์ที่อ้างถึงไม่สามารถต่ออนุกรมได้จริงๆ ตัวอย่างเช่นใน

   Object x = "abc";
   if (x instanceof Serializable) {
   }

คลาสพาเรนต์ ( Object) ไม่สามารถต่ออนุกรมกันได้และจะเริ่มต้นโดยใช้คอนสตรัคเตอร์ที่ไม่มีพารามิเตอร์ ค่าอ้างอิงโดยx, Stringเป็น serializable และคำสั่งเงื่อนไขจะวิ่ง


7

ฉันได้ทำการสาธิตง่ายๆเพื่อไขข้อสงสัยข้อ 1 และ 2:

เราจะมีอินเทอร์เฟซที่เคลื่อนย้ายได้ซึ่งจะนำไปใช้โดยMobilePhone.javaคลาสและอีกหนึ่งคลาสLandlinePhone.javaที่ไม่ได้ใช้อินเตอร์เฟซที่เคลื่อนย้ายได้

อินเทอร์เฟซเครื่องหมายของเรา:

package com;

public interface Movable {

}

LandLinePhone.java และ MobilePhone.java

 package com;

 class LandLinePhone {
    // more code here
 }
 class MobilePhone implements Movable {
    // more code here
 }

Custom Exception Class ของเรา: package com;

public class NotMovableException extends Exception {

private static final long serialVersionUID = 1L;

    @Override
    public String getMessage() {
        return "this object is not movable";
    }
    // more code here
    }

ชั้นทดสอบของเรา: TestMArkerInterface.java

package com;

public class TestMarkerInterface {

public static void main(String[] args) throws NotMovableException {
    MobilePhone mobilePhone = new MobilePhone();
    LandLinePhone landLinePhone = new LandLinePhone();

    TestMarkerInterface.goTravel(mobilePhone);
    TestMarkerInterface.goTravel(landLinePhone);
}

public static void goTravel(Object o) throws NotMovableException {
    if (!(o instanceof Movable)) {
        System.out.println("you cannot use :" + o.getClass().getName() + "   while travelling");
        throw new NotMovableException();
    }

    System.out.println("you can use :" + o.getClass().getName() + "   while travelling");
}}

ตอนนี้เมื่อเราดำเนินการคลาสหลัก:

you can use :com.MobilePhone while travelling
you cannot use :com.LandLinePhone while travelling
Exception in thread "main" com.NotMovableException: this object is not movable
    at com.TestMarkerInterface.goTravel(TestMarkerInterface.java:22)
    at com.TestMarkerInterface.main(TestMarkerInterface.java:14)

ดังนั้นคลาสใดที่ใช้อินเทอร์เฟซเครื่องหมายMovableจะผ่านการทดสอบข้อความแสดงข้อผิดพลาดอื่นจะปรากฏขึ้น

นี่คือวิธีinstanceOfการตรวจสอบตัวดำเนินการสำหรับSerializable , Cloneableฯลฯ


6

อินเทอร์เฟซเครื่องหมายใน Java เป็นอินเทอร์เฟซที่ไม่มีฟิลด์หรือเมธอด พูดง่ายๆว่าอินเทอร์เฟซว่างใน Java เรียกว่าอินเทอร์เฟซเครื่องหมาย ตัวอย่างของการเชื่อมต่อเป็นเครื่องหมายSerializable, CloneableและRemoteอินเตอร์เฟซ สิ่งเหล่านี้ใช้เพื่อระบุข้อมูลบางอย่างให้กับคอมไพเลอร์หรือ JVM ดังนั้นหาก JVM เห็นว่ามีคลาสอยู่Serializableก็สามารถดำเนินการพิเศษกับมันได้ ในทำนองเดียวกันถ้า JVM เห็นบางคลาสกำลังใช้งานอยู่Cloneableก็สามารถดำเนินการบางอย่างเพื่อสนับสนุนการโคลนได้ เช่นเดียวกับ RMI และRemoteอินเทอร์เฟซ ดังนั้นในระยะสั้นอินเทอร์เฟซของเครื่องหมายระบุสัญญาณหรือคำสั่งไปยังคอมไพเลอร์หรือ JVM

ข้างต้นเริ่มต้นจากสำเนาของโพสต์บล็อกแต่ได้รับการแก้ไขเล็กน้อยสำหรับไวยากรณ์


6
คัดลอกมาได้เลย แต่กรุณาระบุแหล่งที่มาด้วย: javarevisited.blogspot.com/2012/01/… . นอกจากนี้จะเป็นการดีถ้าคุณไม่คัดลอกวางข้อผิดพลาดในการสะกดด้วย :)
Saurabh Patil

5

อินเทอร์เฟซ a / A marker ตามชื่อที่แนะนำมีไว้เพื่อแจ้งทุกสิ่งที่รู้ว่าคลาสนั้นประกาศบางสิ่ง ทุกอย่างสามารถเป็นคลาส JDK สำหรับSerializableอินเทอร์เฟซหรือคลาสใดก็ได้ที่คุณเขียนของคุณเองสำหรับคลาสที่กำหนดเอง

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

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


4
  1. ไม่มีอะไรต้องทำ (จำเป็น) กับ JVM และคอมไพเลอร์มันมีส่วนเกี่ยวข้องกับโค้ดใด ๆ ที่สนใจและกำลังทดสอบอินเทอร์เฟซเครื่องหมายที่กำหนด

  2. เป็นการตัดสินใจในการออกแบบและทำด้วยเหตุผลที่ดี ดูคำตอบจาก Audrius Meškauskas

  3. สำหรับหัวข้อนี้โดยเฉพาะฉันไม่คิดว่ามันจะดีขึ้นหรือแย่ลง อินเทอร์เฟซของเครื่องหมายกำลังทำในสิ่งที่ควรจะทำได้ดี


คุณสามารถเพิ่มลิงก์ "คำตอบจาก Audrius Meškauskas" ได้หรือไม่ ฉันไม่เห็นอะไรในหน้านี้ที่มีชื่อนั้น
Sarah Messer

3

ก. ฉันมองว่ามันเป็นรูปแบบการออกแบบมาโดยตลอดและไม่มี JVM-Special ที่ฉันใช้รูปแบบนั้นในหลาย ๆ สถานการณ์

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

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


2

จุดประสงค์หลักของอินเทอร์เฟซมาร์กเกอร์คือการสร้างชนิดพิเศษโดยที่ประเภทเองไม่มีพฤติกรรมของตัวเอง

public interface MarkerEntity {

}

public boolean save(Object object) throws InvalidEntityFoundException {
   if(!(object instanceof MarkerEntity)) {
       throw new InvalidEntityFoundException("Invalid Entity Found, can't be  saved);
   } 
   return db.save(object);
}

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

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

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

แหล่งที่มาสำหรับความคิดเห็นส่วนต่อประสานเครื่องหมาย


1
+1 เพื่อแสดงให้เห็นว่ามันถูกใช้เป็นเพียงเบ็ด "ความปลอดภัย" ที่โปรแกรมเมอร์ต้องบอกอย่างชัดเจนว่าสามารถบันทึกลงในฐานข้อมูลได้
Ludvig W

1

ฉันขอโต้แย้งก่อนว่า Serializable และ Cloneable เป็นตัวอย่างที่ไม่ดีของส่วนต่อประสานเครื่องหมาย แน่นอนว่าพวกเขาเชื่อมต่อกับวิธีการ แต่มันบ่งบอกถึงวิธีการเช่นwriteObject(ObjectOutputStream)วิธีการเช่น(คอมไพเลอร์จะสร้างwriteObject(ObjectOutputStream)เมธอดให้คุณหากคุณไม่ได้ลบล้างและอ็อบเจ็กต์ทั้งหมดมีอยู่แล้วclone()แต่คอมไพเลอร์จะสร้างclone()เมธอดจริงให้คุณอีกครั้งแต่มีข้อแม้ทั้งสองอย่างนี้เป็นเคสขอบแปลก ๆ ที่ไม่ได้จริงๆ ตัวอย่างการออกแบบที่ดี)

โดยทั่วไปอินเทอร์เฟซมาร์กเกอร์จะใช้เพื่อวัตถุประสงค์หนึ่งในสองประการ:

1) เป็นทางลัดในการหลีกเลี่ยงประเภทที่ยาวเกินไปซึ่งอาจเกิดขึ้นได้กับยาสามัญจำนวนมาก ตัวอย่างเช่นสมมติว่าคุณมีลายเซ็นวิธีนี้:

public void doSomething(Foobar<String, Map<String, SomethingElse<Integer, Long>>>) { ... }

มันยุ่งและน่ารำคาญในการพิมพ์และที่สำคัญกว่านั้นคือเข้าใจยาก พิจารณาสิ่งนี้แทน:

public interface Widget extends Foobar<String, Map<String, SomethingElse<Integer, Long>>> { }

วิธีการของคุณมีลักษณะดังนี้:

public void doSomething(Widget widget) { ... }

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

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

public interface IterableWidget extends Iterable<String>, Widget { }

และในรหัสของคุณ:

public void doSomething(IterableWidget widget) {
    for (String s : widget) { ... }
}

1
คอมไพลเลอร์จะไม่สร้างเมธอดใด ๆ หากคลาสของคุณใช้SerializableหรือCloneable. คุณสามารถตรวจสอบได้โดยการตรวจสอบไฟล์ชั้นเรียนของคุณ นอกจากนี้การสร้าง "อินเทอร์เฟซทางลัด" ไม่ใช่สิ่งที่เกี่ยวกับอินเทอร์เฟซเครื่องหมาย และเป็นการฝึกเขียนโค้ดที่ไม่ดีอย่างแท้จริงเนื่องจากจะต้องมีการสร้างคลาสการใช้งานเพิ่มเติมเพื่อตอบสนองอินเทอร์เฟซเพิ่มเติม อย่างไรก็ตาม Java มีสี่แยกประเภทมานานกว่าทศวรรษแล้ว เรียนรู้เกี่ยวกับ Generics …
Holger

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

0

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

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