Почему JVM позволяет нам называть функцию, начинающуюся с цифры в байт-коде?

Идентификаторы хорошо определены Спецификацией языка Java, Java SE 7 Edition (§3.8)

An identifier is an unlimited-length sequence of Java letters and Java digits, the
first of which must be a Java letter.

Насколько мне известно, поскольку имя метода является идентификатором, не следует указывать метод, начинающийся с цифры в java, и javac соблюдает это правило.

Итак, почему виртуальная машина Java, похоже, не соблюдает это правило, позволяя нам называть функцию, начинающуюся с чисел, в Bytecode?


Этот простой фрагмент фактически напечатает имя метода f99() и значение его параметра.

public class Test {
    public static void main(String[] args) {
        Test t = new Test();
        System.out.println(t.f99(100));
    }

    public int f99(int i){
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName());
        return i;
    }
}

Компиляция и выполнение:

$ javac Test.java
$ java Test

Вывод:

f99
100

Можно разобрать код после компиляции и переименовать все f99 вхождения на 99 (с помощью такого инструмента, как reJ).

$ java Test

Вывод:

99
100

Итак, имя метода фактически "99"?

Ответ 1

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

JVM был разработан, чтобы поддерживать языки, отличные от Java. Таким образом, ограничения не должны быть одинаковыми; если мы не хотим заставить все языки, не относящиеся к Java, иметь те же ограничения. Ограничения, выбранные для JVM, - это минимальное множество, допускающее однозначный синтаксический анализ сигнатур метода, формат, который появляется в спецификации JVM, а не JLS.

Взято из JVM Spec

a name must not contain any of the ASCII characters . ; [ / < > :

Таким образом, следующие действительные сигнатуры JVM [Lcom/foo/Bar;, и его специальные символы были исключены из имен методов.

<> дополнительно зарезервировано для разделения специальных методов JVM из методов приложения, в частности <init> и <clinit>, которые являются именами обоих методов, которые JLS не разрешает.

Ответ 2

Итак, имя метода фактически "99"?

Реальные программисты не используют парсеры, они используют sed:

javac Test.java
sed -i 's/\d003f99/\d00299/' Test.class
java Test

Вывод:

99
100

Это работает, потому что мы знаем, что имя метода хранится в пуле констант как открытый текст в записи Utf8, а JVMS говорит, что Utf8 записи имеют вид:

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

поэтому у нас было что-то вроде:

01 | 00 03 | 'f' '9' '9'

(идентификатор длиной 3 байта), а команда sed заменила 03 | 'f' '9' '9' на 02 | '9' '9' (теперь на 2 байта).

Позже я проверил с javap -v Test.class, что sed сделал то, что я хотел. До:

#18 = Utf8               f99

После:

#18 = Utf8               99

После редактирования, запуска, декомпиляции и сравнения .class с JVMS вручную можно сделать вывод, что имя метода должно быть 99: -)

Так что это просто ограничение языка Java, отсутствующее в байт-коде.

Почему Java предотвращает такие имена?

Вероятно, чтобы синтаксис выглядел как C.

Не начинающийся с цифр упрощает дифференцирование идентификаторов из целых литералов для людей и парсеров.

См. также: