Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't String toCharArray use Arrays.copyOf?

Tags:

java

In the JDK source of java.lang.String.toCharArray, it doesn't use Arrays.copyOf to implement this and it says:

Cannot use Arrays.copyOf because of class initialization order issues

What are the "class initialization order issues"?

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; } 
like image 892
shaoyihe Avatar asked Apr 08 '18 07:04

shaoyihe


People also ask

What is string toCharArray?

The Java String toCharArray() method converts the string to a char array and returns it. The syntax of the toCharArray() method is: string.toCharArray() Here, string is an object of the String class.

How does toCharArray work in Java?

The java string toCharArray() method converts the given string into a sequence of characters. The returned array length is equal to the length of the string. Syntax : public char[] toCharArray() Return : It returns a newly allocated character array.

Why do we use toCharArray?

The toCharArray() method converts a string to a char array in Java. This method lets you manipulate individual characters in a string as list items. Spaces, numbers, letters, and other characters are all added to the char array.


1 Answers

I did a test about this. Following is the works I've done:

  1. Get the source code of String.java from JDK.
  2. Modify its toCharArray method to use Arrays.copyOf.

    like this:

    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. compile this modified String, and save it back into JRE's rt.jar.

  4. Write a simple HelloWorld Java code.

  5. Compile & run the code using java program.

And finally, I get this:

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) 

We can see there is truly an initialization error. And because System.initProperties is a native method, I can not check its code.

However, We can make a guess why this could happen:

  1. System.initProperties needs to handle some Strings while it initializes system properties.
  2. And while doing initialize, it might invoke String.toCharArray to get char arrays from those strings.
  3. Strings invoke Arrays.copyOf, but at this point & this time, Arrays has not been loaded / initialized.
  4. Differing from running Java code, the native code would not ask for a class initializing request (I'm not sure about this, please let me know if I'm wrong!!), and which will lead to a NullPointerException and make VM exit.

2018.04.10 Update.

I'd like to Thank @Radiodef for his hint. But when I tried going into the C++ codes, I was stopped by so many execution paths which I could not handle without running or debugging the OpenJDK's JVM.

And then, I changed my strategy. I did some more test based on above which I had done for a few days.

This time, I'm not going to use Arrays.copyOf with String.toCharArray, instead, I attempt to find out which codes will invoke toCharArray method while JVM initializing.

So I modify String, add two static variables to it:

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

In which count is used to count the invocation of toCharArray, logs is used to keep those invocation's stack traces.

In toCharArray method:

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; } 

After done those, I compile String again and save it back into rt.jar.

Then, I write a test program to print count and invocation stack traces out:

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); } 

We can not access String.count & String.logs directly in our codes, as compiler (javac) does not recognize these fields and will cause a compile error. That's why I'm using reflect-way to do this.

Run the program we've just written, and the results will be:

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) 

What a long invocation list. However it is clearer than the previous test. We can clearly see which classes invoke toCharArray.

like image 133
terry.qiao Avatar answered Oct 03 '22 00:10

terry.qiao