मैं संग्रह को सुरक्षित रूप से कैसे कॉपी कर सकता हूं?


9

अतीत में, मैंने कहा है कि एक संग्रह को सुरक्षित रूप से कॉपी करें कुछ ऐसा करें:

public static void doThing(List<String> strs) {
    List<String> newStrs = new ArrayList<>(strs);

या

public static void doThing(NavigableSet<String> strs) {
    NavigableSet<String> newStrs = new TreeSet<>(strs);

लेकिन क्या ये "कॉपी" कंस्ट्रक्टर हैं, समान स्थिर निर्माण विधियाँ और धाराएँ, वास्तव में सुरक्षित हैं और नियम कहाँ निर्दिष्ट हैं? सुरक्षित रूप से मेरा मतलब है कि जावा भाषा और एक दुर्भावनापूर्ण फोन करने वाले के खिलाफ लागू किए गए संग्रह की मूल मौलिक अखंडता की गारंटी है, एक उचित द्वारा समर्थित है SecurityManagerऔर इसमें कोई दोष नहीं हैं।

मैं विधि फेंकने के साथ खुश हूँ ConcurrentModificationException, NullPointerException, IllegalArgumentException, ClassCastException, आदि, या शायद यह भी फांसी।

मैंने Stringएक अपरिवर्तनीय प्रकार के तर्क के उदाहरण के रूप में चुना है। इस प्रश्न के लिए, मुझे म्यूटेबल प्रकारों के संग्रह के लिए गहरी प्रतियों में कोई दिलचस्पी नहीं है, जिनके पास अपने स्वयं के गोच हैं।

(स्पष्ट रूप से, मैं OpenJDK स्रोत कोड पर ध्यान दिया है और के लिए जवाब के कुछ प्रकार है ArrayListऔर TreeSet।)


2
आप सुरक्षित से क्या मतलब है ? आम तौर पर संग्रह फ्रेमवर्क में कक्षाएं बोलना इसी तरह से काम करते हैं, अपवाद के साथ javadocs में। कॉपी कंस्ट्रक्टर किसी अन्य कंस्ट्रक्टर की तरह ही "सुरक्षित" होते हैं। क्या आपके मन में कोई विशेष बात है, क्योंकि यह पूछना कि क्या एक संग्रह प्रतिलिपि निर्माता सुरक्षित ध्वनियां बहुत विशिष्ट हैं?
कायमन

1
ठीक है, NavigableSetऔर अन्य Comparableआधारित संग्रह कभी-कभी यह पता लगा सकते हैं कि क्या कोई वर्ग compareTo()सही तरीके से लागू नहीं करता है और एक अपवाद नहीं फेंकता है। यह थोड़ा अस्पष्ट है कि आप अविश्वसनीय तर्क से क्या मतलब है। आपका मतलब है कि एक ईविलवियर खराब स्ट्रिंग्स का एक संग्रह है और जब आप उन्हें अपने संग्रह में कॉपी करते हैं तो कुछ बुरा होता है? नहीं, संग्रह की रूपरेखा बहुत ठोस है, यह लगभग 1.2 के बाद से है।
कयामन

1
@ जेसे विल्सन आप अपने इंटर्नल में हैक किए बिना बहुत सारे मानक संग्रहों में समझौता कर सकते हैं, HashSet(और सामान्य रूप से अन्य सभी हैशिंग संग्रह) hashCodeतत्वों के कार्यान्वयन की शुद्धता / अखंडता पर निर्भर करते हैं, TreeSetऔर (और आप भी नहीं कर सकते हैं) कस्टम तुलनित्र को स्वीकार किए बिना एक समतुल्य प्रति बनाएँ अगर एक है), उस विशेष प्रकार की अखंडता पर भरोसा करता है जिसे संकलन के बाद कभी भी सत्यापित नहीं किया जाता है, इसलिए एक वर्ग फ़ाइल, जिसके साथ या हैंडक्राफ्ट नहीं किया गया है, उसे उल्टा कर सकता है। PriorityQueueComparatorEnumSetenumjavac
होल्गर

1
आपके उदाहरणों में, आपके पास new TreeSet<>(strs)जहां strsए है NavigableSet। यह बल्क कॉपी नहीं है, क्योंकि परिणामस्वरूप TreeSetस्रोत के तुलनित्र का उपयोग करेगा, जो कि शब्दार्थ को बनाए रखने के लिए भी आवश्यक है। यदि आप केवल निहित तत्वों को संसाधित करने के साथ ठीक हैं, toArray()तो जाने का रास्ता है; यहां तक ​​कि पुनरावृत्ति क्रम भी रहेगा। जब आप "एलिमेंट एलीमेंट, एलिमेंट एलिमेंट, यूज़ एलिमेंट" के साथ ठीक होते हैं, तो आपको कॉपी बनाने की भी जरूरत नहीं है। समस्या तब शुरू होती है जब आप सभी तत्वों को सत्यापित करना चाहते हैं, सभी तत्वों का उपयोग करके। फिर, आप एक TreeSetकस्टम w कस्टम कॉपीर पर भरोसा नहीं कर सकते
होल्गर

1
checkcastप्रत्येक तत्व के लिए एकमात्र थोक कॉपी ऑपरेशन का प्रभाव toArrayएक विशिष्ट प्रकार के साथ होता है। हम हमेशा इसे खत्म कर रहे हैं। जेनेरिक संग्रह उनके वास्तविक तत्व प्रकार को भी नहीं जानते हैं, इसलिए उनके प्रतिलिपि निर्माता समान कार्यक्षमता प्रदान नहीं कर सकते हैं। बेशक, आप किसी भी चेक को सही उपयोग करने के लिए सुरक्षित कर सकते हैं, लेकिन फिर, मुझे नहीं पता कि आपके प्रश्न क्या लक्ष्य कर रहे हैं। आपको "सिमेंटिक अखंडता" की आवश्यकता नहीं है, जब आप तत्वों का उपयोग करने से पहले तुरंत जाँच और असफल हो जाते हैं।
होल्गर

जवाबों:


12

सामान्य API में समान JVM के भीतर जानबूझकर दुर्भावनापूर्ण कोड के विरुद्ध कोई वास्तविक सुरक्षा संग्रह API की तरह नहीं है।

जैसा कि आसानी से प्रदर्शित किया जा सकता है:

public static void main(String[] args) throws InterruptedException {
    Object[] array = { "foo", "bar", "baz", "and", "another", "string" };
    array[array.length - 1] = new Object() {
        @Override
        public String toString() {
            Collections.shuffle(Arrays.asList(array));
            return "string";
        }
    };
    doThing(new ArrayList<String>() {
        @Override public Object[] toArray() {
            return array;
        }
    });
}

public static void doThing(List<String> strs) {
    List<String> newStrs = new ArrayList<>(strs);

    System.out.println("made a safe copy " + newStrs);
    for(int i = 0; i < 10; i++) {
        System.out.println(newStrs);
    }
}
made a safe copy [foo, bar, baz, and, another, string]
[bar, and, string, string, another, foo]
[and, baz, bar, string, string, string]
[another, baz, and, foo, bar, string]
[another, bar, and, foo, string, and]
[another, baz, string, another, and, foo]
[string, and, another, foo, string, foo]
[baz, string, foo, and, baz, string]
[bar, another, string, and, another, baz]
[bar, string, foo, string, baz, and]
[bar, string, bar, another, and, foo]

जैसा कि आप देख सकते हैं, List<String>वास्तव में Stringउदाहरणों की एक सूची प्राप्त करने की गारंटी नहीं देता है । प्रकार के क्षरण और कच्चे प्रकारों के कारण, सूची कार्यान्वयन पक्ष पर भी एक ठीक संभव नहीं है।

दूसरी बात, आप के ArrayListलिए कंस्ट्रक्टर को दोष दे सकते हैं , आने वाले संग्रह के toArrayकार्यान्वयन में विश्वास है । TreeMapउसी तरह से प्रभावित नहीं होता है, लेकिन केवल इसलिए कि सरणी के पारित होने से ऐसा कोई प्रदर्शन नहीं होता है, जैसा कि निर्माण में ArrayList। कंस्ट्रक्टर में न तो कोई सुरक्षा की गारंटी देता है।

आम तौर पर, हर कोने के आसपास जानबूझकर दुर्भावनापूर्ण कोड मानकर कोड लिखने की कोशिश करने का कोई मतलब नहीं है। सब कुछ से बचाने के लिए यह बहुत कुछ कर सकता है। इस तरह की सुरक्षा केवल कोड के लिए उपयोगी है जो वास्तव में एक ऐसी कार्रवाई को अंजाम देती है जो किसी दुर्भावनापूर्ण कॉलर को किसी चीज़ तक पहुंच दे सकती है, यह पहले से ही इस कोड के बिना उपयोग नहीं कर सकता है।

यदि आपको किसी विशेष कोड के लिए सुरक्षा की आवश्यकता है, तो उपयोग करें

public static void doThing(List<String> strs) {
    String[] content = strs.toArray(new String[0]);
    List<String> newStrs = new ArrayList<>(Arrays.asList(content));

    System.out.println("made a safe copy " + newStrs);
    for(int i = 0; i < 10; i++) {
        System.out.println(newStrs);
    }
}

फिर, आप यह सुनिश्चित कर सकते हैं कि newStrsइसमें केवल तार होते हैं और इसके निर्माण के बाद अन्य कोड द्वारा संशोधित नहीं किया जा सकता है।

या List<String> newStrs = List.of(strs.toArray(new String[0]));जावा 9 या नए
नोट के साथ उपयोग करें कि जावा 10 List.copyOf(strs)ऐसा ही करता है, लेकिन इसका प्रलेखन यह नहीं कहता है कि यह आने वाले संग्रह की toArrayविधि पर भरोसा नहीं करने की गारंटी है । इसलिए List.of(…), कॉलिंग , जो निश्चित रूप से एक कॉपी बनाता है अगर यह एक सरणी आधारित सूची देता है, तो सुरक्षित है।

चूंकि कोई कॉल करने वाला रास्ता बदल नहीं सकता है, सरणियाँ काम करती हैं, आने वाले संग्रह को एक सरणी में डंप करती हैं, इसके बाद नए संग्रह को पॉप्युलेट करने के बाद, हमेशा कॉपी को सुरक्षित बना देगा। चूंकि संग्रह ऊपर दिखाए गए अनुसार लौटे हुए सरणी के लिए एक संदर्भ रख सकता है, यह इसे कॉपी चरण के दौरान बदल सकता है, लेकिन यह संग्रह में प्रतिलिपि को प्रभावित नहीं कर सकता है।

तो किसी भी निरंतरता की जाँच किसी विशेष तत्व को सरणी से या संपूर्ण संग्रह के रूप में प्राप्त होने के बाद की जानी चाहिए।


2
जावा का सुरक्षा मॉडल स्टैक पर सभी कोड के अनुमति सेटों के प्रतिच्छेदन कोड प्रदान करके काम करता है, इसलिए जब आपके कोड का कॉलर आपके कोड को अनपेक्षित काम करता है, तो यह अभी भी शुरू में की तुलना में अधिक अनुमति नहीं प्राप्त करता है। तो यह केवल आपके कोड को उन चीजों को बनाता है जो दुर्भावनापूर्ण कोड आपके कोड के बिना भी कर सकते थे। आपको केवल उस कोड को सख्त करना होगा जिसे आप उन्नत विशेषाधिकारों के साथ चलाने के इरादे से करते हैं AccessController.doPrivileged(…)आदि। लेकिन एप्लेट सुरक्षा संबंधी बग की लंबी सूची हमें संकेत देती है कि इस तकनीक को क्यों छोड़ दिया गया है ...
होल्गर

1
लेकिन मुझे "सामान्य एपीआई में संग्रह एपीआई की तरह" सम्मिलित करना चाहिए था, जैसा कि मैं उत्तर में ध्यान केंद्रित कर रहा था।
होल्गर

2
आपको अपना कोड कठोर क्यों करना चाहिए, जो स्पष्ट रूप से सुरक्षा प्रासंगिक नहीं है, विशेषाधिकार प्राप्त कोड के खिलाफ जो एक दुर्भावनापूर्ण संग्रह कार्यान्वयन पर्ची की अनुमति देता है? वह काल्पनिक कॉलर आपके कोड को कॉल करने से पहले और बाद में अभी भी दुर्भावनापूर्ण व्यवहार के अधीन होगा। यह भी ध्यान नहीं दिया जाएगा कि आपका कोड सही व्यवहार करने वाला है। new ArrayList<>(…)कॉपी कंस्ट्रक्टर के रूप में उपयोग करना सही संग्रह कार्यान्वयन को ठीक मानता है। जब पहले से ही बहुत देर हो चुकी है तो सुरक्षा के मुद्दों को ठीक करना आपका कर्तव्य नहीं है। समझौता हार्डवेयर के बारे में क्या? ऑपरेटिंग सिस्टम? मल्टी-थ्रेडिंग के बारे में कैसे?
होल्गर

2
मैं "कोई सुरक्षा नहीं" की वकालत कर रहा हूं, लेकिन इस तथ्य के बाद टूटे माहौल को ठीक करने की कोशिश करने के बजाय, सही स्थानों पर सुरक्षा। यह एक दिलचस्प दावा है कि " ऐसे कई संग्रह हैं जो अपने सुपरपाइप को सही ढंग से लागू नहीं करते हैं " लेकिन यह बहुत पहले से ही साबित हो गया है, इसे आगे भी विस्तार करने के लिए कहना है। मूल प्रश्न का पूरी तरह से उत्तर दिया गया है; अब आप जिन बिंदुओं को ला रहे हैं, वे कभी इसका हिस्सा नहीं थे। जैसा कि कहा गया है, List.copyOf(strs)स्पष्ट मूल्य पर उस संबंध में आने वाले संग्रह की शुद्धता पर भरोसा नहीं करता है। ArrayListएक उचित-से-रोज़ समझौता है।
होल्गर

4
यह स्पष्ट रूप से कहता है कि ऐसी कोई विशिष्टता नहीं है, सभी "समान स्थिर निर्माण विधियों और धाराओं" के लिए। इसलिए यदि आप पूरी तरह से सुरक्षित होना चाहते हैं, तो आपको toArray()खुद को कॉल करना होगा , क्योंकि सरणियों में व्यवहार की अधिकता नहीं हो सकती है, इसके बाद सरणी की एक संग्रह प्रतिलिपि बनाकर, जैसे new ArrayList<>(Arrays.asList( strs.toArray(new String[0])))या List.of(strs.toArray(new String[0]))। दोनों में तत्व प्रकार को लागू करने का दुष्प्रभाव भी है। मैं, व्यक्तिगत रूप से, नहीं लगता कि वे कभी भी copyOfअपरिवर्तनीय संग्रह से समझौता करने की अनुमति देंगे , लेकिन विकल्प उत्तर में हैं।
होल्गर

1

मैं इस जानकारी को टिप्पणी में छोड़ दूंगा, लेकिन मेरे पास पर्याप्त प्रतिष्ठा नहीं है, क्षमा करें :) मैं इसे उतनी ही क्रिया समझाने की कोशिश करूंगा जितना मैं कर सकता हूं।

