слишком длинные имена внутренних классов Java - PullRequest
4 голосов
/ 17 июня 2009

Я перевожу программы некоторых языков на вложенные классы Java. В какой-то момент уровень вложенности становится настолько глубоким, что я получаю:

компиляция Test.javaTest.java:5179: ошибка при записи: Test $ 2 ... $ 1.class (слишком длинное имя файла)

где ... длинная строка.

Я использую файловую систему ext3, поэтому я ограничен именами файлов длиной 256 символов. Кроме того, я хотел бы продолжить этот метод перевода (для внутренних классов) на данный момент, потому что я больше заинтересован в тестировании языка, чем в выполнении преобразования замыкания, что решило бы проблему. Есть быстрый и грязный способ обойти это? (используя другую файловую систему или сообщая javac для создания разных имен файлов?)

Ответы [ 4 ]

3 голосов
/ 17 июня 2009

Итог, да, это выполнимо. Можно изменить имя внутреннего класса, поэтому оно короче исходного имени, присвоенного javac.

Я искал Спецификации языка Java и Спецификации виртуальной машины Java , чтобы найти, где говорится об использовании символа $ для обозначения внутреннего класса, и не было смог найти ссылку на него. Причина в том, что это не имеет значения.

Дело и точка:

class A {
    class B {
        class C {}
    }

    A() {
        new B().new C();
    }

    public static void main(String[] s){
        new A();
    }
}

Здесь у нас есть вложенные внутренние классы. После компиляции мы получаем следующие файлы:

A.class
A$B.class
A$B$C.class

Вот быстрый эксперимент:

  1. Откройте файл A.class, измените ссылку на A$B$C и измените на ABCDE.
  2. Переименуйте A$B$C.class в ABCDE.class.
  3. Откройте ABCDE.class и измените ссылку на ABCDE.
  4. Запустите java A, посмотрите, работает ли он.

Примечание. Причина, по которой A$B$C был изменен на ABCDE, заключается в том, что изменение длины идентификатора, похоже, искажает формат файла class и вызывает ошибку. Техническое объяснение будет в конце этого поста.

Результат? Это работает.

Причина в файле class. Вот разборка оригинального A.class и только соответствующих частей:

Compiled from "A.java"
class A extends java.lang.Object
  SourceFile: "A.java"
  InnerClass: 
   #10= #3 of #7; //B=class A$B of class A
   #22= #2 of #3; //C=class A$B$C of class A$B

// ... snip ... //

const #2 = class    #21;    //  A$B$C

// ... snip ... //

const #21 = Asciz   A$B$C;

// ... snip ...//

Оказывается, имена внутренних классов - это просто имена в пуле констант.

Если имя класса A$B$C в пуле констант A.class изменяется на ABCDE, а если имя и имя файла A$B$C class 'в файле class изменяется, то Виртуальная машина Java будет успешно выполняться с новым внутренним классом.

Что это значит?

Не нужно использовать MyClass$1$1$1 ... $1 для имени класса, но все остальное, что соответствует его потребностям, следовательно, было бы возможно иметь больше комбинаций в более коротком имени файла.

Как бы кто-то пошел и сделал это? Это я оставлю читателю в качестве упражнения.

Примечание об использовании ABCDE в качестве имени нового класса

В этом посте имя вложенного внутреннего класса A$B$C было изменено на ABCDE, чтобы сохранить длину имени класса одинаковой, чтобы предотвратить выброс ClassFormatError. Причина этого заключается в том, что структура CONSTANT_Utf8_info пула констант имеет свойство length, которое обозначает длину строки. Я не смог изменить длину, так как редактировал файл class в текстовом редакторе.

Чтобы сократить строку в пуле констант, я бы предположил, что нужно изменить значение поля length, чтобы отразить длину самой строки.

Обновление

Да, есть возможность отредактировать пул констант файла class, чтобы сократить имя внутреннего класса.

Мне удалось изменить класс ABCDE на Z класс.

Вот часть разборки A.class:

Compiled from "A.java"
class A extends java.lang.Object
  SourceFile: "A.java"
  InnerClass: 
   #10= #3 of #7; //B=class A$B of class A
   #22= #2 of #3; //C=class Z of class A$B

// ... snip ...//

const #2 = class    #21;    //  Z

// ... snip ...//

const #21 = Asciz   Z;

// ... snip ...//

Как видно, внутренний класс теперь обозначается как Z, а не A$B$C.

Изменение было выполнено путем поиска строки A$B$C в файлах A.class и A$B$C.class, замены ее на Z и изменения символа перед строкой со значения 0x05 на 0x01 означает, что длина строки теперь равна 1, а не 5.

С этими изменениями, наряду с переименованием файла в Z.class, программа работала так, как будто ничего не происходило.

Итак, да, возможно также сократить имя внутреннего класса.

1 голос
/ 17 июня 2009

1 возможным решением является компиляция в другой операционной системе, а затем использование Obfuscater, например yGuard . По умолчанию obfuscater изменит имена классов на минимальные (например, A, B, C ...), тем самым значительно сократив имя класса (и, следовательно, имя файла).

Может быть, вам не понадобится, в зависимости от того, что именно вы хотите проверить.

1 голос
/ 17 июня 2009

Вы можете скомпилировать Java изнутри Java, отправив вывод в файловый менеджер, который вы реализуете сами.

Использовать javax.tools.JavaCompiler , с JavaFileManager , который предоставляет вам скомпилированный вывод, который вы, возможно, могли бы записать непосредственно в jar?

0 голосов
/ 17 июня 2009

Сравнение файловых систем , похоже, что ResierFS может быть одним из немногих, кто поддерживает более длинные имена файлов. Я бы с осторожностью отнесся к этому методу, поскольку все инструменты (javac, java, ant, ls, rm, cp и т. Д.) Могут делать предположения о длине имени файла, так как большинство файловых систем имеют размер 255, и вы будете привязаны к одной FS ( что, если это исчезнет?) Если это чисто академический процесс, который переформатируют (или используют виртуализацию).

Возможно, вам просто нужно пересмотреть свой алгоритм, чтобы избежать столь глубокого вложения классов. Вы можете использовать несколько файлов? Я знаю, что вы не хотите сделать это, но это может быть только опция

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...