2012-07-02 7 views
16

संग्रह पर स्ट्रिंग करने के लिए एक अनंत लूप में प्रवेश किया जा सकता है यदि एकत्रित वस्तुओं के ग्राफ में कहीं भी एक संदर्भ है। नीचे उदाहरण देखें।toString() में एक अनंत रिकर्सन को रोकने के लिए सबसे प्रभावी तरीका?

हां, अच्छे कोडिंग प्रथाओं को इसे पहले स्थान पर रोकना चाहिए, लेकिन फिर भी, मेरा प्रश्न है: इस स्थिति में एक पुनरावर्तन का पता लगाने का सबसे प्रभावी तरीका क्या है?

एक दृष्टिकोण थ्रेडलोकल में एक सेट का उपयोग करना है, लेकिन यह थोड़ा भारी लगता है।

public class AntiRecusionList<E> extends ArrayList<E> { 
    @Override 
    public String toString() { 
    if ( /* ???? test if "this" has been seen before */) { 
     return "{skipping recursion}"; 
    } else { 
     return super.toString(); 
    } 
    } 
} 


public class AntiRecusionListTest { 
    @Test 
    public void testToString() throws Exception { 
     AntiRecusionList<AntiRecusionList> list1 = new AntiRecusionList<>(); 
     AntiRecusionList<AntiRecusionList> list2 = new AntiRecusionList<>(); 
     list2.add(list1); 
     list1.add(list2); 
     list1.toString(); //BOOM ! 
    } 
} 
+3