constसदस्य फ़ंक्शंस को चिह्नित करने के लिए C ++ में उपयोग किए जाने वाले संशोधक जैसी कुछ चीज़ों के बजाय जो ऑब्जेक्ट सामग्री को संशोधित करने के लिए नहीं हैं, जावा में मूल रूप से "अपरिवर्तनीयता" की अवधारणा का उपयोग किया गया था। एनकैप्सुलेशन (या OCP, ओपन-क्लोज्ड सिद्धांत) को किसी वस्तु के किसी भी अप्रत्याशित उत्परिवर्तन (परिवर्तन) से बचाने वाला था। बेशक प्रतिबिंब एपीआई यह चारों ओर चलता है; प्रत्यक्ष मेमोरी एक्सेस वही करता है; अपने पैर की शूटिंग के बारे में अधिक है :)

java.util.Collectionस्वयं ही परिवर्तनशील इंटरफ़ेस है: इसमें वह addविधि है जिसे संग्रह को संशोधित करना है। बेशक प्रोग्रामर संग्रह को किसी ऐसी चीज में लपेट सकता है जो फेंक देगा ... और सभी रनटाइम अपवाद होंगे क्योंकि एक अन्य प्रोग्रामर javadoc को पढ़ने में सक्षम नहीं था जो स्पष्ट रूप से कहता है कि संग्रह अपरिवर्तनीय है।

