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

Почему Java связывает переменные во время компиляции?

Рассмотрим следующий пример кода

class MyClass {
    public String var = "base";

    public void printVar() {
        System.out.println(var);
    }
}

class MyDerivedClass extends MyClass {
    public String var = "derived";

    public void printVar() {
        System.out.println(var);
    }
}

public class Binding {
    public static void main(String[] args) {
        MyClass base = new MyClass();
        MyClass derived = new MyDerivedClass();

        System.out.println(base.var);
        System.out.println(derived.var);
        base.printVar();
        derived.printVar();
    }
}

он дает следующий выход

base
base
base
derived

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

base
derived
base
derived

потому что в производном классе переопределение var затеняет одно в базовом классе.
Почему привязка переменных происходит во время компиляции, а не во время выполнения? Это только по соображениям производительности?

4b9b3361

Ответ 1

Объяснение объясняется в спецификации языка Java в примере в разделе 15.11, приведенном ниже:

...

Последняя строка показывает, что, действительно, доступное поле не зависит от класса времени выполнения ссылочного объекта; даже если s содержит ссылку на объект класса T, выражение s.x относится к полю x класса s, потому что тип выражения s равен s. Объекты класса T содержат два поля с именем x, один для класса T и один для его суперкласса s.

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

Итак, да, это причина. Спецификация выражения выражения доступа к полю указывается следующим образом:

  • Если поле не static:

    ...

    • Если поле является непустым final, то результатом является значение поля с именем-членом в типе T, найденном в объекте, на который ссылается значение Первичного.

где Primary в вашем случае относится к переменной derived, которая имеет тип MyClass.

Другая причина, по мнению @Clashsoft, заключается в том, что в подклассах поля не переопределяются, они скрыты. Таким образом, имеет смысл разрешить доступ к полям на основе объявленного типа или с помощью трансляции. Это также верно для статических методов. Вот почему поле определяется на основе объявленного типа. В отличие от переопределения методами экземпляра, где это зависит от фактического типа. В приведенной выше цитате JLS упоминается эта причина неявно:

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

Ответ 2

Хотя вы можете быть правы в отношении производительности, есть еще одна причина, по которой поля не динамически отправляются: вы не сможете получить доступ к полю MyClass.var вообще, если у вас есть экземпляр MyDerivedClass.

Как правило, я не знаю ни одного статически типизированного языка, который фактически имеет разрешение динамической переменной. Но если вам это действительно нужно, вы можете использовать методы getters или accessor (что в большинстве случаев должно быть сделано, чтобы избежать полей public):

class MyClass
{
    private String var = "base";

    public String getVar() // or simply 'var()'
    {
        return this.var;
    }
}

class MyDerivedClass extends MyClass {
    private String var = "derived";

    @Override
    public String getVar() {
        return this.var;
    }
}

Ответ 3

Полиморфное поведение Java-языка работает с методами, а не с переменными-членами: они создали язык для привязки переменных-членов во время компиляции.

Ответ 4

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

Таким образом, поля будут лучше разрешены в время компиляции:)