2009-10-23 3 views
50
में अंतिम क्षेत्रों में संशोधन करना

के एक साधारण परीक्षण का मामला साथ शुरू करते हैं:जावा

import java.lang.reflect.Field; 

public class Test { 
    private final int primitiveInt = 42; 
    private final Integer wrappedInt = 42; 
    private final String stringValue = "42"; 

    public int getPrimitiveInt() { return this.primitiveInt; } 
    public int getWrappedInt()  { return this.wrappedInt; } 
    public String getStringValue() { return this.stringValue; } 

    public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException { 
    Field field = Test.class.getDeclaredField(name); 
    field.setAccessible(true); 
    field.set(this, value); 
    System.out.println("reflection: " + name + " = " + field.get(this)); 
    } 

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException { 
    Test test = new Test(); 

    test.changeField("primitiveInt", 84); 
    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt()); 

    test.changeField("wrappedInt", 84); 
    System.out.println("direct: wrappedInt = " + test.getWrappedInt()); 

    test.changeField("stringValue", "84"); 
    System.out.println("direct: stringValue = " + test.getStringValue()); 
    } 
} 

किसी का अनुमान लगाना क्या निर्गम (नीचे दिखाया गया के रूप में आश्चर्य तुरंत खराब नहीं करने के लिए) के रूप में प्रिंट किया जाएगा परवाह है।

प्रश्न हैं:

  1. क्यों आदिम और लिपटे पूर्णांक अलग तरीके से व्यवहार करते हैं?
  2. परावर्तक बनाम सीधी पहुंच अलग-अलग परिणाम क्यों देता है?
  3. वह जो मुझे सबसे ज्यादा परेशान करता है - क्यों स्ट्रिंग आदिम int की तरह व्यवहार करता है और Integer की तरह नहीं?

परिणाम (जावा 1.5):

reflection: primitiveInt = 84 
direct: primitiveInt = 42 
reflection: wrappedInt = 84 
direct: wrappedInt = 84 
reflection: stringValue = 84 
direct: stringValue = 42 
+5

+1: अपना विषय पढ़ने में अच्छा लगा। –

+0

यह कम्पाइलर से कसकर संबंधित हो सकता है। – andy

उत्तर

21

संकलन समय स्थिरांक (javac संकलन समय पर) inlined कर रहे हैं। जेएलएस देखें, विशेष रूप से 15.28 निरंतर अभिव्यक्ति को परिभाषित करता है और 13.4.9 बाइनरी संगतता या अंतिम फ़ील्ड और स्थिरांक पर चर्चा करता है।

यदि आप फ़ील्ड को गैर-फाइनल बनाते हैं या एक गैर-संकलन समय निरंतर आवंटित करते हैं, तो मान इनलाइन नहीं है। उदाहरण के लिए:

निजी अंतिम स्ट्रिंग स्ट्रिंगवैल्यू = शून्य! = शून्य? "": "42";

+0

तो वे हैं। आदिम int के लिए मुझे उम्मीद है कि परिणाम वापस आ गया है। यह स्ट्रिंग है जो मुझे पहेली करता है। क्या आपके पास जेएलएस के लिए एक और ठोस लिंक है जहां इसका वर्णन किया गया है? – ChssPly76

+0

यह निश्चित रूप से उत्तर का हिस्सा है, लेकिन यह कैसे है कि यदि आप प्रतिबिंब फ़ील्ड के माध्यम से स्ट्रिंग को acces करते हैं। अगर आप ऑब्जेक्ट को सीधे पूछते हैं तो आपको एक अलग मूल्य मिलता है? –

+0

ChssPly76: दो दिलचस्प खंड जोड़े गए। स्टीव बी: यदि आप प्रतिबिंब का उपयोग करते हैं तो आप उस मान पर वापस जाते हैं जो परिलक्षित क्लास फ़ाइल उस फ़ील्ड पर सेट हो गई है। यदि आप इसे सीधे संदर्भित करते हैं, तो मान (javac) संकलन-समय पर कॉपी किया जाता है (जब तक यह एक संकलन-समय स्थिर होता है)। –

1

यह एक जवाब नहीं है, लेकिन यह भ्रम की एक अन्य बिंदु को लाता है:

मैं देखना चाहता था कि क्या समस्या संकलन समय मूल्यांकन या प्रतिबिंब वास्तव में जावा final खोजशब्द के आसपास पाने के लिए अनुमति देता है किया गया था। यहां एक परीक्षा कार्यक्रम है। मैंने जो भी जोड़ा वह गेटर कॉल का एक और सेट था, इसलिए प्रत्येक changeField() कॉल से पहले और बाद में एक है।

package com.example.gotchas; 

import java.lang.reflect.Field; 

public class MostlyFinal { 
    private final int primitiveInt = 42; 
    private final Integer wrappedInt = 42; 
    private final String stringValue = "42"; 

    public int getPrimitiveInt() { return this.primitiveInt; } 
    public int getWrappedInt()  { return this.wrappedInt; } 
    public String getStringValue() { return this.stringValue; } 

    public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException { 
    Field field = MostlyFinal.class.getDeclaredField(name); 
    field.setAccessible(true); 
    field.set(this, value); 
    System.out.println("reflection: " + name + " = " + field.get(this)); 
    } 

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException { 
    MostlyFinal test = new MostlyFinal(); 

    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt()); 
    test.changeField("primitiveInt", 84); 
    System.out.println("direct: primitiveInt = " + test.getPrimitiveInt()); 

    System.out.println(); 

    System.out.println("direct: wrappedInt = " + test.getWrappedInt()); 
    test.changeField("wrappedInt", 84); 
    System.out.println("direct: wrappedInt = " + test.getWrappedInt()); 

    System.out.println(); 

