Переменные с одинаковым именем, но с другим типом

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

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

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

В любом случае вот выписка с сайта

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

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

4 ответа

Решение

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

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

class test
{
    private int x;
    private double x;

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

Java-компилятор не может понять, на какой х вы на самом деле ссылаетесь. Поэтому такой код не является синтаксически правильным и не компилируемым.

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

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

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

Как говорили другие, нелегально в 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 синтетическое поле для кэширования вызова метода, см. /questions/5872701/chto-delaet-klyuchevoe-slovo-java-assert-i-kogda-ono-dolzhno-ispolzovatsya/5872719#5872719 для получения подробной информации.

Затем:

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

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

жасмин

Конечно, мы также можем попробовать это с Жасмин:

.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 указывает на тип, очевидно,

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

Согласно спецификации языка ( JLS 8.3):

Это ошибка времени компиляции, когда тело объявления класса объявляет два поля с одинаковыми именами.

Утверждение, которое вы цитировали, относится к файлу класса (т.е. скомпилированному файлу, а не к исходному коду).

Конечно, вы не можете иметь поля 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.

Трудно показать рабочий пример того, что я пытаюсь доказать. Но давайте рассмотрим методы, похожую проблему. Анализ байт-кода показывает, что в классе 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;
Другие вопросы по тегам