Статьи

Печать массивов путем взлома JVM

обзор

Одна из самых распространенных ошибок в Java — это умение печатать массивы. Если ответ о том, как напечатать массив, набрал более 1000 голосов, вам нужно задаться вопросом, существует ли более простой способ. Почти любой другой популярный язык имеет такой простой способ, поэтому мне не ясно, почему Java все еще делает это.

В отличие от других классов JDK, массивы не имеют особенно нормального toString (), поскольку он наследуется от Object.

Это печатает тип и адрес правильно?

На самом деле, он не печатает адрес, он выглядит так же загадочно, как и один. Он печатает внутреннее представление типа и hashCode () объекта. Поскольку все массивы являются объектами, они имеют hashCode (), тип и синхронизированную блокировку, а также все, что имеет объект, но не имеют методов, специфичных для массива. Вот почему toString () не используется для массивов.

Как это выглядит без взлома?

Если я запускаю следующую программу.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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);
    }
}

это печатает.

1
2
3
4
5
6
7
8
9
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

Я думаю, что это очевидно для всех. o_O Как тот факт, что J является внутренним кодом для l ong, а L является внутренним кодом для класса J ava. Также Z — это код для логического значения, когда b не используется.

Что мы можем с этим поделать?

В этой программе нам приходится писать специальный метод toString для вызова объекта нашим специальным методом для печати Map.Entry. Повторите это много раз для пропускной способности вашей программы, и просто избежать использования массивов в Java, потому что их сложно отлаживать.

Как насчет взлома JVM?

Что мы можем сделать, это изменить Object.toString (). Мы должны изменить этот класс, так как он является единственным родителем массивов, к которым у нас есть доступ. Мы не можем изменить код для массива, поскольку он является внутренним для JVM. Для всех байтовых методов не существует, например, файла java-класса byte [].

Возьмите копию исходного кода для java.lang.Object и замените toString () на

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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 мы можем добавить этот класс в начало загрузочного пути, добавив в командную строку

1
-Xbootclasspath/p:target/classes

(или где ваши классы были скомпилированы) и теперь, когда мы запускаем нашу программу, мы видим

1
2
3
4
5
6
7
8
9
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, так что это дает нормальный вывод для массивов. Он знает, что он вам нужен, и предоставляет его, но скрывает его в классе, который вы должны найти в Google, чтобы у каждого нового разработчика Java был момент WTF при первой попытке работать с массивами.

Ссылка: Печать массивов путем взлома JVM от нашего партнера JCG Питера Лори в блоге Vanilla Java .