【译】打印数组的最难得的一种实现方式:侵入JVM转载
Java 中有个讨论最多的问题,那就是打印数组的办法是什么?几乎所有其他流行语言都有比较简单的方法,但是不清楚为什么 Java 仍然用侵入JVM的方式。
与其他 JDK 类不同,数组没有特别健全的 toString(),因为它继承自 Object。
它打印的类型和地址对吗?
实际上,它不会打印地址,它看起来就像一个神秘的一样。它打印类型的内部表示,以及对象的hashCode()。由于所有数组都是一个对象,它们有一个 hashCode() 和一个类型和一个同步锁,以及一个对象所具有的所有其他东西,但没有特定于数组的方法。这就是 toString() 对数组没有用的原因。
它看起来好像没实现?
如果我运行以下程序。
public class ObjectTest {
boolean[] booleans = {true, false};
byte[] bytes = {1, 2, 3};
char[] chars = "Hello World".toCharArray();
short[] shorts = {111, 222, 333};
float[] floats = {1.0f, 2.2f, 3.33f, 44.44f, 55.555f, 666.666f};
int[] ints = {1, 22, 333, 4_444, 55_555, 666_666};
double[] doubles = {Math.PI, Math.E};
long[] longs = {System.currentTimeMillis(), System.nanoTime()};
String[] words = "The quick brown fox jumps over the lazy dog".split(" ");
@Test
public void testToString() throws IllegalAccessException {
Map<String, Object> arrays = new LinkedHashMap<>();
for(Field f : getClass().getDeclaredFields())
arrays.put(f.getName(), f.get(this));
arrays.entrySet().forEach(System.out::println);
}
}
booleans=[Z@277c0f21
bytes=[B@6073f712
chars=[C@43556938
shorts=[S@3d04a311
floats=[F@7a46a697
ints=[I@5f205aa
doubles=[D@6d86b085
longs=[J@75828a0f
words=[Ljava.lang.String;@3abfe836
显而易见,就像 J 是 long 的内部代码 ,L是 Java 类的内部代码一样。当b 未使用时, Z 也是布尔值的代码 。
我们对于它可以做些什么呢?
在这个程序中,我们最终不得不为对象编写一个特殊的 toString 方法,该方法需要由我们用于打印 Map.Entry 的特殊方法调用。重复此多次以提高程序的吞吐量,避免在 Java 中使用数组更容易,因为它们很难调试。
侵入 JVM 到底如何?
我们可以做的是改变 Object.toString()。我们必须更改此类,因为它是我们可以访问的数组的唯一父级。我们无法更改数组的代码,因为它在 JVM 内部。例如,对于所有 byte[] 特定方法,没有 byte[] java 类文件。
获取 java.lang.Object 源的副本并将 toString() 替换为
public String toString() {
if (this instanceof boolean[])
return Arrays.toString((boolean[]) this);
if (this instanceof byte[])
return Arrays.toString((byte[]) this);
if (this instanceof short[])
return Arrays.toString((short[]) this);
if (this instanceof char[])
return Arrays.toString((char[]) this);
if (this instanceof int[])
return Arrays.toString((int[]) this);
if (this instanceof long[])
return Arrays.toString((long[]) this);
if (this instanceof float[])
return Arrays.toString((float[]) this);
if (this instanceof double[])
return Arrays.toString((double[]) this);
if (this instanceof Object[])
return Arrays.deepToString((Object[]) this);
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
在 Java <= 8 中,我们可以通过添加到命令行将此类添加到引导类路径的开头
-Xbootclasspath/p:target/classes
(或者你的类被编译到的任何地方)现在当我们运行我们的程序时,我们看到
booleans=[true, false]
bytes=[1, 2, 3]
chars=[H, e, l, l, o, , W, o, r, l, d]
shorts=[111, 222, 333]
floats=[1.0, 2.2, 3.33, 44.44, 55.555, 666.666]
ints=[1, 22, 333, 4444, 55555, 666666]
doubles=[3.141592653589793, 2.718281828459045]
longs=[1457629893500, 1707696453284240]
words=[The, quick, brown, fox, jumps, over, the, lazy, dog]
就像你用任何其他语言一样。
结论
虽然这是一个很酷的技巧,但最好的解决方案当然是他去修复Java,以便它为数组生成一个健全的输出。
它知道你需要一个并提供它,但将它隐藏在一个你必须用谷歌搜索的类中,所以每个新的 Java 开发人员在他们第一次尝试使用数组时都必须有一个 WTF 时刻。
原文地址:https://dzone.com/articles/printing-arrays-by-hacking-the-jvm
原文作者:Peter Lawrey/Developer, Vanilla Java,伦敦