मैंने java.util.Iterableअपने इंटरफेस में अपरिवर्तनीय संग्रह को उजागर करने के लिए टाइप का उपयोग करने का निर्णय लिया । शब्दार्थ Iterableमें "उत्परिवर्तन" के रूप में संग्रह की ऐसी विशेषता नहीं है। फिर भी आप (संभवत:) धाराओं के माध्यम से अंतर्निहित संग्रह को संशोधित करने में सक्षम होंगे।


जेआईसी, नक्शे को अपरिवर्तनीय तरीके से उजागर करने के लिए java.util.Function<K,V>इस्तेमाल किया जा सकता है (मानचित्र की getविधि इस परिभाषा को फिट करती है)


रीड-ओनली इंटरफेस और अपरिवर्तनीयता की अवधारणाएं ऑर्थोगोनल हैं। C ++ और C की बात यह है कि वे शब्दार्थ अखंडता का समर्थन नहीं करते हैं । कॉपी ऑब्जेक्ट / स्ट्रक्चर आर्ग्युमेंट्स - कॉन्स्टेंट और उसके लिए एक डोडी ऑप्टिमाइज़ेशन है। यदि आप Iteratorतब पास करने के लिए थे, जो व्यावहारिक रूप से एक मूल रूप से प्रतिलिपि बनाने के लिए मजबूर करता है, लेकिन यह अच्छा नहीं है का उपयोग forEachRemaining/ forEachजाहिर है एक पूर्ण आपदा होने जा रहा है। (मुझे यह भी उल्लेख करना होगा कि Iteratorइसका एक removeतरीका है।)
टॉम हैटिन -

यदि स्काला संग्रह पुस्तकालय को देखें तो परिवर्तनशील और अपरिवर्तनीय इंटरफेस के बीच सख्त अंतर है। हालांकि (मुझे लगता है) यह पूरी तरह से अलग कारणों की वजह से बनाया गया था, लेकिन अभी भी एक प्रदर्शन है कि सुरक्षा कैसे प्राप्त की जा सकती है। केवल पढ़ने के लिए शब्दार्थ ही अपरिवर्तनीयता मानता है, यही मैं कहने की कोशिश कर रहा हूं। (मैं Iterableवास्तव में अपरिवर्तनीय नहीं होने के बारे में सहमत हूं , लेकिन किसी भी मुद्दे को नहीं देखता हूं forEach*)
अलेक्जेंडर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.