[Lombok] (http://projectlombok.org/) पर एक नज़र डालें। यह आपको अपने वर्गों को '@ToString (बहिष्कृत करें = {" फ़ील्ड "," आप "," न करें "," चाहते हैं "} के साथ एनोटेट करने की अनुमति देता है। :) – elias

+1

इस में कभी भी भाग न लें लेकिन इस तरह की चीजें असली दर्द हो सकती हैं, क्योंकि यह तब हो सकता है जब आप अपना लॉग 4j (या समान) डिबगिंग चालू करते हैं। तो आप किसी समस्या को डीबग करने का प्रयास कर रहे हैं और लॉगजर संदेशों के साथ किसी समस्या में भाग लेना चाहते हैं - बहुत परेशान! मेरे प्रश्न के लिए +1 – davidfrancis

+0

@ डेविड फ्रांसीसी - लॉगिंग और डिबगिंग बिल्कुल ठीक है जहां यह आया! चीयर्स। – marathon

उत्तर

4

threadlocal बिट मैं प्रश्न में उल्लेख किया:

public class AntiRecusionList<E> extends ArrayList<E> { 


private final ThreadLocal<IdentityHashMap<AntiRecusionList<E>, ?>> fToStringChecker = 
     new ThreadLocal<IdentityHashMap<AntiRecusionList<E>, ?>>() { 
      @Override 
      protected IdentityHashMap<AntiRecusionList<E>, ?> initialValue() { 
       return new IdentityHashMap<>(); 
      } 
     };  

@Override 
public String toString() { 
    boolean entry = fToStringChecker.get().size() == 0; 
    try { 
     if (fToStringChecker.get().containsKey(this)/* test if "this" has been seen before */) { 
      return "{skipping recursion}"; 
     } else { 
      fToStringChecker.get().put(this, null); 
      entry = true; 
     } 
     return super.toString(); 
    } finally { 
     if (entry) 
      fToStringChecker.get().clear(); 
    } 
} 
} 
+1

यह एक न्यूनतम शर्त है जिसमें न्यूनतम ओवरहेड शामिल है। –

+0

क्या थ्रेड स्थानीय को स्थिर होना चाहिए? – davidfrancis

1

सबसे आसान तरीका: एक संग्रह या एक नक्शा के तत्वों पर toString() फोन नहीं है, कभी। यह इंगित करने के लिए कि यह एक संग्रह या मानचित्र है, और पूरी तरह से इसे फिर से शुरू करने से बचने के लिए बस [] प्रिंट करें। अनंत पुनरावृत्ति में गिरने से बचने के लिए यह एकमात्र बुलेट प्रूफ तरीका है।

सामान्य मामले में, आप अनुमान नहीं लगा सकते कि Collection या Map में किसी अन्य ऑब्जेक्ट के अंदर कौन से तत्व होने जा रहे हैं, और निर्भरता ग्राफ काफी जटिल हो सकता है, जिससे अप्रत्याशित परिस्थितियां होती हैं जहां ऑब्जेक्ट ग्राफ़ में एक चक्र होता है ।

आप किस आईडीई का उपयोग कर रहे हैं? क्योंकि ग्रहण में कोड जनरेटर के माध्यम से toString() विधि उत्पन्न करते समय स्पष्ट रूप से इस मामले को संभालने का विकल्प होता है - जब मैं एक विशेषता गैर-शून्य संग्रह या मानचित्र प्रिंट [] होता है, इस पर ध्यान दिए बिना कि इसमें कितने तत्व हैं।

+0

हैशपैप बुलेट प्रूफ क्यों नहीं होगा? – Aidanc

+2

@ एडिंक यह वही स्थिति है, सामान्य मामले के लिए आप अनुमान लगा सकते हैं कि हैशैप का उपयोग कहां किया जा रहा है, यह ऑब्जेक्ट ग्राफ़ और बूम में एक चक्र के बीच में आसानी से समाप्त हो सकता है! अनंत पुनरावृत्ति। विचार के लिए –

11

जब मुझे जोखिम भरा ग्राफ पर पुनरावृत्ति करना होता है, तो मैं आमतौर पर कम करने वाले काउंटर के साथ एक फ़ंक्शन करता हूं।

उदाहरण के लिए:

public String toString(int dec) { 
    if ( dec<=0) { 
     return "{skipping recursion}"; 
    } else { 
     return super.toString(dec-1); 
    } 
} 

public String toString() { 
    return toString(100); 
} 

मैं इस पर जोर देते हैं नहीं, जैसा कि आप पहले से ही जानते हैं, लेकिन यह है कि जो छोटी और उम्मीद के मुताबिक हो गया है toString() के अनुबंध सम्मान नहीं करता।

+1

+1, हालांकि यह ऑब्जेक्ट को अपने स्वयं के प्रकार के किसी अन्य वस्तु का संदर्भ देने के मामले में काम करेगा, ज्यादातर मामलों में हमारे पास ऐसा कुछ विभाग है जो विभाग से संबंधित है जिसमें कई लोग हैं (एक चक्र बनाते हैं), और इस स्थिति पर कोई समाधान –

+0

नहीं है जब ग्राफ विषम होता है, तो आप आमतौर पर कम करने वाले काउंटर के साथ एक स्थिर रिकर्सिव फ़ंक्शन कर सकते हैं। या एक ही विधि के कई विशेषज्ञताओं। महत्वपूर्ण बिंदु काउंटर को पास करना और शून्य होने पर असफल होना है। –

+0

सरल और सुरुचिपूर्ण और मेरा दिन बचाया – Leo

3

समस्या संग्रह के लिए निहित नहीं है, यह ऑब्जेक्ट्स के किसी ग्राफ के साथ हो सकती है जिसमें चक्रीय संदर्भ हैं, उदाहरण के लिए, एक दोगुनी-लिंक्ड सूची।

मुझे लगता है कि एक शेन नीति है: toString() अपनी कक्षा के तरीके को अपने बच्चों के toString() पर कॉल नहीं करना चाहिए/यदि कोई संभावना है कि यह चक्र के साथ ऑब्जेक्ट ग्राफ़ का हिस्सा है तो संदर्भित नहीं किया जाना चाहिए। कहीं और, हमारे पास एक विशेष विधियां हो सकती हैं (शायद स्थैतिक, शायद एक सहायक वर्ग के रूप में) जो पूर्ण ग्राफ का एक स्ट्रिंग प्रतिनिधित्व उत्पन्न करती है।

0

हो सकता है आप अपने toString और स्टैकट्रेस पर लाभ उठाने में एक अपवाद बना सकते हैं पता करने के लिए जहां ढेर में हैं, और आप इसे देखते हैं मिलेगा रिकर्सिव कॉल कुछ ढांचे इस तरह से करते हैं।

@Override 
public String toString() { 
    // ... 
    Exception exception = new Exception(); 
    StackTraceElement[] stackTrace = exception.getStackTrace(); 
    // now you analyze the array: stack trace elements have 
    // 4 properties: check className, lineNumber and methodName. 
    // if analyzing the array you find recursion you stop propagating the calls 
    // and your stack won't explode  
    //...  

} 
3

आप उस स्ट्रिंग को बना सकते हैं जो एक पहचान हैश सेट लेता है।

public String toString() { 
    return toString(Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>())); 
} 

private String toString(Set<Object> seen) { 
    if (seen.add(this)) { 
     // to string this 
    } else { 
     return "{this}"; 
    } 
} 
+0

ग्राफ में एंटीक्रिक्यूशनलिस्ट प्रकार का सामना करने के लिए हर बार यह एक नया सेट नहीं बनाएगा? और इस प्रकार वांछित प्रभाव कभी नहीं होता है क्योंकि अगर (देखा) परीक्षण हमेशा एक नए सेट के खिलाफ होगा? – marathon

+2

हाँ। इससे बचने के लिए आपको सेट को थ्रेडलोकल में रखना होगा। – Jorn

+3

हालांकि मुझे यह पसंद है कि आपने पहचान सेट कैसे बनाया है। मैं इसे चुरा लेगा :) – marathon

2

तुम हमेशा प्रत्यावर्तन का ट्रैक रखने सकता है इस प्रकार है (ध्यान में रखा कोई सूत्रण मुद्दों):

public static class AntiRecusionList<E> extends ArrayList<E> { 
    private boolean recursion = false; 

    @Override 
    public String toString() { 
     if(recursion){ 
       //Recursion's base case. Just return immediatelly with an empty string 
       return ""; 
     } 
     recursion = true;//start a perhaps recursive call 
     String result = super.toString(); 
     recursion = false;//recursive call ended 
     return result; 
    } 
} 
+0

... मुझे लगता है कि वह सही है, है ना? मुझे इस विचार में कोई दोष नहीं मिल रहा है, यह बस काम करता है। और इसका थ्रेड-लोकेशन आसान है। –

+0

@ स्लेनेक समस्या यह है कि किसी ऑब्जेक्ट को पहले देखा जाने पर यह रुकता नहीं है, बल्कि, यह केवल एक स्तर के रिकर्सन के बाद बंद हो जाता है। यह केवल रिकर्सन को रोक देगा, संग्रह में एक चक्र नहीं ढूंढ पाएगा। – dcow

+0

@ डेविडकौडेन: सूची का 'toString' केवल प्रदर्शन के लिए प्रस्तुत किए जाने वाले सूची के प्रत्येक आइटम पर 'toString' को कॉल करता है। रिकर्सन को रोकना अपेक्षित है क्योंकि ऑब्जेक्ट पहले ही मुद्रित हो चुका है। यह वास्तव में एक ग्राफ एल्गोरिदम नहीं है कि ओपी पूरा करने की कोशिश कर रहा है, कम से कम जिस तरह से मैं इस मुद्दे को समझता हूं – Cratylus

1

आप पानी में गिर जाने के लिए चाहते हैं, आप एक पहलू यह है कि नेस्टेड संग्रह जब भी आप पटरियों इस्तेमाल कर सकते हैं कॉल टूस्ट्रिंग()।

public aspect ToStringTracker() { 
    Stack collections = new Stack(); 

    around(java.util.Collection c): call(String java.util.Collection+.toString()) && target(c) { 
    if (collections.contains(c)) { return "recursion"; } 
    else { 
     collections.push(c); 
     String r = c.toString(); 
     collections.pop(); 
     return r; 
    } 
    } 
} 

मैं ग्रहण में इस फेंकने के बिना वाक्य रचना पर कभी नहीं 100% हूँ, लेकिन मैं आप विचार

2

मैं अपाचे कॉमन्स लैंग से ToStringBuilder का उपयोग करना चाहिये मिल लगता है। आंतरिक रूप से यह एक थ्रेडलोकल मानचित्र का उपयोग करता है "चक्रीय वस्तु संदर्भों का पता लगाने और अनंत लूप से बचें।"

संबंधित मुद्दे