наследование и динамическое связывание

Может кто-нибудь объяснить мне, что здесь происходит и ПОЧЕМУ?

class Base{
    private float f = 1.0f;
    void setF(float f1){ this.f = f1; }
    float getF() {return f;}
    public void xx(){}
}

class Base2 extends Base{
    private float f = 2.0f;

    public void xx(){
        System.out.println(super.getF()+" "+this.getF());
    }

    //float getF() {return f;} //1
    //void setF(float f1){ this.f = f1; } //2

    public static void main(String[]args){
        Base b=new Base2();
        b.setF(3);
        b.xx();
        System.out.println(b.getF());
        System.out.println(((Base)b).getF());
    }
}

Вывод этого кода будет 3 3, 3, 3.

Если я раскомментирую только строку 1 с помощью геттера, вывод будет 3 2, 2, 2.

Если я раскомментирую только строку 2 с помощью setter, вывод будет 1 1, 1, 1.

Если я раскомментирую строки 1 и 2 (с сеттером и геттером), вывод будет 1 3, 3, 3.

        //default output: 3 3, 3, 3
        //with getter: 3 2, 2, 2
        //with setter: 1 1, 1, 1
        //with getter and setter: 1 3, 3, 3

Если переопределить метод в родительском классе кодом в подклассе, этот переопределенный метод не сможет получить доступ к закрытой переменной-члену, даже если переопределенный метод в родительском классе сделает это. Однако переопределяющий метод в подклассе может вызывать переопределенный метод в родительском.

Итак, это объясняет случай № 4 с геттером и сеттером, которые имеют доступ только к переменной-члену Base2.


person anastsiacrs    schedule 24.07.2015    source источник


Ответы (2)


Вы получаете 3 3 3 3, потому что методы set/get изменяют переменную Base.f:

Вы получаете 3 2 2 2, потому что устанавливаете значение изменения переменной Base.f, но метод get получает значение переменной Base2.f.

Вы получаете 1 1 1 1, потому что метод set изменяет значение переменной Base2.f, но метод get получает значение переменной Base.f.

Вы получаете 1 3 3 3, потому что super.getF() возвращает значение переменной Base.f, а другие методы получения возвращают значение переменной Base2.f. Также метод set изменяет значение переменной Base2.f.

person lukaslew    schedule 24.07.2015
comment
почему в случае № 1 (без геттера и сеттера в подклассе) setF (3) меняет как Base.f, так и Base2. ф? - person anastsiacrs; 24.07.2015
comment
@anastsiacrs setF(3) изменить только Base.f. Геттер и сеттер определены только в классе Base, поэтому при их выполнении они получают/устанавливают значение переменной Base.f. Base2.f не изменяется, поэтому в случае № 2 (где вы переопределяете метод получения) вы получаете 3 2 2 2 - person lukaslew; 24.07.2015
comment
case#1: если setF(3) изменяет только Base.f, то почему this.f (в методе xx()) возвращает 3? - person anastsiacrs; 24.07.2015
comment
Класс Base2 не имеет своей версии метода getF(), поэтому он использует определенный в классе Base метод с той же сигнатурой. в первом примере super.getF() и this.getF() совпадают. Если вы действительно хотите понять, что происходит, вы можете вставить этот код в какую-нибудь IDE и отладить код. Вы могли видеть значения переменных и когда какой метод выполняется. - person lukaslew; 24.07.2015
comment
Спасибо. Теперь понятно. Я не заметил, что использую this.getF() и super.getF(), а не this.f и super.f - person anastsiacrs; 24.07.2015

Давайте начнем с некоторой предыстории.

В учебнике по Наследование по Java говорится:

Частные члены в суперклассе

Подкласс не наследует частные члены своего родительского класса. Однако, если у суперкласса есть общедоступные или защищенные методы для доступа к его закрытым полям, они также могут использоваться подклассом.

В главе Полиморфизм говорится:

Виртуальная машина Java (JVM) вызывает соответствующий метод для объекта, на который ссылается каждая переменная. Он не вызывает метод, определенный типом переменной. Такое поведение называется вызовом виртуального метода и демонстрирует один из аспектов важных возможностей полиморфизма в языке Java.

Наконец, в главе Скрытие полей того же учебника заявил:

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

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

Теперь вернемся к самому вопросу.

В вашем первом примере вы не переопределяете ни метод доступа, ни мутатор - вы просто все время вызываете унаследованные. Они возвращают и изменяют значение f of Base. Почему унаследованный метод доступа/мутатор не возвращает/не изменяет значение f из Base2? Потому что даже если бы f of Base не был приватным, он не был бы переопределен, а просто скрыт.

Во втором примере вы переопределяете метод доступа. Здесь вы начинаете использовать полиморфизм. Этот краткий ответ может быть полезен для понимания. Когда вы вызываете b.setF(3), вы устанавливаете значение f of Base. Однако при вызове getF() вы получаете значение f по основанию 2, за исключением случая, когда вы вызываете его с ключевым словом super. Обратите внимание, что в вашем последнем вызове System.out.println(((Base)b).getF()) приведение к Base ничего не делает, так как b уже объявлен как База. Вы не можете вызвать переопределенный метод суперкласса без использования super (как вы уже знаете, переопределять можно только методы экземпляра).

В вашем третьем примере вы переопределяете мутатор. Ситуация противоположна вашему второму примеру. Когда вы вызываете b.setF(3), вы устанавливаете значение f по основанию 2. Но вы всегда получаете f of Base от геттера, потому что геттер не переопределяется. Таким образом, все 4 вызова getF() возвращают исходное значение f of Base.

В последнем примере вы переопределяете как аксессор, так и мутатор. Следовательно, они работают с f из Base2. Единственный вызов, возвращающий начальное значение f of Base, очевидно, super.getF().

Это определенно не идеальное объяснение, но я надеюсь, что оно поможет.

person Constantine    schedule 24.07.2015