เหตุใดจึงสามารถแก้ไขวัตถุสุดท้ายได้


90

ฉันเจอรหัสต่อไปนี้ในฐานรหัสที่ฉันกำลังทำอยู่:

public final class ConfigurationService {
    private static final ConfigurationService INSTANCE = new ConfigurationService();
    private List providers;

    private ConfigurationService() {
        providers = new ArrayList();
    }

    public static void addProvider(ConfigurationProvider provider) {
        INSTANCE.providers.add(provider);
    }

    ...

INSTANCEถูกประกาศเป็นfinal. ทำไมจึงสามารถเพิ่มวัตถุเข้าไปได้INSTANCE? ไม่ควรทำให้การใช้ขั้นสุดท้ายเป็นโมฆะ (มันไม่)

ฉันสมมติว่าคำตอบต้องทำอะไรบางอย่างกับตัวชี้และหน่วยความจำ แต่อยากจะรู้แน่


ความเข้าใจผิดนี้เกิดขึ้นค่อนข้างบ่อยไม่จำเป็นต้องเป็นคำถาม มักเป็นคำตอบหรือแสดงความคิดเห็น
Robin

5
คำอธิบายง่ายๆจาก JLS: "ถ้าตัวแปรสุดท้ายมีการอ้างอิงถึงอ็อบเจ็กต์สถานะของอ็อบเจ็กต์อาจเปลี่ยนแปลงได้โดยการดำเนินการกับอ็อบเจ็กต์ แต่ตัวแปรจะอ้างถึงอ็อบเจ็กต์เดียวกันเสมอ" เอกสาร JLS
realPK

คำตอบ:


163

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


1
+1 สำหรับรายละเอียดเพิ่มเติมโปรดตรวจสอบjava.sun.com/docs/books/jls/second_edition/html/…ส่วน 4.5.4
Abel Morelos

สมมติว่าฉันยกเลิกการกำหนดค่าสถานะของConfigurationServiceวัตถุและฉันพยายามทำ INSTANCE = deserializedConfigurationServiceจะไม่ได้รับอนุญาต?
diegoaguilar

คุณไม่สามารถกำหนดINSTANCEให้อ้างถึงวัตถุอื่นได้ ไม่สำคัญว่าวัตถุอื่นมาจากไหน (NB มีหนึ่งรายการINSTANCEต่อClassLoaderคลาสที่โหลดคลาสนี้ในทางทฤษฎีคุณสามารถโหลดคลาสได้หลายครั้งใน JVM เดียวและแต่ละคลาสแยกจากกัน แต่นี่เป็นประเด็นทางเทคนิคที่แตกต่างกัน)
Sean Owen

@AkhilGite การแก้ไขคำตอบของฉันทำให้ผิด; จริงๆแล้วมันกลับความรู้สึกของประโยคซึ่งถูกต้อง ข้อมูลอ้างอิงไม่เปลี่ยนรูป วัตถุยังคงไม่แน่นอน มันไม่ "ไม่เปลี่ยนรูป"
Sean Owen

@SeanOwen Sry สำหรับการแก้ไขที่ฉันทำคำสั่งของคุณถูกต้องและขอบคุณ
AkhilGite

33

การเป็นขั้นสุดท้ายไม่เหมือนกับการไม่เปลี่ยนรูป

final != immutable

finalคำหลักที่ถูกนำมาใช้เพื่อให้แน่ใจว่าการอ้างอิงจะไม่เปลี่ยนแปลง (นั่นคือการอ้างอิงที่ยังไม่สามารถทดแทนด้วยใหม่)

แต่ถ้าแอตทริบิวต์นั้นเป็นของตัวเองสามารถแก้ไขได้ก็สามารถทำตามที่คุณได้อธิบายไว้ได้

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

class SomeHighLevelClass {
    public final MutableObject someFinalObject = new MutableObject();
}

ถ้าเรายกตัวอย่างชั้นนี้เราจะไม่สามารถที่จะกำหนดค่าอื่น ๆ ที่จะแอตทริบิวต์someFinalObjectเพราะมันเป็นครั้งสุดท้าย

ดังนั้นจึงเป็นไปไม่ได้:

....
SomeHighLevelClass someObject = new SomeHighLevelClass();
MutableObject impostor  = new MutableObject();
someObject.someFinal = impostor; // not allowed because someFinal is .. well final

แต่ถ้าวัตถุที่เป็นตัวมันเองเปลี่ยนแปลงได้เช่นนี้:

class MutableObject {
     private int n = 0;

     public void incrementNumber() {
         n++;
     }
     public String toString(){
         return ""+n;
     }
}  

จากนั้นค่าที่อยู่ในวัตถุที่เปลี่ยนแปลงได้นั้นอาจเปลี่ยนแปลงได้

SomeHighLevelClass someObject = new SomeHighLevelClass();

someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();

System.out.println( someObject.someFinal ); // prints 3

สิ่งนี้มีผลเช่นเดียวกับโพสต์ของคุณ:

public static void addProvider(ConfigurationProvider provider) {
    INSTANCE.providers.add(provider);
}

ที่นี่คุณไม่ได้เปลี่ยนค่าของ INSTANCE คุณกำลังแก้ไขสถานะภายใน (ผ่านวิธีการให้บริการเพิ่ม)

หากคุณต้องการป้องกันไม่ให้เปลี่ยนนิยามคลาสเช่นนี้:

public final class ConfigurationService {
    private static final ConfigurationService INSTANCE = new ConfigurationService();
    private List providers;

    private ConfigurationService() {
        providers = new ArrayList();
    }
    // Avoid modifications      
    //public static void addProvider(ConfigurationProvider provider) {
    //    INSTANCE.providers.add(provider);
    //}
    // No mutators allowed anymore :) 
....

แต่มันอาจไม่สมเหตุสมผลเท่าไหร่ :)

อย่างไรก็ตามคุณต้องซิงโครไนซ์การเข้าถึงโดยทั่วไปด้วยเหตุผลเดียวกัน


26

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

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


11

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

final ConfigurationService INSTANCE = new ConfigurationService();
ConfigurationService anotherInstance = new ConfigurationService();
INSTANCE = anotherInstance;

จะทำให้เกิดข้อผิดพลาดในการคอมไพล์


7

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

ที่มา

นี่คือคำแนะนำในการทำไม่เปลี่ยนรูปวัตถุ


4

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

INSTANCE = ...

ไม่เปลี่ยนรูปหมายความว่าวัตถุนั้นไม่สามารถแก้ไขได้ ตัวอย่างนี้คือjava.lang.Stringคลาส คุณไม่สามารถแก้ไขค่าของสตริง


2

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

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