2

I have a rough idea, but still would like to ask if anyone knows why String constants provided as annotation defaults change identity even though they reference static constants.

To illustrate, why does this code prints true, true, false, false.

@TestAnn
public class TestClass {

    public static final String STRING_CONSTANT = "SHOULD_BE_CONSTANT";

    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnn {
        String value() default TestClass.STRING_CONSTANT;
    }
    public class OtherTestClass {
        String NOT_EVEN_STATIC = TestClass.STRING_CONSTANT;
    }
    private void run() throws Exception {

        System.out.println(STRING_CONSTANT == constantValue());

        System.out.println(STRING_CONSTANT == new OtherTestClass().NOT_EVEN_STATIC);

        String str1 = getClass().getAnnotation(TestAnn.class).value();
        System.out.println(STRING_CONSTANT == str1);

        String str2 = (String) TestAnn.class.getMethod("value").getDefaultValue();
        System.out.println(STRING_CONSTANT == str2);
    }
    private String constantValue() {
        return TestClass.STRING_CONSTANT;
    }
    public static void main(String[] args) throws Exception {
        new TestClass().run();
    }
}
SGal
  • 1,072
  • 12
  • 13

2 Answers2

0

The tricky things is that when using value() or getDefaultValue() the real value is retrieved using reflection based on its runtime class, basically a new object String object (Type Object) is created and filled with the string value,

So when you trying to use == operator which compare Objects it will return false, but equal will return true because it compare the content,

the first two examples return true because you reference the same object, constantValue() return the same and NOT_EVEN_STATIC store same reference

You can refer to this question to know why always String with constant values are always equal using == , for NOT_EVEN_STATIC example

Cheers

elmehdi
  • 449
  • 2
  • 10
0

Because the String value in really not the intern String, Let's find where the String in annotation from.

First we check code in Class, how it create annotation data.

private AnnotationData createAnnotationData(int classRedefinedCount) {
    Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
        AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
...
}

Then, go to the AnnotationParser.parseAnnotations, we find this:

private static Object parseConst(int tag, ByteBuffer buf, ConstantPool constPool) {
  ...
  case 's': return constPool.getUTF8At(constIndex);
  ...
}

So the AnnotationParser get the String value from ConstantPool. Let's get the value by ourselves:

Method m = Class.class.getDeclaredMethod("getConstantPool");
m.setAccessible(true);
ConstantPool pool = (ConstantPool) m.invoke(getClass());

Then get the String and its intern value, and compare them:

String str = pool.getUTF8At(9); // I find the index by traverse the pool, may different in your code
String intern = str.intern();
System.out.println(str == intern); // false
System.out.println(str == STRING_CONSTANT); // false
System.out.println(intern == STRING_CONSTANT); // true

So the String from ConstantPool is not the intern value. And I checked their id in eclipse

str(id=371), intern(id=372)

So the truth is the ConstantPool do the intern on the constant String, but it returns the origin value it holds which is not intern.

Dean Xu
  • 4,438
  • 1
  • 17
  • 44