Статические методы в родительском классе и наследнике

Есть код:

public class Coffee {
    String name="Кофе";

    public static void buyCoffee(Coffee coffee) {
        System.out.println("Вы купили " + coffee.name);
    }
}

public class Latte extends Coffee{
    String name = "Латте";

    public static void buyCoffee(Coffee coffee) {
        System.out.println("Вы купили " + coffee.name);
    }
}

import static filosofia.java.p212.inheritance.static_method.Latte.buyCoffee;

public class Main {
    public static void main(String[] args) {
      
        Latte latte = new Latte();
        buyCoffee(latte);
    }
} 

Который выводит следующий результат

Вы купили Кофе

Я знаю, что статические методы не поддерживают полиморфное поведение, поэтому вывод Вы купили Кофе для объекта Coffee coffee = new Latte() было бы ожидаемо. Но почему на объект Latte latte = new Latte() java реагирует как на объект типа Coffee не понятно. Т.е. при отсутствии созданного экземпляра класса Coffee программа все равно обратится к его полю. Как такое может быть?


Ответы (2 шт):

Автор решения: Alex Krass

Определение перекрывающихся переменных происходит на основе типа, который имеет переменная, а не реальный тип объекта. Это отличается от методов, которые наоборот определяются типом реального объекта, а не типом переменной.

Поскольку у вас стоит изменение типа в статическом методе с Latte на Coffee, то будет произведено обращение к родительскому классу.

public class Parent {
    String name = "Parent";
}

public class Child extends Parent {
    String name = "Child";
}

Parent parent = new Parent();
Child child = new Child();
Parent childWithTypeParent = new Child();

System.out.println(parent.name); // Parent
System.out.println(child.name); // Child
System.out.println(childWithTypeParent.name); // Parent

Следовательно, поменяв код на

public class Latte extends Coffee{
    String name = "Латте";

    public static void buyCoffee(Latte latte) {
        System.out.println("Вы купили " + latte.name);
    }
}

Вы получите вывод "Вы купили Латте".

→ Ссылка
Автор решения: Nowhere Man

Поля классов нельзя "переопределить" (override), только методы экземпляра.

Поскольку и в классе родителя Coffee, и в классе-потомке Latte определены поля с одинаковым именем name, при прямом обращении к такому полю (без виртуального геттера) будет возвращаться значение, соответствующее типу ссылки на данный экземпляр. То есть, переменная name будет браться из того класса, тип которого указан в параметре метода, а не из типа своей ссылки.

Т.е. при отсутствии созданного экземпляра класса Coffee программа все равно обратится к его полю

При вызове конструктора new Latte() будет вызван конструктор родительского класса по умолчанию new Coffee(), который "создаст" поле родителя, к которому будет доступ через тип ссылки:

new Latte() 
    -> new Coffee() 
       {name = "Coffee"}
{name = "Latte"}

Для вывода названия Latte потребуется "перегрузить" (overload) метод, чтобы он принимал аргумент типа Latte:

class Coffee {
    String name = "Кофе";
    
    public static void buyCoffee(Coffee coffee) {
        System.out.println("Coffee: Вы купили " + coffee.name);
    }
}

class Latte extends Coffee {
    String name = "Латте"; // скрывает поле в классе-родителе

    public static void buyCoffee(Coffee coffee) {
        System.out.println("Latte: coffee: Вы купили " + coffee.name);
    }
    
    public static void buyCoffee(Latte latte) {
        System.out.println("Latte: latte: Вы купили " + latte.name);
    }
}

Тест:

Coffee coffee = new Coffee();
Latte.buyCoffee(coffee);       // buyCoffee(Coffee)

Coffee coffeeLatte = new Latte();
Latte.buyCoffee(coffeeLatte);  // buyCoffee(Coffee)

Latte latte = new Latte();
Latte.buyCoffee(latte);        // buyCoffee(Latte)

Результат

Latte: coffee: Вы купили Кофе
Latte: coffee: Вы купили Кофе
Latte: latte: Вы купили Латте

→ Ссылка