与使用显式类型“String”相比,为什么使用“newInstance()”方法创建字符串的行为在使用“var”时有所不同?

han*_*szt 71 java reflection string-pool

我正在学习 Java 中的反射。偶然间,我发现了以下对我来说意想不到的行为。

下面写的两个测试都成功。

class NewInstanceUsingReflection {
    @Test
    void testClassNewInstance()
        throws NoSuchMethodException, InvocationTargetException,
        InstantiationException, IllegalAccessException
    {
        final var input = "A string";
        final var theClass = input.getClass();
        final var constructor = theClass.getConstructor();
        final String newString = constructor.newInstance();

        assertEquals("", newString);
    }

    @Test
    void testClassNewInstanceWithVarOnly()
        throws NoSuchMethodException, InvocationTargetException,
        InstantiationException, IllegalAccessException
    {
        final var input = "A string";
        final var theClass = input.getClass();
        final var constructor = theClass.getConstructor();
        final var newString = constructor.newInstance();

        assertEquals("A string", newString);
    }
}
Run Code Online (Sandbox Code Playgroud)

除了断言之外的唯一区别是变量类型在第一个测试中是显式的,并在第二个测试中newString声明。var

我正在使用 java 17 和 junit5 测试框架。

为什么第一个测试中的值为newString空字符串,而input第二个测试中的值为字符串值?

它与字符串池有关系吗?

还是有其他事情发生?

rzw*_*oot 43

Java17,同样的问题。解释很清楚:bug。

反编译它,相关部分:

        20: anewarray     #2                  // class java/lang/Object
        23: invokevirtual #35                 // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;
        26: checkcast     #41                 // class java/lang/String
        29: astore        4
        31: ldc           #23                 // String A string
        33: ldc           #23                 // String A string
        35: invokevirtual #43                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
Run Code Online (Sandbox Code Playgroud)

astore 4是结果所在的位置,但无处可去:插槽 4 不再使用。相反,相同的字符串常量被加载两次,实际上很容易导致 ,"A string".equals("A string")这当然是true

替换var为 String,重新编译并重新运行javap

        20: anewarray     #2                  // class java/lang/Object
        23: invokevirtual #35                 // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;
        26: checkcast     #41                 // class java/lang/String
        29: astore        4
        31: ldc           #23                 // String A string
        33: aload         4
        35: invokevirtual #43                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
Run Code Online (Sandbox Code Playgroud)

ldc除了第二个是正确的之外,其他方面都相同aload 4

我很难弄清楚这里发生了什么。感觉更像是以var某种方式导致ldc重复(与错误地认为值保证相同的分析相反;javac 故意很少进行此类优化)。

我真的很难弄清楚这在2 个LTS 版本中是如何发生的。令人印象深刻的发现。

下一步是验证最新的 JDK (18),然后提交错误。我快速浏览了一下是否已被报道,但我不确定要使用什么搜索词。不过,我在搜索中没有找到任何报告。

注意:反编译痕迹是使用javap -c -v NewInstanceUsingReflection.

编辑:刚刚尝试了 ecj ( Eclipse Compiler for Java(TM) v20210223-0522, 3.25.0, Copyright IBM Corp 2000, 2020. All rights reserved.) - 那里没有发生错误。

  • FTR:我已向 Oracle 提交了错误报告。我收到问题后会立即发布该问题的链接。 (7认同)
  • 更新:[这里是错误报告(ID:JDK-8293578)](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8293578) (7认同)
  • 至于是什么原因导致这个(我认为......):常量在 javac 中表示为类型(就像它们在许多其他编译器中一样)。由于 `getClass()` 返回 `Class<? extends [exact type]>`,最终会变成 `Class<? 扩展“A string”>`(常量是类型)。那么构造函数也是 `Constructor<? extends "A string">`,最后 `newInstance` 返回一个 `"A string"` 作为类型。所以 `newString` 的类型是常量,然后它会变成一个 LDC。该错误在于如何确定“getClass”的类型。 (7认同)
  • 适合那些喜欢在 JIRA 界面中阅读 JDK 票证的人的链接:https://bugs.openjdk.org/browse/JDK-8293578 (4认同)
  • @yyyy 为了“你有哪些成员”的目的,我打赌这正是 String 所拥有的,其中特别包括一个无参数构造函数。问题是 javac 本身是这样的:哦,它是一个 [voodoo magic here] 类型的表达式,因此我可以直接从常量池加载。在字节码级别没有什么特别的事情发生(“javac”只是编写了“错误的”字节码)。 (2认同)