سؤال

لنبدأ بحالة اختبار بسيطة:

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
هل كانت مفيدة؟

المحلول

يتم تضمين ثوابت وقت الترجمة (في وقت الترجمة javac).راجع JLS، على وجه الخصوص 15.28 يحدد تعبيرًا ثابتًا ويناقش 13.4.9 التوافق الثنائي أو الحقول والثوابت النهائية.

إذا جعلت الحقل غير نهائي أو قمت بتعيين ثابت وقت غير مترجم، فلن يتم تضمين القيمة.على سبيل المثال:

سلسلة نهائية خاصة stringValue = null!=null؟"":"42" ؛

نصائح أخرى

وهذا في رأيي أسوأ من ذلك:وأشار أحد الزملاء إلى الأمر المضحك التالي:

@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 فقط)

خواطر 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 is instantiated by reflection - it is not allowed otherwise) (and I call getObject ل Integer و String)

وهذا يعطي بعض النتائج المثيرة للاهتمام:

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

في هذه المرحلة أتذكر مقال في javaspecialists.eu مناقشة مسألة ذات صلة.يقتبس جي إس آر-133:

إذا تمت تهيئة الحقل النهائي إلى ثابت وقت الترجمة في إعلان الحقل، فقد لا يتم ملاحظة التغييرات في الحقل النهائي، حيث يتم استبدال استخدامات هذا الحقل النهائي في وقت الترجمة بثابت وقت الترجمة.

ويناقش الفصل التاسع التفاصيل التي لوحظت في هذا السؤال.

واتضح أن هذا السلوك ليس غير متوقع، منذ التعديلات final من المفترض أن تحدث الحقول مباشرة بعد تهيئة الكائن.

وهذا ليس جوابا، ولكنه يثير نقطة أخرى من الارتباك:

كنت أرغب في معرفة ما إذا كانت المشكلة تتعلق بتقييم وقت الترجمة أو ما إذا كان الانعكاس يسمح بالفعل لـ Java بالتغلب على final الكلمة الرئيسية.هنا برنامج اختبار.كل ما أضفته هو مجموعة أخرى من مكالمات getter، لذا هناك واحدة قبل كل منها وبعدها 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());
  }
}

هذا هو الناتج الذي أحصل عليه (ضمن Eclipse، Java 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()؟

هناك عمل حول هذا.إذا قمت بتعيين قيمة الملف النهائي الثابت الخاص الذي تم تقديمه في الكتلة الثابتة {}، فسوف يعمل لأنه لن يتم تضمين الحقل:

private static final String MY_FIELD;

static {
    MY_FIELD = "SomeText"
}

...

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

field.setAccessible(true);
field.set(field, "fakeText");
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top