Подтвердить что ты не робот

Могут ли файлы классов Java использовать зарезервированные ключевые слова в качестве имен?

Я знаю, что Java-язык компилятивного программирования не является одним и тем же, что и Java-by-encode-format-for-JVM-исполнение. Есть примеры вещей, которые действительны в формате .class, но не в исходном коде .java, таких как классы без конструктора и синтетические методы.

  • Если мы создадим файл .class с зарезервированным ключевым словом Java (например, int, while) в качестве класса, метода или имени поля, виртуальная машина Java примет его для загрузки

  • Если класс загружен, означает ли это, что единственный способ доступа к этому классу или члену - через отражение Java, потому что это имя является синтаксически незаконным на языке программирования Java?

4b9b3361

Ответ 1

Единственными ограничениями на имена классов на уровне байт-кода являются то, что они не могут содержать символы [, . или ; и что они не более 65535 байт. Помимо всего прочего, это означает, что вы можете свободно использовать зарезервированные слова, пробелы, специальные символы, Unicode или даже такие странные вещи, как новые строки.

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

Вот пример некоторых вещей, которые вы можете сделать (написано на сборке Кракатау):

; Entry point for the jar
.class Main
.super java/lang/Object

.method public static main : ([Ljava/lang/String;)V
    .limit stack 10
    .limit locals 10
    invokestatic int                                hello ()V
    invokestatic "-42"                              hello ()V
    invokestatic ""                                 hello ()V
    invokestatic "  some  whitespace and \t tabs"   hello ()V
    invokestatic "new\nline"                        hello ()V
    invokestatic 'name with "Quotes" in it'         hello ()V
    return
.end method
.end class


.class int
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from int"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class "-42"
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from -42"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

; Even the empty string can be a class name!
.class ""
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from "
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class "  some  whitespace and \t tabs"
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from   some  whitespace and \t tabs"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class "new\nline"
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from new\nline"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

.class 'name with "Quotes" in it'
.super java/lang/Object
.method public static hello : ()V
    .limit stack 2
    .limit locals 0
    getstatic java/lang/System out Ljava/io/PrintStream;
    ldc "Hello from name with \"Quotes\" in it"
    invokevirtual java/io/PrintStream println (Ljava/lang/Object;)V
    return
.end method
.end class

Выход выполнения:

Hello from int
Hello from -42
Hello from
Hello from   some  whitespace and        tabs
Hello from new
line
Hello from name with "Quotes" in it

См. ответ Holger для точной цитаты правил из спецификации JVM.

Ответ 2

Да, вы можете использовать зарезервированные слова. Слова предназначены только для компилятора. Они не отображаются в сгенерированном байт-коде.

Пример использования зарезервированных слов Java находится на языке Scala на основе JVM. Scala имеет разные конструкции и синтаксис, чем Java, но компилируется в байт-код Java, для работы на JVM.

Это законно Scala:

class `class`

Определяет класс с именем class с конструктором no-arg. Запуск javap (дизассемблер) в скомпилированном файле class.class показывает

public class class {
    public class();
}

Scala может сделать то же самое с любым другим зарезервированным словом Java.

class int
class `while`
class goto

Они также могут использоваться для имени метода или поля.

Как вы подозревали, вы не сможете использовать эти классы из Java, за исключением отражения. Вы можете использовать их из аналогичного "настроенного" файла класса, например. из файла класса, сгенерированного компилятором Scala.

Таким образом, это ограничение javac (компилятор), а не java (среда VM/runtime).

Ответ 3

Ограничения на имена фиксируются в спецификации JVM:

§4.2.1. Бинарный класс и имена интерфейсов

Имена классов и интерфейсов, которые появляются в файловых структурах классов, всегда представлены в полностью квалифицированной форме, называемой двоичными именами (JLS §13.1). Такие имена всегда представлены в виде структур CONSTANT_Utf8_info (§4.4.7) и, следовательно, могут быть оттянуты, если они еще не ограничены, из всего кода Юникода...

По историческим причинам синтаксис двоичных имен, которые появляются в структурах файлов классов, отличается от синтаксиса двоичных имен, задокументированных в JLS §13.1. В этой внутренней форме периоды ASCII (.), которые обычно разделяют идентификаторы, которые составляют двоичное имя, заменяются косой чертой ASCII (/). Сами идентификаторы должны быть неквалифицированными именами (п. 4.2.2).

§4.2.2. Неквалифицированные имена

Имена методов, полей, локальных переменных и формальных параметров хранятся как неквалифицированные имена. Неквалифицированное имя должно содержать по крайней мере одну кодовую точку Unicode и не должно содержать никаких символов ASCII . ; [ / (то есть период или точка с запятой или левая квадратная скобка или косая черта).

Имена методов дополнительно ограничены, так что, за исключением специальных имен методов <init> и <clinit> (§2.9), они не должны содержать символы ASCII < или > (то есть слева угловой кронштейн или угловой кронштейн).

Итак, ответ: есть только несколько символов, которые вы не можете использовать на двоичном уровне. Во-первых, / является разделителем пакетов. Тогда ; и [ нельзя использовать, поскольку они имеют особое значение в подписях и подписи который может содержать имена типов. В этих подписях [ начинается тип массива, а ; обозначает конец имени ссылочного типа.

Нет четкой причины, по которой . запрещено. Он не используется в JVM и имеет значение только в общих подписях, но если вы используете общие подписи, имена типов дополнительно ограничены тем, что им не разрешено содержать <, >, :, а также эти символы имеют особое значение и в общих подписях.

Следовательно, нарушение спецификации с помощью . внутри идентификаторов не влияет на основную функцию JVM. Это делают обфускаторы. Полученный код работает, но вы можете столкнуться с проблемами с Reflection при запросе подписей типа Generic. Кроме того, преобразование двоичных имен в имя источника путем замены всех / на . станет необратимым, если двоичное имя содержит . s.


Может быть интересно, что было предложение для поддержки всех возможных идентификаторов в синтаксисе Java (см. пункт 3, "экзотические идентификаторы" ), но это не сделало в финальную Java 7. И, похоже, никто в настоящее время не предпринимает новых попыток ее вносить.


Существует дополнительное техническое ограничение на то, что имена can not имеют измененное представление UTF-8 длиной более 65535 байт, так как количество байтов хранится как короткое значение без знака.

Ответ 4

  • Ключевые слова известны только компилятору. Компилятор переводит их в адекватный байт-код. Таким образом, они не существуют во время выполнения скомпилированный байт-код и, следовательно, не проверяется JVM.
  • Несомненно, вы не можете получить доступ к членам класса, которые неизвестны в время компиляции. Но вы можете использовать отражение для этой цели, если вы что такой член класса будет существовать в скомпилированном коде (вы будет "ручным" их там), потому что доступ через отражение не проверяется компилятором.