Переопределение статического метода в Java

В Java нельзя переопределять статические методы. Но почему работает следующий код:

public class Runner {
    public static void main(String[] args) {
        B.fun();
    }
}

class A {
    public static void fun() {
        System.out.println("A");
    }
}
class B extends A {
    public static void fun() {
        System.out.println("B");
    }
}

На печать выводится "B". Все выглядит так, как будто это переопределение. Потому что при перегрузке я должна была бы что-то изменить в сигнатуре метода. Но нет, я ничего не меняла.

Аннотацию "override" не могу поставить над методом.


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

Автор решения: Igor

... работает следующий код:

На печать выводится "B".

Потому что Вы вызываете fun так, что компилятору видно, что метод вызывается в контексте класса B.

Аннотацию "override" не могу поставить над методом.

Потому что статические методы - невиртуальные.

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

Начнем по порядку

Переопределение метода:

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

public class App {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.voice(); // Вывод: Гав
        cat.voice(); // Вывод: Мяу
    }
}

class Animal { // родительский класс
    public void voice() {
        System.out.print("Голос животного");
    }
}

class Dog extends Animal { // класс-наследник

    @Override // Переопределение метода voice() унаследованного от класса Animal в классе-наследнике Dog
    public void voice() {
        System.out.print("Гав");
    }
}

class Cat extends Animal { // класс-наследник

    @Override // Переопределение метода voice() унаследованного от класса Animal в классе-наследнике Cat
    public void voice() {
        System.out.print("Мяу");
    }

}

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

public class App {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.voice("Шарик", 10); // Вывод: Гав
        cat.voice("не Шарик", 7); // Вывод: Мяу
    }
}

class Animal {
    // Родительский метод имеет возвращаемый тип String. Принимает два параметра, типа String и int
    public String voice(String animal_name, int animal_age) {
        System.out.print("Гав");

        return animal_name;
    }
}

class Dog extends Animal{
    // Переопределенный метод должен как его родитель, иметь возвращаемый тип String, и принимать параметры как его родитель 
    @Override
    public String voice(String dog_name, int dog_age) {
        System.out.print("Гав");

        return dog_name;
    }
}

class Cat extends Animal {
    @Override
    public String voice(String cat_name, int cat_age) {
        System.out.print("Мяу");        
        return cat_name;
    }
}

Сокрытие метода:

Сокрытие методов, грубо говоря, представляет из себя "перекрытие" методом текущего класса, метода родительского класса. Выглядит это следующим образом:

public class App {
    public static void main(String[] args) {
        Dog.voice(); // Вывод: Гав
    }
}

class Animal {
    public static void voice() {
        System.out.print("звук");
    }
}
class Dog extends Animal {
    // Метод определенный в классе-наследнике идентичный по сигнатуре с методом родительского класса
    public static void voice() {
        System.out.print("Гав");
    }
}

Сигнатуры методов этих двух классов идентичны, т.е. в классе родителе Animal есть public static void voice() и в классе-наследнике Dog есть public static void voice(). Следовательно вызов Dog.voice() будет вызывать метод определенный в классе Dog

Важно отметить, что перекрытие требует таких же правил как и переопределение метода.

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

т.е.

public class App {
    public static void main(String[] args) {
        Dog.voice(); // Вывод: Гав
        Dog.voice("Шарик"); // Вывод: Шарик говорит гав
    }
}

class Animal {
    public static void voice() {
        System.out.print("звук");
    }
}

class Dog extends Animal {

    public static void voice() { // Метод перекрывающий унаследованный метод
        System.out.print("Гав");
    }

    public static void voice(String name) { // Отдельный метод класса Dog
        System.out.print(name + " говорит гав");
    }

}

Рассмотрим использование таких методов в своих классах

  1. Использование без схожего по сигнатуре метода
public class App {
    public static void main(String[] args) {
        Dog.voice(); // Вывод: звук
    }
}

class Animal {
    public static void voice() {
        System.out.print("звук");
    }
}

class Dog extends Animal {  
    public static void dog_voice() {
        voice();
    }
}

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

  1. Использование схожего по сигнатуре метода
public class App {
    public static void main(String[] args) {
        Dog.dog_voice(); // Вывод: гав
    }
}

class Animal {
    public static void voice() {
        System.out.print("звук");
    }
}

class Dog extends Animal {

    public static void voice() {
        System.out.print("Гав");
    }
    public static void dog_voice() {
        voice();
    }
}

Поскольку теперь у класса Dog появился "свой метод" voice() он перекрывает метод унаследованный от Animal

  1. Использование метода унаследованного от родителя, когда есть метод перекрывающий его
public class App {
    public static void main(String[] args) {
        Dog.dog_voice(); // Вывод: Гавзвук
    }
}

class Animal {
    public static void voice() {
        System.out.print("звук");
    }
}

class Dog extends Animal {

    public static void voice() { // Метод перекрывающий унаследованный метод
        System.out.print("Гав");
    }

    public static void dog_voice() {
        voice(); // Вызов метода voice определенного в классе Dog;
        Animal.voice(); // Вызов метода voice определенного в классе Animal 
    }
}

Хочу дополнить тем, что перекрытие работает не только с методами класса, но и с его полями. Небольшой пример:

public class App {
    public static void main(String[] args) {
        Dog.animal(); // Вывод: Имя этого животного Собакен
    }
}
class Animal {
    public static String animal_name = "Нет имени, просто животное";

    public static void animal() {
        System.out.print("Имя этого животного " + animal_name);
    }
}
class Dog extends Animal {
    public static String animal_name = "Собакен"; // Перекрывает поле родительского класса Animal

    public static void animal() {
        System.out.print("Имя этого животного " + animal_name);
    }
}
→ Ссылка
Автор решения: Marika

ПЕРЕОПРЕДЕЛЕНИЕ метода - это когда ты создала обьект b от подкласса B, и вызвала от объекта метод, а метод запустился не от суперкласса, а от подкласса которому принадлежит объект b. Пример:

B b = new B();
b.fun();

Что же касается твоего кода, то ты НЕ создавала обьект, а поэтому ПЕРЕОПРЕДЕЛЕНИЕ объекта не имело место быть. В твоем коде есть просто прямое обращение к методу с указанием что он лежит в классе В:

B.fun();

Забавно, что переопределение статического метода ВОЗМОЖНО. НО нужно помнить что статический метод именно для того и создавали что бы вызывать его БЕЗ создания/указания объекта. Поэтому программисты договорились НЕ писать:

b.fun();

Программисты договорились вызывать статические методы именно как у тебя в коде и написанно (БЕЗ создания обьекта):

B.fun();
→ Ссылка