यदि ऑब्जेक्ट के गैर-आदिम वाले फ़ील्ड को ऑब्जेक्ट हैंडल के रूप में पास किया जाता है जो उस फ़ील्ड की ऑब्जेक्ट को संदर्भित करता है, तो क्या मूल रूप से पास किए गए फ़ील्ड को अपडेट/बदलने पर तथ्य के बाद इसे बदलने की संभावना है?

public class MutableDog
{
    public String name;
    public String color;

    public MutableDog(String name, String color)
    {
        this.name = name;
        this.color = color;
    }
}

public class ImmutableDog // are fields of these objects truly safe from changing?
{
    private final String name;
    private final String color;

    public ImmutableDog(MutableDog doggy)
    {
        this.name = doggy.name;
        this.color = doggy.color;
    }

    public String getColor()
    {
        return this.color;
    }
}

public static void main(String[] args)
{
    MutableDog aMutableDog = new MutableDog("Courage", "Pink");

    ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);

    aMutableDog.color = "Pink/Black";

    anImmutableDog.getColor().equals(aMutableDog.color); // true or false?
}

अनिवार्य रूप से, क्या ImmutableDog वास्तव में अपरिवर्तनीय है? उदाहरण में, स्ट्रिंग्स का उपयोग किया जाता है। क्या एक परिवर्तनशील वस्तु, जैसे Collection का उपयोग करने से फर्क पड़ेगा?

यह प्रश्न इस उत्तर के प्रत्युत्तर में है।

2
Snap 3 सितंबर 2020, 09:18

3 जवाब

सबसे बढ़िया उत्तर

ImmutableDog वास्तव में अपरिवर्तनीय है, भले ही यह एक परिवर्तनशील वस्तु से तार प्राप्त कर सकता है। ऐसा इसलिए है क्योंकि Strings अपरिवर्तनीय हैं। यह अपरिवर्तनीयता के महान लाभों में से एक को भी प्रदर्शित करता है - आप अपरिवर्तनीय वस्तुओं को इस चिंता के बिना पास कर सकते हैं कि वे अचानक बदल जाएंगे।

आप सोच रहे होंगे कि ImmutableDog में फ़ील्ड्स को MutableDog इंस्टेंस के फ़ील्ड सेट करके किसी तरह बदला जा सकता है:

aMutableDog.color = "Pink/Black";

हालांकि, "Pink/Black" यहां ImmutableDog को असाइन किए गए स्ट्रिंग इंस्टेंस से अलग है, इसलिए ImmutableDog नहीं बदलेगा।


दूसरी ओर, यदि ImmutableDog में एक परिवर्तनशील प्रकार का क्षेत्र है, तो यह वास्तव में अपरिवर्तनीय नहीं है।

उदाहरण के लिए, यहां आपका समान कोड है, लेकिन StringBuilder के साथ:

public class MutableDog
{
    public StringBuilder name;
    public StringBuilder color;

    public MutableDog(StringBuilder name, StringBuilder color)
    {
        this.name = name;
        this.color = color;
    }
}

public class ImmutableDog // are fields of these objects truly safe from changing?
{
    private final StringBuilder name;
    private final StringBuilder color;

    public ImmutableDog(MutableDog doggy)
    {
        this.name = doggy.name;
        this.color = doggy.color;
    }

    public String getColor()
    {
        return this.color.toString();
    }
}

public static void main(String[] args)
{
    MutableDog aMutableDog = new MutableDog("Courage", "Pink");

    ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);

    aMutableDog.color.append(" and Black");

    anImmutableDog.getColor().equals(aMutableDog.color);
}

अब अपरिवर्तनीय कुत्ते का रंग बदलता हुआ दिखाई देगा। आप अभी भी कंस्ट्रक्टर में स्ट्रिंग बिल्डर को कॉपी करके इसका बचाव कर सकते हैं:

public ImmutableDog(MutableDog doggy)
{
    this.name = new StringBuilder(doggy.name);
    this.color = new StringBuilder(doggy.color);
}

हालांकि, यह आपको अभी भी (गलती से) ImmutableDog वर्ग के अंदर स्ट्रिंग बिल्डर को संशोधित करने की अनुमति देगा।

तो अपरिवर्तनीय कक्षाओं में परिवर्तनीय कक्षाओं को स्टोर न करें। :)

2
Sweeper 3 सितंबर 2020, 09:39

यह बहुत आसान है: जावा में कोई भी संदर्भ प्रकार, अंत में, किसी वस्तु को अंक इंगित करता है।

यदि उस वस्तु में परिवर्तनशील अवस्था है, तो किसी भी संदर्भ का उपयोग उस अवस्था को बदलने के लिए किया जा सकता है।

इस प्रकार, आप सही हैं: प्रत्येक फ़ील्ड घोषणा से पहले केवल private final डालने से यह आवश्यक नहीं है कि वह वर्ग स्वयं अपरिवर्तनीय हो।

