Почему 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
затеняет одно в базовом классе.
Почему привязка переменных происходит во время компиляции, а не во время выполнения? Это только по соображениям производительности?
Ответы
Ответ 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 и получать к ним доступ с методами, которые динамически разрешены.
Таким образом, поля будут лучше разрешены в время компиляции:)