为什么String toCharArray不使用Arrays.copyOf?

时间:2018-04-08 07:00:01

标签: java

java.lang.String.toCharArray的JDK源代码中,它不使用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 个答案:

答案 0 :(得分:43)

我对此做了一个测试。以下是我所做的工作:

  1. 从JDK获取String.java的源代码。
  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. 编写一个简单的HelloWorld Java代码。

  5. 编译&使用java程序运行代码。

  6. 最后,我明白了:

    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并使VM退出。
    5. 2018.04.10更新。

      我想感谢@Radiodef的暗示。但是当我尝试使用C ++代码时,我被许多执行路径所阻止,如果不运行或调试OpenJDK的JVM,我无法处理这些路径。

      然后,我改变了策略。我做了一些基于上面的测试,我做了几天。

      这一次,我不打算将Arrays.copyOfString.toCharArray一起使用,相反,我试图找出在JVM初始化时哪些代码会调用toCharArray方法。

      所以我修改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)无法识别这些字段并会导致编译错误。这就是为什么我使用reflect-way来做到这一点。

      运行我们刚刚编写的程序,结果将是:

      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