आपके उदाहरण में, स्ट्रिंग क्लास अपरिवर्तनीय है (Unsafe का उपयोग करके बहुत अस्पष्ट चाल के अलावा)। name और color असाइन किए जाने के बाद, संदर्भों को बदला नहीं जा सकता है, और जिन वस्तुओं को वे इंगित करते हैं उन्हें भी बदला नहीं जा सकता है।

लेकिन निश्चित रूप से: यदि उदाहरण के लिए प्रकार List था, तो यह बहुत अच्छी तरह से संभव होगा कि अंतर्निहित वस्तु को कहीं और बदल दिया जाए। यदि आप इसे रोकना चाहते हैं, तो आपको उदाहरण के लिए उस आने वाली सूची की एक प्रतिलिपि बनानी होगी, और उसका संदर्भ रखना होगा।

2
GhostCat 3 सितंबर 2020, 09:33

क्या यह वास्तव में सभी इंद्रियों और स्थितियों में अपरिवर्तनीय है? नहीं. क्या यह कुछ हद तक अपरिवर्तनीय है? हां।

जब तक आप केवल परिवर्तनीय असाइनमेंट कर रहे हैं, अपरिवर्तनीय कुत्ता वास्तव में हमेशा अपरिवर्तित रहेगा। यह आम तौर पर पास-बाय-वैल्यू सेमेन्टिक्स के कारण होता है, जिसे बहुत समझाया जाता है यहां.

जब आप ImmutableDog कुत्ते में color के संदर्भ की प्रतिलिपि बनाते हैं, तो आप अनिवार्य रूप से हैंडल की प्रतिलिपि बनाते हैं। जब आप परिवर्तनशील कुत्ते के माध्यम से रंग को Pink/Black मान निर्दिष्ट करके संशोधित करते हैं, तो परिवर्तनशील कुत्ते का हैंडल बदल जाता है, लेकिन अपरिवर्तनीय कुत्ता अभी भी मूल रंग की ओर इशारा करते हुए मूल संभाल रखता है।

String प्रकार के साथ, यह एक कदम आगे जाता है, क्योंकि तार अपरिवर्तनीय हैं। इसलिए स्ट्रिंग पर किसी भी विधि को कॉल करने की गारंटी है कि मूल मान को संशोधित न करें। तो स्ट्रिंग के संदर्भ में, हाँ, अपरिवर्तनीय कुत्ता वास्तव में अपरिवर्तनीय है और उस पर भरोसा किया जा सकता है।

संग्रहों से फर्क पड़ता है। अगर हम आपकी कक्षाओं के डिजाइन में थोड़ा बदलाव करते हैं:

public class MutableDog {
    public String name;
    public List<String> acceptedMeals;

    public MutableDog(String name, List<String> acceptedMeals) {
        this.name = name;
        this.acceptedMeals = new ArrayList<>(acceptedMeals);
    }
}

public class ImmutableDog {
    private final String name;
    private final Iterable<String> acceptedMeals;

    public ImmutableDog(MutableDog doggy) {
        this.name = doggy.name;
        this.acceptedMeals = doggy.acceptedMeals;
    }

    public Iterable<String> getAcceptedMeals() {
        return this.acceptedMeals;
    }
}

और निम्नलिखित कोड निष्पादित करें:

public static void main(String[] args) throws IOException {
    MutableDog mutableDog = new MutableDog("Spot", Collections.singletonList("Chicken"));
    ImmutableDog immutableDog = new ImmutableDog(mutableDog);

    immutableDog.getAcceptedMeals().forEach(System.out::println);

    mutableDog.acceptedMeals.add("Pasta");

    immutableDog.getAcceptedMeals().forEach(System.out::println);
}

निम्नलिखित का प्रिंट आउट लिया गया है:

Chicken
Chicken
Pasta

यह ImmutableDog के कंस्ट्रक्टर द्वारा acceptedMeals संग्रह में कॉपी करने वाले हैंडल के कारण है, लेकिन नया हैंडल मूल संग्रह के समान स्थान की ओर इशारा करता है। तो जब आप परिवर्तनशील कुत्ते के माध्यम से संशोधन कहते हैं, क्योंकि ImmutableDog स्मृति में एक ही स्थान पर इंगित करता है, इसके मान भी संभावित रूप से संशोधित होते हैं।

इस विशिष्ट मामले में, आप केवल संदर्भ हैंडल की प्रतिलिपि बनाने के बजाय, संग्रह की एक गहरी प्रतिलिपि बनाकर इस दुष्प्रभाव को रोक सकते हैं:

public ImmutableDog(MutableDog doggy) {
    this.name = doggy.name;
    this.acceptedMeals = new ArrayList<>(doggy.acceptedMeals);
}

ऐसा करने से, ऊपर प्रस्तुत वही main विधि केवल निम्नलिखित प्रिंट करती है:

Chicken
Chicken
1
Andy 3 सितंबर 2020, 09:39