Длина строкового литерала (то есть "..."
) ограничена структурой CONSTANT_Utf8_info
формата файла класса, на которую ссылается структура CONSTANT_String_info
.
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
Ограничивающим фактором здесь является атрибут length
, который имеет размер всего 2 байта, то есть имеет максимальное значение 65535. Это число соответствует количеству байтов в модифицированном представлении строки UTF-8 (на самом деле этопочти CESU-8 , но символ 0 также представлен в двухбайтовой форме).
Таким образом, чистый строковый литерал ASCII может иметь до 65535 символов, тогда как строка, состоящая изсимволов в диапазоне U + 0800 ... U + FFFF имеют только одну треть из них.А те, которые закодированы как суррогатные пары в UTF-8 (то есть U + 10000 до U + 10FFFF), занимают по 6 байтов каждый.
(То же ограничение существует для идентификаторов, то есть имен классов, методов и переменных,и введите для них дескрипторы, поскольку они используют одну и ту же структуру.)
В спецификации языка Java не упоминается никаких ограничений для строковых литералов :
Строкалитерал состоит из нуля или более символов, заключенных в двойные кавычки.
Таким образом, в принципе, компилятор может разбить более длинный строковый литерал на более чем одну CONSTANT_String_info
структуру и реконструировать его во время выполнения путем конкатенации (и .intern()
- результат).Я понятия не имею, если какой-либо компилятор фактически делает это.
Это показывает, что проблема не в строковых литералах, а в инициализаторах массива.
При передаче объекта в BMethod.invoke
(и так же, как BConstructor.newInstance), это может быть либо BObject (то есть оболочка вокруг существующего объекта, затем он будет передавать обернутый объект), String (которая будет передана как есть) или что-нибудь еще.В последнем случае объект будет преобразован в строку (toString()
), а затем эта строка будет интерпретирована как выражение Java.
. Для этого BlueJ обернет это выражение в класс / методи скомпилируйте этот метод.В этом методе инициализатор массива просто преобразуется в длинный список назначений массива ... и это, наконец, делает метод длиннее, чем максимальный размер байт-кода метода Java:
Значение элемента code_length должно быть меньше 65536.
Вот почему оно разбивается на более длинные массивы.
Итак, чтобы передать большие массивы, мы имеемчтобы найти какой-то другой способ передать их в BMethod.invoke.API расширения BlueJ не имеет возможности создавать массивы или получать к ним доступ, заключенные в BObject.
Одна идея, которую мы нашли в чате, такова:
Создайте новый класс внутрипроект (или в новом проекте, если они могут взаимодействовать), что-то вроде этого:
public class IntArrayBuilder {
private ArrayList<Integer> list;
public void addElement(int el) {
list.add(el);
}
public int[] makeArray() {
int[] array = new int[list.size()];
for(int i = 0; i < array.length; i++) {
array[i] = list.get(i);
}
return array;
}
}
(Это для случая создания int[]
- если вам нужны также другие типы массивов,его также можно сделать более универсальным. Кроме того, его можно сделать более эффективным, если использовать внутреннее int[]
в качестве хранилища, время от времени увеличивая его по мере роста, а int makeArray делать окончательную копию массива. Это эскиз, поэтому этопростейшая реализация.)
Из нашего расширения создайте объект этого класса и добавьте элементы к этому объекту, вызвав его метод .addElement
.
BObject arrayToBArray(int[] a) {
BClass builderClass = package.getClass("IntArrayBuilder");
BObject builder = builderClass.getConstructor(new Class<?>[0]).newInstance(new Object[0]);
BMethod addMethod = builderClass.getMethod("addElement", new Class<?>[]{int.class});
for(int e : a) {
addMethod.invoke(builder, new Object[]{ e });
}
BMethod makeMethod = builderClass.getMethod("addElement", new Class<?>[0]);
BObject bArray = (BObject)makeMethod.invoke(builder, new Object[0]);
return bArray;
}
(Для эффективности объекты BClass / BMethod могут быть получены один раз и кэшированы, а не один раз для каждого преобразования массива.)
Если вы генерируете содержимое массивов по какому-либо алгоритму, вы можете сделать это поколениездесь вместо того, чтобы сначала создавать еще один объект переноса.
В нашем расширении вызовите метод, который мы на самом деле хотим вызвать с длинным массивом, передав наш упакованный массив:
Object result = method.invoke(obj, new Object[] { bArray });