Переменные, имеющие одно и то же имя, но разные типы

Я прочитал здесь, что в Java возможно, что две переменные, имеющие одно и то же имя, но разные типы, будут сосуществовать в одной области. Я имею в виду это

class test
{
    private int x;
    private double x;
}

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

В любом случае вот выдержка из сайта

"Если вам повезет, вы можете перекомпилировать вывод из Jad. Однако у виртуальной машины Java есть более мягкие правила для именования переменных, чем сам язык Java. Для экземпляров действительный файл класса может имеют несколько переменных с именем" a", если у них разные типы. Если вы декомпилируете такой класс, исходный код, который вы получите, будет недействительным.

JAD обычно переименовывает оскорбительные поля и делает перекомпилируемый файл... единственная проблема заключается в том, что перекомпилированный файл не будет совместим с исходными классами.

Ответ 1

Вы не можете иметь переменные с таким же именем (но другим типом), чтобы существовать в той же области. Подумайте, возможно ли тогда, как java-компилятор определит, что вы имели в виду.

Рассмотрим этот фрагмент кода

class test
{
    private int x;
    private double x;

    test() //constructor
    {
        System.out.println(x); //Error cannot determine which x you meant
    } 
}

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

Однако существуют такие инструменты, как ClassEditor, которые могут изменять сгенерированный файл класса после его создания. Там можно изменить имя двух переменных на одно и то же.

Однако такой класс не обязательно запускается java jvm.

Программное обеспечение, которое вы указали i.e JAD, может переименовать такие повторяющиеся именованные переменные в файле класса, чтобы исходный код, который вы получите, действительно будет синтаксически правильным.

Ответ 2

Как говорили другие, незаконным на Java, но законным в байт-коде.

javac assert

assert - пример Java, который в Oracle JDK 1.8.0_45 генерирует несколько полей с тем же именем, но с разными типами. Например:.

public class Assert {
    // We can't use a primitive like int here or it would get inlined.
    static final int[] $assertionsDisabled = new int[0];
    public static void main(String[] args) {
        System.out.println($assertionsDisabled.length);
        // currentTimeMillis so it won't get optimized away.
        assert System.currentTimeMillis() == 0L;
    }
}

Присутствие assert генерирует синтетическое поле bool $assertionsDisable для кэширования вызова метода, см. fooobar.com/questions/16319/....

Тогда:

javac Assert.java
javap -c -constants -private -verbose Assert.class

содержит строки:

 #3 = Fieldref           #9.#28         // Assert.$assertionsDisabled:[I
 #5 = Fieldref           #9.#31         // Assert.$assertionsDisabled:Z
#12 = Utf8               $assertionsDisabled
#28 = NameAndType        #12:#13        // $assertionsDisabled:[I
#31 = NameAndType        #12:#14        // $assertionsDisabled:Z

public static void main(java.lang.String[]);
 3: getstatic     #3                  // Field $assertionsDisabled:[I
10: getstatic     #5                  // Field $assertionsDisabled:Z

Обратите внимание, что таблица констант даже повторно использует #12 как имя переменной.

Если бы мы объявили еще одно логическое значение, оно не скомпилировалось бы:

static final boolean $assertionsDisabled = false;

с ошибкой:

the symbol $assertionsDisabled conflicts with a compile synthesized symbol

Вот почему очень плохое использование имен полей со знаками доллара: Когда я должен использовать символ доллара ($) в имени переменной?

жасмин

Конечно, мы также можем попробовать его с помощью Jasmin:

.class public FieldOverload
.super java/lang/Object

.field static f I
.field static f F

.method public static main([Ljava/lang/String;)V
    .limit stack 2

    ldc 1
    putstatic FieldOverload/f I
    ldc 1.5
    putstatic FieldOverload/f F

    getstatic java/lang/System/out Ljava/io/PrintStream;
    getstatic FieldOverload/f I
    invokevirtual java/io/PrintStream/println(I)V

    getstatic java/lang/System/out Ljava/io/PrintStream;
    getstatic FieldOverload/f F
    invokevirtual java/io/PrintStream/println(F)V

    return
.end method

который содержит два статических поля: один int (I) и один float (F), и выводит:

1
1.5

Если работает, потому что:

  • getstatic указывает на структуру Fieldref в таблице констант
  • Fieldref указывает на NameAndType
  • NameAndType указывает на тип, очевидно

Итак, чтобы различать их, Jasmin просто использует два разных Fieldref с разными типами.

Ответ 3

В соответствии со спецификацией языка (JLS 8.3):

Это ошибка времени компиляции для тела объявления класса для объявления двух полей с тем же именем.

Заявление, которое вы указали, касается файла класса (то есть скомпилированного файла, а не исходного кода).

Ответ 4

Конечно, у вас не может быть int x и long x полей в одном классе, так же как вы не можете иметь два метода с тем же именем и списком параметров. Но это на уровне источника. JVM и байт-код имеют разные правила. Рассмотрим это:

пакетный тест;

public class Test {
    static int x;

    @Override
    protected Test clone() throws CloneNotSupportedException {
        return this;
    }

    public static void main(String[] args) {
        int y = x;
    }
}

Если вы используете инструмент построения байт-кода (я использовал плагин Андрея Лоскутова для Eclipse), вы увидите в этой строке int Test.main()

GETSTATIC test/Test.x : I

Так JVM загружает значение из поля x, его полное имя - "test/Test.x: I". Это дает вам подсказку о том, что значение поля присутствует в полном имени поля.

Не секрет, что javac - это не единственное средство для создания класса, есть инструменты/библиотеки для создания байт-кода напрямую, и они могут создавать классы с полями с тем же именем, но с другим типом. То же самое касается JNI.

Трудно показать рабочий пример того, что я пытаюсь доказать. Но рассмотрим методы, аналогичную проблему. Bytecode analisys показывает, что в классе Test есть два метода с тем же именем и параметрами, которые не разрешены JLS:

protected clone()Ltest/Test; throws java/lang/CloneNotSupportedException 

protected volatile bridge clone()Ljava/lang/Object; throws java/lang/CloneNotSupportedException 

Метод "bridge" был добавлен javac, потому что Test.clone() возвращает Test, и это означает, что он не переопределяет Object.clone(), который возвращает Object, и это связано с тем, что JVM считает, что эти два являются разными методами.

1 clone()Ltest/Test;
2 clone()Ljava/lang/Object;