    System.out.println("direct: stringValue = " + test.getStringValue()); 
    test.changeField("stringValue", "84"); 
    System.out.println("direct: stringValue = " + test.getStringValue()); 
    } 
} 

यहाँ उत्पादन मैं (ग्रहण के तहत, जावा 1,6)

direct: primitiveInt = 42 
reflection: primitiveInt = 84 
direct: primitiveInt = 42 

direct: wrappedInt = 42 
reflection: wrappedInt = 84 
direct: wrappedInt = 84 

direct: stringValue = 42 
reflection: stringValue = 84 
direct: stringValue = 42 

क्यों बिल्ली getWrappedInt के लिए सीधी कॉल करता है() परिवर्तित कर सकते हैं?

+5

असली सवाल यह है कि "अन्य दो क्यों नहीं बदलते?" और जवाब यह है - क्योंकि उनके मूल्य गेटर्स में रेखांकित हैं (इस व्यवहार का वर्णन करने वाले जेएलएस अनुभागों के टॉम का उत्तर बिंदु)।इनलाइनिंग केवल प्राचीन प्रकारों और स्ट्रिंग के लिए होती है (अंतिम/स्थिर अभिव्यक्ति/आदि जैसी अन्य स्थितियों को मानते हुए ... संतुष्ट हैं)। इस प्रकार गेटर्स से int और स्ट्रिंग वापसी 42; इंटीजर नहीं करता है। ** वास्तविक ** मान सभी 3 क्षेत्रों के लिए बदल दिए गए हैं। यदि यह उलझन में है - और यह है :-) - कक्षा को कम करें और आप देखेंगे कि मेरा क्या मतलब है। – ChssPly76

+0

आह, विचित्र ........ –

6

प्रतिबिंब set(..) विधि FieldAccessor एस के साथ काम करता है।

int के लिए यह एक UnsafeQualifiedIntegerFieldAccessorImpl, जिसका सुपर क्लास readOnly संपत्ति को परिभाषित करता है सच होना ही अगर क्षेत्र है हो जाता है दोनोंstatic और final

तो सबसे पहले अचानक सवाल का जवाब देने - यहाँ क्यों final के बिना बदल रहा है अपवाद।

UnsafeQualifiedFieldAccessor के सभी उप-वर्ग मूल्य प्राप्त करने के लिए sun.misc.Unsafe कक्षा का उपयोग करें। विधियों में सभी native हैं, लेकिन उनके नाम getVolatileInt(..) और getInt(..) (getVolatileObject(..) और getObject(..) क्रमशः) हैं। उपर्युक्त एक्सेसर्स "अस्थिर" संस्करण का उपयोग करते हैं।यहाँ क्या हम नॉन-वोलाटाइल संस्करण जोड़ते हैं क्या होता है:

System.out.println("reflection: non-volatile primitiveInt = " 
    unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt")))); 

(जहां unsafe प्रतिबिंब द्वारा instantiated है - यह अन्यथा अनुमति नहीं है) (और मैं Integer और String के लिए getObject कहते हैं)

कि देता है कुछ रोचक परिणाम:

reflection: primitiveInt = 84 
direct: primitiveInt = 42 
reflection: non-volatile primitiveInt = 84 
reflection: wrappedInt = 84 
direct: wrappedInt = 84 
reflection: non-volatile wrappedInt = 84 
reflection: stringValue = 84 
direct: stringValue = 42 
reflection: non-volatile stringValue = 84 

इस बिंदु पर मैं एक संबंधित मामले पर चर्चा an article at javaspecialists.eu याद। यह उद्धरण JSR-133:

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

अध्याय 9 इस प्रश्न में दिए गए विवरणों पर चर्चा करता है।

और यह पता चला है कि यह व्यवहार अप्रत्याशित नहीं है, क्योंकि final फ़ील्ड के संशोधनों को ऑब्जेक्ट के प्रारंभ के बाद ही सही होना चाहिए।

+1

'wrappedInt' पढ़ने के लिए आप 'getInt' –

+0

धन्यवाद के बजाय' getObject' का उपयोग कर सकते हैं, यह सही है। मैंने जवाब अपडेट किया। – Bozho

10

मेरी राय में यह और भी बदतर है: एक सहयोगी ने निम्नलिखित अजीब बात की ओर इशारा किया।

@Test public void testInteger() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {     
    Field value = Integer.class.getDeclaredField("value");     
    value.setAccessible(true);     
    Integer manipulatedInt = Integer.valueOf(7);     
    value.setInt(manipulatedInt, 666);     
    Integer testInt = Integer.valueOf(7);     
    System.out.println(testInt.toString()); 
} 

ऐसा करने से, आप पूरे JVM आप में चल रहे हैं के व्यवहार को बदल सकते (बेशक आप -127 और 127 के बीच मानों के लिए केवल मूल्यों को बदल सकते हैं)

+1

यह बेहद बुराई के लिए सिर्फ एक उत्थान का हकदार है। –

+0

असल में मुझे लगता है कि इंटीग्रर्स के पास कैश की ऊपरी सीमा के लिए मूल्य हो सकता है, ताकि आप उससे अधिक जा सकें! –

+0

साक्षात्कार प्रश्न :) – TWiStErRob

1

इसके लिए एक कार्य है। यदि आप स्थैतिक {} ब्लॉक में दायर निजी स्थैतिक फाइनल का मान निर्धारित करते हैं तो यह काम करेगा क्योंकि यह फ़ाइलld इनलाइन नहीं करेगा:

private static final String MY_FIELD; 

static { 
    MY_FIELD = "SomeText" 
} 

... 

Field field = VisitorId.class.getDeclaredField("MY_FIELD"); 

field.setAccessible(true); 
field.set(field, "fakeText");