Java `วิธีสุดท้าย ': มันสัญญาอะไร?


141

ในคลาส Java สามารถกำหนดวิธีเป็นfinalเพื่อทำเครื่องหมายว่าวิธีนี้อาจไม่ถูกแทนที่:

public class Thingy {
    public Thingy() { ... }
    public int operationA() {...}
    /** this method does @return That and is final. */
    public final int getThat() { ...}
}

ชัดเจนและอาจเป็นประโยชน์ในการป้องกันการทับซ้อนโดยไม่ตั้งใจหรืออาจเป็นการทำงาน - แต่นั่นไม่ใช่คำถามของฉัน

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

ข้อ จำกัด ทางวากยสัมพันธ์ชัดเจนสำหรับฉัน แต่ความหมายของ OOP คืออะไร? มีการfinalใช้อย่างถูกต้องในแง่นี้โดยผู้เขียนชั้นเรียนมากที่สุด?

วิธีการ "สัญญา" ประเภทใดที่finalสัญญา?

คำตอบ:


156

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

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

มีเหตุผลหลายประการที่ทำให้ไม่สามารถปรับแต่งบางสิ่งได้รวมถึง:

  • ประสิทธิภาพ - คอมไพเลอร์บางตัวสามารถวิเคราะห์และเพิ่มประสิทธิภาพการทำงานโดยเฉพาะอย่างยิ่งสิ่งที่ไม่มีผลข้างเคียง

  • รับข้อมูลที่ห่อหุ้ม - ดูที่ Object ที่ไม่เปลี่ยนรูปแบบซึ่งมีการตั้งค่าคุณลักษณะของพวกเขาในเวลาการก่อสร้างและไม่ควรเปลี่ยนแปลง หรือค่าที่คำนวณได้มาจากแอตทริบิวต์เหล่านั้น ตัวอย่างที่ดีคือStringคลาสJava

  • ความน่าเชื่อถือและสัญญา - วัตถุที่มีองค์ประกอบของ primitives ( int, char, doubleฯลฯ ) และ / หรือวัตถุอื่น ๆ การดำเนินการบางอย่างที่ไม่สามารถใช้งานได้กับองค์ประกอบเหล่านั้นควรใช้งานได้หรือมีเหตุผลเมื่อใช้ในวัตถุที่ใหญ่กว่า วิธีการที่มีfinalตัวดัดแปลงสามารถใช้เพื่อให้แน่ใจว่า คลาส Counter เป็นตัวอย่างที่ดี


public class Counter {
    private int counter = 0;

    public final int count() {
        return counter++;
    }

    public final int reset() {
        return (counter = 0);
    }
}

หากpublic final int count()วิธีนี้ไม่ใช่finalเราสามารถทำสิ่งนี้:

Counter c = new Counter() {   
    public int count() {
        super.count();   
        return super.count();   
    } 
}

c.count(); // now count 2

หรืออะไรทำนองนี้:

Counter c = new Counter() {
    public int count() {
        int lastCount = 0;
        for (int i = super.count(); --i >= 0; ) {
            lastCount = super.count();
        }

        return lastCount;
    }
}

c.count(); // Now double count

27

"สัญญา" ประเภทใดที่เป็นวิธีสุดท้ายที่สัญญาไว้?

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


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

8

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

ฉันยอมรับว่าวิธีการทำเครื่องหมายfinalไม่รับประกันว่าพฤติกรรมของพวกเขาจะเหมือนกันในคลาสย่อยหากวิธีการเหล่านี้เรียกวิธีที่ไม่เป็นขั้นสุดท้าย หากพฤติกรรมจำเป็นต้องได้รับการแก้ไขแน่นอนสิ่งนี้จะต้องได้รับจากการประชุมและการออกแบบอย่างระมัดระวัง และอย่าลืมความคิดนี้ใน javadoc! (เอกสารคู่มือ java)

สุดท้าย แต่ไม่ท้ายสุดfinalคำหลักมีบทบาทสำคัญใน Java Memory Model (JMM) มันรับประกันโดย JMM ว่าเพื่อให้สามารถมองเห็นfinalฟิลด์ที่คุณไม่ต้องการการซิงโครไนซ์ที่เหมาะสม เช่น:

class A implements Runnable {
  final String caption = "Some caption";                           

  void run() {
    // no need to synchronize here to see proper value of final field..
    System.out.println(caption);
  }
}  

ใช่ฉันรู้เกี่ยวกับชั้นเรียนสุดท้าย - กรณีง่าย จุดดีเกี่ยวกับเขตข้อมูลสุดท้ายด้วย JMM ไม่จำเป็นต้องซิงค์ ... hmm: นี่หมายถึง "ตัวชี้" เท่านั้นใช่มั้ย ฉันยังคงสามารถปรับเปลี่ยนการซิงค์วัตถุที่อ้างถึงได้ (ตกลงไม่ใช่ในStringแต่ในชั้นเรียนที่ผู้ใช้กำหนด) แต่สิ่งที่คุณพูดเกี่ยวกับ "สุดท้ายไม่ได้รับประกันพฤติกรรม" คือตรงจุดของฉัน ฉันเห็นด้วยเอกสารและการออกแบบเป็นสิ่งสำคัญ
towi

@towi คุณเหมาะสมที่จะไม่ให้การแสดงผลของการเปลี่ยนแปลงที่เกิดขึ้นกับวัตถุสารประกอบเช่นfinal Map
Victor Sorokin

0

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


1
ใช่นั่นคือสิ่งที่ฉันหมายถึง ขวา. ผมชอบคำว่า "รับประกันอ่อนแอ" :-) และฉัน (ส่วนใหญ่) เช่น C ++ const's ในฐานะที่เป็นchar const * const = "Hello"หรือchar const * const addName(char const * const name) const...
towi

0

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

http://download.oracle.com/javase/tutorial/java/IandI/final.html

มูลค่า noting finalเป็นส่วนหนึ่งที่มันแสดงให้เห็นว่าวิธีการที่เรียกว่าจากการก่อสร้างควรจะเป็น


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