Ответ 1
Длина строкового литерала (т.е. "..."
) ограничена структурой файла 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 делая окончательный arraycopy. Это эскиз, таким образом, это самая простая реализация.) -
Из нашего расширения создайте объект этого класса, и добавьте элементы к этому объекту, вызвав его метод
.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 });