Почему String toCharArray не использует Arrays.copyOf?

В JDK-источнике java.lang.String.toCharArray он не использует Arrays.copyOf для реализации этого, и он говорит:

Невозможно использовать Arrays.copyOf из-за проблем с порядком инициализации класса

Каковы "проблемы с порядком инициализации класса"?

public char[] toCharArray() {
    // Cannot use Arrays.copyOf because of class initialization order issues
    char result[] = new char[value.length];
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}

Ответ 1

Я сделал тест об этом. Ниже приведены работы, которые я сделал:

  1. Получите исходный код String.java от JDK.
  2. Измените его метод toCharArray для использования Arrays.copyOf.

    как это:

    public char[] toCharArray() {
        // Cannot use Arrays.copyOf because of class initialization order issues
        /*char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;*/
        return Arrays.copyOf(value, value.length);
    }
    
  3. скомпилируйте эту модифицированную String и сохраните ее обратно в JRE rt.jar.

  4. Напишите простой Java-код HelloWorld.

  5. Скомпилируйте и запустите код с помощью java программы.

И, наконец, я понимаю:

PS D:\> & 'C:\Program Files (x86)\Java\jdk1.8.0_121\jre\bin\java.exe' StringTest
Error occurred during initialization of VM
java.lang.NullPointerException
    at java.util.Hashtable.remove(Hashtable.java:491)
    at java.lang.System.initProperties(Native Method)
    at java.lang.System.initializeSystemClass(System.java:1166)

Мы видим, что действительно есть ошибка initialization. И поскольку System.initProperties является родным методом, я не могу проверить его код.

Однако мы можем предположить, почему это может произойти:

  1. System.initProperties должен обрабатывать некоторые строки, когда он инициализирует свойства системы.
  2. И, выполняя инициализацию, он может вызывать String.toCharArray для получения массивов char из этих строк.
  3. Строки вызывают Arrays.copyOf, но в этот момент и на этот раз Arrays не были загружены/инициализированы.
  4. В отличие от исполняемого кода Java, собственный код не запрашивает class initializing request (я не уверен в этом, сообщите мне, если я ошибаюсь !!), и это приведет к NullPointerException и NullPointerException VM,

2018.04.10 Обновление.

Я хотел бы поблагодарить @Radiodef за его намек. Но когда я попытался войти в коды C++, меня остановили так много путей выполнения, с которыми я не мог справиться без запуска или отладки OpenJDK JVM.

И затем я изменил свою стратегию. Я сделал еще несколько тестов на основе выше, которые я сделал в течение нескольких дней.

На этот раз я не буду использовать Arrays.copyOf с String.toCharArray, вместо этого String.toCharArray выяснить, какие коды будут вызывать toCharArray при инициализации JVM.

Поэтому я изменяю String, добавляю к ней две статические переменные:

public static int count;
public static Throwable[] logs = new Throwable[10000];

В котором count используется для подсчета вызова toCharArray, logs используются для сохранения этих стеков стека вызовов.

В методе toCharArray:

public char[] toCharArray() {
    if (count < logs.length) {
        try {
            throw new RuntimeException();
        } catch (Throwable e) {
            logs[count] = e;
        }
    }
    count++;

    // Cannot use Arrays.copyOf because of class initialization order issues
    char result[] = new char[value.length];
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}

После этого я снова скомпилирую String и сохраню его обратно в rt.jar.

Затем я пишу тестовую программу для печати трассировки стека count и вызова:

Class<String> clazz = String.class;
Field count = clazz.getDeclaredField("count");
System.out.println(count.getInt(null));
Field logs = clazz.getDeclaredField("logs");
Throwable[] arr = (Throwable[]) logs.get(null);
for (Throwable e : arr) {
    if (e != null)
        e.printStackTrace(System.out);
}

Мы не можем получить доступ к String.count & String.logs непосредственно в наших кодах, поскольку компилятор (javac) не распознает эти поля и вызовет ошибку компиляции. Вот почему я использую рефлекторный способ сделать это.

Запустите программу, которую мы только что написали, и результаты будут следующими:

525
java.lang.RuntimeException
    at java.lang.String.toCharArray(String.java:2889)
    at sun.nio.cs.ext.GBK.initb2c(Unknown Source)
    at sun.nio.cs.ext.GBK.newDecoder(Unknown Source)
    at java.lang.StringCoding$StringDecoder.<init>(Unknown Source)
    at java.lang.StringCoding$StringDecoder.<init>(Unknown Source)
    at java.lang.StringCoding.decode(Unknown Source)
    at java.lang.String.<init>(String.java:414)
    at java.lang.String.<init>(String.java:479)
    at java.lang.System.initProperties(Native Method)
    at java.lang.System.initializeSystemClass(Unknown Source)

......

java.lang.RuntimeException
    at java.lang.String.toCharArray(String.java:2889)
    at sun.net.www.ParseUtil.encodePath(Unknown Source)
    at sun.misc.URLClassPath$FileLoader.getResource(Unknown Source)
    at sun.misc.URLClassPath.getResource(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

Какой длинный список вызовов. Однако это яснее предыдущего теста. Мы можем четко видеть, какие классы вызываются toCharArray.