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


534

Почему невозможно переопределить статические методы?

Если возможно, пожалуйста, используйте пример.


3
Большинство языков ООП не позволяют этого.
jmucchiello

7
@jmucchiello: см. мой ответ. Я думал так же, как и вы, но затем узнал о методах Ruby / Smalltalk 'class', и поэтому есть другие настоящие языки ООП, которые делают это.
Кевин Брок

5
@jmucchiello большинство языков ООП не являются реальными языками ООП (я думаю о Smalltalk)
mathk


1
возможно, потому что Java разрешает вызовы статических методов во время компиляции. Таким образом, даже если вы написали, Parent p = new Child()и p.childOverriddenStaticMethod()компилятор разрешит его Parent.childOverriddenStaticMethod(), взглянув на ссылочный тип.
Манодж

Ответы:


494

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

На разработку Java повлияли два фактора. Одна из них была связана с производительностью: было много критики по поводу того, что Smalltalk слишком медленный (сборщик мусора и полиморфные вызовы являются частью этого), и создатели Java были полны решимости этого избежать. Другим было решение, что целевой аудиторией для Java были разработчики C ++. Заставить статические методы работать так, как они это делали, было полезно для программистов на C ++, а также очень быстро, потому что не нужно ждать времени выполнения, чтобы выяснить, какой метод вызывать.


18
... но только "правильно" в Java. Например, эквивалент Scala «статических классов» (которые называются objects) допускают перегрузку методов.

32
Objective-C также позволяет переопределять методы класса.
Ричард

11
Существует иерархия типов времени компиляции и иерархия типов времени выполнения. Имеет смысл задать вопрос, почему вызов статического метода не использует иерархию типов времени выполнения в тех случаях, когда она существует. В Java это происходит при вызове статического метода из object ( obj.staticMethod()), который разрешен и использует типы времени компиляции. Когда статический вызов выполняется в нестатическом методе класса, «текущий» объект может быть производным типом класса, но статические методы, определенные для производных типов, не учитываются (они относятся к типу времени выполнения). иерархии).
Стив Пауэлл

18
Я должен был бы сделать это ясно: это не правда , что понятие не применимо .
Стив Пауэлл

13
Этот ответ, хотя и правильный, больше похож на «как это», а не на то, как он должен быть, или, точнее, на то, каким он должен быть, чтобы оправдать ожидания ФП и, следовательно, здесь, меня и других. Нет никакой конкретной причины запрещать переопределение статических методов, кроме как «это так». Я думаю, что это недостаток лично.
RichieHH

186

Лично я считаю, что это недостаток в дизайне Java. Да, да, я понимаю, что нестатические методы присоединяются к экземпляру, а статические методы - к классу и т. Д. И т. Д. Тем не менее рассмотрим следующий код:

public class RegularEmployee {
    private BigDecimal salary;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }

    public BigDecimal calculateBonus() {
        return salary.multiply(getBonusMultiplier());
    }

    /* ... presumably lots of other code ... */
}

public class SpecialEmployee extends RegularEmployee {
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

Этот код не будет работать так, как вы могли бы ожидать. А именно, SpecialEmployee получают 2% бонус, как и обычные сотрудники. Но если вы удалите «статичные», SpecialEmployee получит бонус 3%.

(Следует признать, что этот пример является плохим стилем кодирования, поскольку в реальной жизни вы, вероятно, захотите, чтобы бонусный множитель находился где-то в базе данных, а не был жестко закодирован. Но это только потому, что я не хотел увязывать пример с большим количеством кода не имеет отношения к делу.)

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

Но это не работает.

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

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

Или, возможно, есть какая-то веская причина, почему Java ведет себя так. Кто-нибудь может указать на преимущество в этом поведении, некоторую категорию проблем, которая облегчается этим? Я имею в виду, не просто указывайте мне на спецификацию языка Java и говорите: «Посмотрите, это задокументировано, как оно себя ведет». Я знаю это. Но есть ли веская причина, почему он ДОЛЖЕН вести себя так? (Помимо очевидного «заставить его работать правильно, было слишком сложно» ...)

Обновить

@VicKirk: Если вы имеете в виду, что это «плохой дизайн», потому что он не соответствует тому, как Java обрабатывает статику, я отвечаю: «Ну, конечно, конечно». Как я уже говорил в моем оригинальном сообщении, это не работает. Но если вы имеете в виду, что это плохой дизайн в том смысле, что с языком, в котором это работает, было бы что-то принципиально неправильное, то есть где статика могла бы быть переопределена, как виртуальные функции, что это каким-то образом внесло бы двусмысленность, или было бы невозможно эффективно или что-то подобное, я отвечаю: «Почему? Что не так с концепцией?»

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


11
@Bemrose: Но это моя точка зрения: почему мне нельзя позволить это сделать? Возможно, мое интуитивное представление о том, что должен делать «статический», отличается от вашего, но в основном я думаю о статическом как о методе, который МОЖЕТ быть статическим, потому что он не использует никаких данных экземпляра, и который ДОЛЖЕН быть статическим, потому что вы можете захотеть называть это независимо от экземпляра. Статика явно привязана к классу: я ожидаю, что Integer.valueOf будет связан с Integers, а Double.valueOf будет связан с Doubles.
Джей

9
@ewernli & Bemrose: Да, это так. Я не обсуждаю это. Поскольку код в моем примере не работает, конечно, я бы не стал его писать. Мой вопрос, почему это так. (Боюсь, что это превращается в один из тех разговоров, когда мы просто не общаемся. «Извините, господин продавец, могу я получить один из них красным цветом?» «Нет, это стоит 5 долларов». «Да, я знаю, что это стоит 5 долларов, но можно ли получить красный? »« Сэр, я только что сказал вам, что это стоит 5 долларов. »« Хорошо, я знаю цену, но я спрашивал о цвете. »« Я уже сказал вам цену! » и т. д.)
Джей

6
Я думаю, что в конечном итоге этот код сбивает с толку. Рассмотрим, передается ли экземпляр в качестве параметра. Затем вы говорите, что экземпляр времени выполнения должен определять, какой статический метод вызывается. Это в основном делает всю отдельную иерархию параллельной существующей инстанции. Что теперь, если подкласс определяет ту же сигнатуру метода, что и нестатическая? Я думаю, что правила усложнят ситуацию. Именно таких языковых сложностей Java пытается избежать.
Ишай

6
@Yishai: RE «Экземпляр времени выполнения определяет, какой статический метод вызывается»: Точно. Я не понимаю, почему вы не можете ничего сделать со статическим, что вы можете сделать с виртуальным. «Отдельная иерархия»: я бы сказал, сделайте это частью той же иерархии. Почему статики не включены в одну и ту же иерархию? «Подкласс определяет одну и ту же сигнатуру нестатически»: я предполагаю, что это будет недопустимо, точно так же, как недопустимо, скажем, иметь подкласс, переопределяющий функцию с идентичной сигнатурой, но с другим типом возврата, или не генерировать все исключения, которые родитель бросает, или иметь более узкую область видимости.
Джей

28
Я думаю, у Джея есть смысл - меня тоже удивило, когда я обнаружил, что статика не может быть переопределена. Частично потому, что если у меня есть A с методом, someStatic()а B расширяет A, то B.someMethod() связывается с методом в A. Если я впоследствии добавлю someStatic()к B, вызывающий код все еще будет вызываться, A.someStatic()пока я не перекомпилирую вызывающий код. Также меня удивило, что bInstance.someStatic()используется объявленный тип bInstance, а не тип времени выполнения, потому что он связывается при компиляции, а не по ссылке, поэтому A bInstance; ... bInstance.someStatic()вызывает A.someStatic (), если существует B.someStatic ().
Лоуренс Дол

42

Короткий ответ: это вполне возможно, но Java этого не делает.

Вот некоторый код, который иллюстрирует текущее состояние дел в Java:

Файл Base.java:

package sp.trial;
public class Base {
  static void printValue() {
    System.out.println("  Called static Base method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Base method.");
  }
  void nonLocalIndirectStatMethod() {
    System.out.println("  Non-static calls overridden(?) static:");
    System.out.print("  ");
    this.printValue();
  }
}

Файл Child.java:

package sp.trial;
public class Child extends Base {
  static void printValue() {
    System.out.println("  Called static Child method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Child method.");
  }
  void localIndirectStatMethod() {
    System.out.println("  Non-static calls own static:");
    System.out.print("  ");
    printValue();
  }
  public static void main(String[] args) {
    System.out.println("Object: static type Base; runtime type Child:");
    Base base = new Child();
    base.printValue();
    base.nonStatPrintValue();
    System.out.println("Object: static type Child; runtime type Child:");
    Child child = new Child();
    child.printValue();
    child.nonStatPrintValue();
    System.out.println("Class: Child static call:");
    Child.printValue();
    System.out.println("Class: Base static call:");
    Base.printValue();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Child:");
    child.localIndirectStatMethod();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Base:");
    child.nonLocalIndirectStatMethod();
  }
}

Если вы запустите это (я сделал это на Mac, из Eclipse, используя Java 1.6), вы получите:

Object: static type Base; runtime type Child.
  Called static Base method.
  Called non-static Child method.
Object: static type Child; runtime type Child.
  Called static Child method.
  Called non-static Child method.
Class: Child static call.
  Called static Child method.
Class: Base static call.
  Called static Base method.
Object: static/runtime type Child -- call static from non-static method of Child.
  Non-static calls own static.
    Called static Child method.
Object: static/runtime type Child -- call static from non-static method of Base.
  Non-static calls overridden(?) static.
    Called static Base method.

Здесь единственные случаи, которые могут быть неожиданностью (и о чем идет речь), являются первым случаем:

«Тип времени выполнения не используется для определения того, какие статические методы вызываются, даже когда они вызываются с помощью экземпляра объекта ( obj.staticMethod())».

и последний случай:

«При вызове статического метода из метода объекта класса выбран статический метод, доступный из самого класса, а не из класса, определяющего тип времени выполнения объекта».

Вызов с экземпляром объекта

Статический вызов разрешается во время компиляции, тогда как вызов нестатического метода разрешается во время выполнения. Обратите внимание, что хотя статические методы наследуются (от родителя), они не переопределяются (дочерними). Это может быть сюрпризом, если вы ожидаете иначе.

Вызов из метода объекта

Вызовы методов объекта разрешаются с использованием типа времени выполнения, но вызовы статических ( классовых ) методов разрешаются с использованием типа времени объявления (объявленного).

Изменение правил

Чтобы изменить эти правила, чтобы при последнем вызове в вызываемом примере Child.printValue()статические вызовы должны были быть предоставлены с типом во время выполнения, а не компилятором, разрешающим вызов во время компиляции с объявленным классом объекта (или контекст). Статические вызовы могут затем использовать (динамическую) иерархию типов для разрешения вызова, так же, как вызовы объектных методов делают сегодня.

Это легко выполнимо (если мы изменили Java: -O), и это вовсе не неразумно, однако, у него есть некоторые интересные соображения.

Основное соображение заключается в том, что нам нужно решить, какие статические вызовы методов должны делать это.

На данный момент в Java есть такая «причуда» в языке, при которой obj.staticMethod()вызовы заменяются ObjectClass.staticMethod()вызовами (обычно с предупреждением). [ Примечание: ObjectClass это тип времени компиляции obj.] Это были бы хорошие кандидаты для переопределения таким способом, принимая тип времени выполнения obj.

Если бы мы сделали это, это бы затруднило чтение тел методов: статические вызовы в родительском классе потенциально могли бы быть динамически «перенаправлены». Чтобы избежать этого, нам пришлось бы вызывать статический метод с именем класса - и это делает вызовы более очевидно разрешенными с помощью иерархии типов времени компиляции (как сейчас).

Другие способы вызова статического метода более хитры: они this.staticMethod()должны означать то же самое obj.staticMethod(), что и тип во время выполнения this. Однако это может вызвать некоторые проблемы с существующими программами, которые вызывают (очевидно локальные) статические методы без декорации (что, возможно, эквивалентно this.method()).

Так что насчет неукрашенных звонков staticMethod()? Я предлагаю сделать то же самое, что и сегодня, и использовать контекст локального класса, чтобы решить, что делать. В противном случае возникнет великое замешательство. Конечно, это означает, что method()это означало this.method()бы, если бы methodбыл нестатический метод, и ThisClass.method()если бы methodбыл статический метод. Это еще один источник путаницы.

Другие соображения

Если мы изменили это поведение (и сделали статические вызовы потенциально динамически нелокальным), мы, вероятно , захотите пересмотреть смысл final, privateа protectedтакже отборочные на staticметодах класса. Тогда нам всем придется привыкнуть к тому факту, что private staticи public finalметоды не переопределяются и поэтому могут быть безопасно разрешены во время компиляции и «безопасны» для чтения как локальные ссылки.


«Если бы мы сделали это, это бы затруднило чтение тел методов: статические вызовы в родительском классе потенциально могли бы динамически« перенаправляться »». Правда, но это именно то, что происходит с обычными вызовами нестатических функций. Это обычно рекламируется как положительная особенность для обычных виртуальных функций, а не проблема.
Джей

25

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

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;

class RegularEmployee {

    private BigDecimal salary = BigDecimal.ONE;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }
    public BigDecimal calculateBonus() {
        return salary.multiply(this.getBonusMultiplier());
    }
    public BigDecimal calculateOverridenBonus() {
        try {
            // System.out.println(this.getClass().getDeclaredMethod(
            // "getBonusMultiplier").toString());
            try {
                return salary.multiply((BigDecimal) this.getClass()
                    .getDeclaredMethod("getBonusMultiplier").invoke(this));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    // ... presumably lots of other code ...
}

final class SpecialEmployee extends RegularEmployee {

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

public class StaticTestCoolMain {

    static public void main(String[] args) {
        RegularEmployee Alan = new RegularEmployee();
        System.out.println(Alan.calculateBonus());
        System.out.println(Alan.calculateOverridenBonus());
        SpecialEmployee Bob = new SpecialEmployee();
        System.out.println(Bob.calculateBonus());
        System.out.println(Bob.calculateOverridenBonus());
    }
}

Результирующий вывод:

0.02
0.02
0.02
0.03

что мы пытались достичь :)

Даже если мы объявим третью переменную Carl как RegularEmployee и назначим ей экземпляр SpecialEmployee, у нас все равно останется вызов метода RegularEmployee в первом случае и вызов метода SpecialEmployee во втором случае

RegularEmployee Carl = new SpecialEmployee();

System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());

просто посмотрите на консоль вывода:

0.02
0.03

;)


9
Да, отражение - это почти единственное, что можно сделать - но вопрос не совсем в этом - хотя полезно иметь его здесь
Mr_and_Mrs_D

1
Этот ответ - самый большой взлом, который я когда-либо видел во всех темах Java. Было весело читать это все еще :)
Андрейс

19

Статические методы обрабатываются JVM как глобальные, они вообще не привязаны к экземпляру объекта.

Концептуально это было бы возможно, если бы вы могли вызывать статические методы из объектов класса (как в языках, таких как Smalltalk), но это не так в Java.

РЕДАКТИРОВАТЬ

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

class MyClass { ... }
class MySubClass extends MyClass { ... }

MyClass obj1 = new MyClass();
MySubClass obj2 = new MySubClass();

ob2 instanceof MyClass --> true

Class clazz1 = obj1.getClass();
Class clazz2 = obj2.getClass();

clazz2 instanceof clazz1 --> false

Вы можете размышлять над классами, но это останавливается там. Вы не вызываете статический метод, используя clazz1.staticMethod(), но используя MyClass.staticMethod(). Статический метод не привязан к объекту, и поэтому в статическом методе нет ни понятия, thisни superстатического метода. Статический метод - это глобальная функция; как следствие, также отсутствует понятие полиморфизма, и поэтому переопределение метода не имеет смысла.

Но это могло бы быть возможно, если бы во время выполнения MyClassбыл объект, для которого вы вызываете метод, как в Smalltalk (или, может быть, JRuby, как предполагает один комментарий, но я ничего не знаю о JRuby).

Ах да ... еще одна вещь. Вы можете вызывать статический метод через объект, obj1.staticMethod()но это действительно синтаксический сахар для MyClass.staticMethod()и его следует избегать. Это обычно вызывает предупреждение в современной IDE. Я не знаю, почему они позволили этот ярлык.


5
Даже многие современные языки, такие как Ruby, имеют методы классов и позволяют их переопределять.
Чандра Секар

3
Классы существуют как объекты в Java. Смотрите класс "Класс". Я могу сказать myObject.getClass (), и он вернет мне экземпляр соответствующего объекта класса.
Джей

5
Вы получаете только «описание» класса, а не сам класс. Но разница невелика.
ewernli

У вас все еще есть класс, но он скрыт в ВМ (рядом с загрузчиком классов), пользователь почти не имеет к нему доступа.
mathk

Для clazz2 instanceof clazz1правильного использования вы можете вместо этого использовать class2.isAssignableFrom(clazz1), что, я считаю, вернет true в вашем примере.
Саймон Форсберг,

14

Переопределение метода стало возможным благодаря динамической диспетчеризации , что означает, что объявленный тип объекта не определяет его поведение, а скорее тип времени выполнения:

Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"

Хотя оба lassieи kermitобъявлены как объекты типа Animal, их поведение (метод .speak()) варьируется, поскольку динамическая диспетчеризация будет связывать вызов метода .speak()только с реализацией во время выполнения, а не во время компиляции.

Теперь вот где staticключевое слово начинает иметь смысл: слово «статический» является антонимом слова «динамический». Поэтому причина, по которой вы не можете переопределить статические методы, заключается в том, что нет динамической диспетчеризации статических элементов - потому что статический буквально означает «не динамический». Если они отправляются динамически (и, следовательно, могут быть переопределены), staticключевое слово больше не будет иметь смысла.


11

Да. Практически Java допускает переопределение статического метода, и теоретически Нет, если вы переопределяете статический метод в Java, тогда он будет скомпилирован и работать гладко, но потеряет полиморфизм, который является базовым свойством Java. Вы будете читать везде, что вы не можете попробовать самостоятельно скомпилировать и запустить. вы получите ответ. Например, если у вас есть Class Animal и статический метод eat (), и вы переопределяете этот статический метод в его подклассе, то вы называете его Dog. Тогда, когда бы вы ни назначали объект Dog для ссылки на животных и вызывали eat () в соответствии с Java, должна быть вызвана функция eat () собаки, но в статических переопределенных животных будет вызываться функция eat ().

class Animal {
    public static void eat() {
        System.out.println("Animal Eating");
    }
}

class Dog extends Animal{
    public static void eat() {
        System.out.println("Dog Eating");
    }
}

class Test {
    public static void main(String args[]) {
       Animal obj= new Dog();//Dog object in animal
       obj.eat(); //should call dog's eat but it didn't
    }
}


Output Animal Eating

Согласно принципу полиморфизма Java, вывод должен быть Dog Eating.
Но результат был другим, потому что для поддержки полиморфизма Java использует Late Binding, что означает, что методы вызываются только во время выполнения, но не в случае статических методов. В статических методах компилятор вызывает методы во время компиляции, а не во время выполнения, поэтому мы получаем методы в соответствии со ссылкой, а не со ссылкой на объект, содержащий ссылку, поэтому вы можете сказать, что практически он поддерживает статическое переопределение, но теоретически это не так. «т.


3
Это плохая практика вызывать статические методы из объекта.
Дмитрий Загорулькин

6

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


1
Разве вы не читали какой-либо другой ответ, уже охватывающий это и разъясняющий, что это не достаточные причины, чтобы сбрасывать со счетов основополагающую статику на концептуальном уровне. Мы знаем, что это не работает. Совершенно «чисто желать переопределения статических методов, и это действительно возможно во многих других языках»
RichieHH

Ричард, давайте на минутку предположим, что когда я ответил на этот вопрос 4 года назад, большинство из этих ответов не было опубликовано, так что не будь ослом! Нет необходимости утверждать, что я не читал внимательно. Кроме того, разве вы не читали, что мы обсуждаем только переопределение по отношению к Java. Кому интересно, что возможно на других языках. Это не имеет значения. Иди тролль в другое место. Ваш комментарий не добавляет ничего полезного этой теме.
Афины Холлоуэй

6

В Java (и во многих языках ООП, но я не могу говорить за всех; а некоторые вообще не имеют статики) все методы имеют фиксированную сигнатуру - параметры и типы. В виртуальном методе подразумевается первый параметр: ссылка на сам объект и при вызове изнутри объекта компилятор автоматически добавляет this.

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

Из-за этой разницы между ними; виртуальные методы всегда имеют ссылку на объект контекста (то есть this), поэтому в кучи можно ссылаться на все, что принадлежит этому экземпляру объекта. Но со статическими методами, поскольку не передается ссылка, этот метод не может получить доступ к переменным и методам объекта, так как контекст неизвестен.

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

Как кто-то спросил в комментарии к оперу - какова ваша причина и цель, чтобы хотеть эту функцию?

Я не знаю много Ruby, так как это было упомянуто OP, я провел некоторые исследования. Я вижу, что в классах Ruby действительно особый вид объектов, и можно создавать (даже динамически) новые методы. Классы являются полными объектами классов в Ruby, их нет в Java. Это то, что вы должны будете принять при работе с Java (или C #). Это не динамические языки, хотя C # добавляет некоторые формы динамических. На самом деле, в Ruby, насколько я мог найти, нет «статических» методов - в этом случае это методы объекта класса singleton. Затем вы можете переопределить этот синглтон новым классом, и методы в предыдущем объекте класса будут вызывать методы, определенные в новом классе (правильно?). Таким образом, если вы вызываете метод в контексте исходного класса, он все равно будет выполнять только исходную статику, но вызов метода в производном классе вызовет методы из родительского или подкласса. Интересно, и я вижу некоторую ценность в этом. Требуется другой образ мыслей.

Так как вы работаете в Java, вам нужно будет приспособиться к такому образу действий. Почему они это сделали? Ну, возможно, чтобы улучшить производительность в то время на основе технологии и понимания, которые были доступны. Компьютерные языки постоянно развиваются. Вернитесь достаточно далеко, и ООП не будет. В будущем появятся другие новые идеи.

РЕДАКТИРОВАТЬ : еще один комментарий. Теперь, когда я вижу различия и, будучи самим разработчиком Java / C #, я могу понять, почему ответы, которые вы получаете от разработчиков Java, могут сбивать с толку, если вы работаете с таким языком, как Ruby. staticМетоды Java не совпадают с classметодами Ruby . Разработчикам Java будет сложно понять это, так же как и тем, кто работает в основном с таким языком, как Ruby / Smalltalk. Я могу видеть, как это могло бы сильно сбить с толку тем фактом, что Java также использует «метод класса» как еще один способ говорить о статических методах, но этот же термин по-разному используется в Ruby. В Java нет методов класса Ruby (извините); Ruby не имеет статических методов в стиле Java, которые на самом деле являются просто старыми функциями процедурного стиля, как в C.

Кстати - спасибо за вопрос! Сегодня я узнал что-то новое о методах класса (стиль Ruby).


1
Конечно, нет причин, по которым Java не могла передать объект «Класс» в качестве скрытого параметра статическим методам. Это просто не было предназначено для этого.
jmucchiello

6

Ну ... ответ НЕТ, если вы думаете с точки зрения того, как переопределенный метод должен вести себя в Java. Но вы не получите никакой ошибки компилятора, если попытаетесь переопределить статический метод. Это означает, что если вы попытаетесь переопределить, Java не остановит вас; но вы, конечно, не получите тот же эффект, что и для нестатических методов. Переопределение в Java просто означает, что конкретный метод будет вызываться на основе типа времени выполнения объекта, а не типа его времени компиляции (что имеет место с переопределенными статическими методами). Хорошо ... есть предположения, почему они ведут себя странно? Поскольку они являются методами класса и, следовательно, доступ к ним всегда разрешается во время компиляции только с использованием информации о типе времени компиляции.

Пример : давайте попробуем посмотреть, что произойдет, если мы попробуем переопределить статический метод:

class SuperClass {
// ......
public static void staticMethod() {
    System.out.println("SuperClass: inside staticMethod");
}
// ......
}

public class SubClass extends SuperClass {
// ......
// overriding the static method
public static void staticMethod() {
    System.out.println("SubClass: inside staticMethod");
}

// ......
public static void main(String[] args) {
    // ......
    SuperClass superClassWithSuperCons = new SuperClass();
    SuperClass superClassWithSubCons = new SubClass();
    SubClass subClassWithSubCons = new SubClass();

    superClassWithSuperCons.staticMethod();
    superClassWithSubCons.staticMethod();
    subClassWithSubCons.staticMethod();
    // ...
}
}

Выход : -
SuperClass: inside staticMethod
SuperClass: inside staticMethod
SubClass: inside staticMethod

Обратите внимание на вторую строку вывода. Если бы staticMethod был переопределен, эта строка должна была бы быть идентичной третьей строке, поскольку мы вызываем staticMethod () для объекта Runtime Type как «SubClass», а не как «SuperClass». Это подтверждает, что статические методы всегда разрешаются с использованием только их информации о типе времени компиляции.


5

В общем случае не имеет смысла разрешать «переопределение» статических методов, поскольку не было бы хорошего способа определить, какой из них вызывать во время выполнения. Если взять пример Employee, если мы вызовем RegularEmployee.getBonusMultiplier () - какой метод должен быть выполнен?

В случае Java можно представить определение языка, в котором можно «переопределить» статические методы, если они вызываются через экземпляр объекта. Тем не менее, все, что нужно сделать, это заново реализовать обычные методы класса, добавив избыточность в язык, не добавляя никакой выгоды.


2
Интуитивно я думаю, что это должно работать как виртуальная функция. Если B расширяет A и оба A и B имеют виртуальные функции с именем doStuff, компилятор знает, что экземпляры A должны использовать A.doStuff, а экземпляры B должны использовать B.doStuff. Почему он не может сделать то же самое со статическими функциями? В конце концов, компилятор знает, к какому классу относится каждый объект.
Джей

Хм ... Джей, статический метод не нужно (как правило, не вызывать) в экземпляре ...
meriton

2
@meriton, но тогда это еще проще, нет? Если статический метод вызывается с использованием имени класса, вы должны использовать метод, подходящий для класса.
CPerkins

Но то, что главное делает для тебя. Если вы вызываете A.doStuff (), должен ли он использовать версию, которая была переопределена в «B extends A», или версию, которая была переопределена в «C extends A». И если у вас есть C или B, то вы все равно вызываете эти версии ... переопределение не требуется.
PSpeed

@meriton: Это правда, что статический метод обычно не вызывается с экземпляром, но я предполагаю, что это потому, что такой вызов не делает ничего полезного, учитывая текущий дизайн Java! Я предполагаю, что альтернативный дизайн мог бы быть лучшей идеей. Кстати, в очень реальном смысле статические функции вызываются с экземпляром довольно регулярно: когда вы вызываете статическую функцию из виртуальной функции. Затем вы неявно получаете this.function (), то есть текущий экземпляр.
Джей

5

Переопределением мы можем создать полиморфную природу в зависимости от типа объекта. Статический метод не имеет отношения к объекту. Так что Java не может поддерживать переопределение статического метода.


5

Мне нравится и двойной комментарий Джея ( https://stackoverflow.com/a/2223803/1517187 ).
Я согласен, что это плохой дизайн Java.
Многие другие языки поддерживают переопределение статических методов, как мы видели в предыдущих комментариях. Я чувствую, что Джей также пришел на Яву из Delphi, как и я.
Delphi (Object Pascal) был первым языком, реализующим ООП.
Очевидно, что многие люди имели опыт работы с этим языком, поскольку в прошлом он был единственным языком для написания коммерческих продуктов с графическим интерфейсом. И - да, мы могли бы в Delphi переопределить статические методы. На самом деле статические методы в Delphi называются «методами класса», в то время как Delphi придерживалась другого понятия «статические методы Delphi», которые были методами с ранним связыванием. Чтобы переопределить методы, которые вам пришлось использовать с поздним связыванием, объявите «виртуальную» директиву. Так что это было очень удобно и интуитивно понятно, и я ожидал этого на Java.


3

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

MyClass.static1()
MySubClass.static1()   // If you overrode, you have to call it through MySubClass anyway.

РЕДАКТИРОВАТЬ: кажется, что из-за неудачного надзора в языковой дизайн, вы можете вызывать статические методы через экземпляр. Обычно никто не делает это. Виноват.


8
«Вы не можете вызывать статические методы через экземпляр» На самом деле, одна из странностей Java заключается в том, что вы МОЖЕТЕ вызывать статические методы через экземпляр, даже если это чрезвычайно плохая идея.
Powerlord

1
В действительности, Java разрешает доступ к статическим элементам через экземпляры: см. Статическая переменная в Java
Ричард Дж. П. Ле Гуэн,

Правильная современная среда IDE выдаст предупреждение, когда вы это сделаете, поэтому, по крайней мере, вы можете его перехватить, в то время как Oracle сможет поддерживать обратную совместимость.
Джимби

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

@RichieHH Это не то, о чем они спорят. Вопрос почему это разрешено вызов variable.staticMethod(), вместо того Class.staticMethod(), где variableпеременная с объявленным типом Class. Я согласен, что это плохой языковой дизайн.
fishinear

3

Переопределение в Java просто означает, что конкретный метод будет вызываться в зависимости от типа объекта во время выполнения, а не от его типа во время компиляции (что имеет место в случае переопределенных статических методов). Так как статические методы являются методами класса, они не являются методами экземпляра, поэтому они не имеют никакого отношения к тому, какая ссылка указывает на какой объект или экземпляр, потому что из-за природы статического метода он принадлежит определенному классу. Вы можете переопределить его в подклассе, но этот подкласс не будет ничего знать о статических методах родительского класса, потому что, как я сказал, он специфичен только для того класса, в котором он был объявлен. Доступ к ним с использованием ссылок на объекты - это просто дополнительная свобода, предоставляемая разработчиками Java, и мы, конечно, не должны думать о том, чтобы прекращать эту практику только тогда, когда они ограничивают ее более подробными данными и примерами. http://faisalbhagat.blogspot.com/2014/09/method-overriding-and-method-hiding.html


3

Переопределив, вы достигните динамического полиморфизма. Когда вы говорите о переопределении статических методов, слова, которые вы пытаетесь использовать, противоречивы.

Статика говорит - время компиляции, переопределение используется для динамического полиморфизма. Оба противоположны по своей природе и, следовательно, не могут быть использованы вместе.

Динамическое полиморфное поведение возникает, когда программист использует объект и обращается к методу экземпляра. JRE отобразит разные методы экземпляров разных классов в зависимости от того, какой тип объекта вы используете.

Когда вы говорите о переопределении статических методов, мы обращаемся к статическим методам, используя имя класса, которое будет связано во время компиляции, поэтому нет понятия связывания методов во время выполнения со статическими методами. Таким образом, сам термин «переопределение» статических методов не имеет никакого значения.

Примечание: даже если вы обращаетесь к методу класса с помощью объекта, все же Java-компилятор достаточно умен, чтобы это выяснить, и будет выполнять статическое связывание.


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

static не означает время компиляции, static означает, что он связан с классом, а не с каким-либо конкретным объектом. он имеет столько же, если не больше смысла, чем создание фабрики классов, за исключением того, что статический Box.createBoxимеет больше смысла, чем BoxFactory.createBox, и является неизбежным шаблоном, когда нужно проверять конструкцию ошибок, не выбрасывая исключения (конструкторы не могут потерпеть неудачу, они могут только убить процесс / throw исключение), тогда как статический метод может возвращать ноль при сбое или даже принимать обратные вызовы об успехе / ошибке, чтобы написать что-то вроде hastebin.com/codajahati.java .
Дмитрий

2

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


1
Привет G4uKu3_Gaurav. Спасибо за решение внести свой вклад. Однако мы обычно ожидаем более длинные и подробные ответы, чем этот.
DJClayworth

@DJClayworth вы должны перейти по этой ссылке для подробного ответа geeksforgeeks.org/…
g1ji

Спасибо за ссылку. На самом деле я здесь, чтобы быть полезным для новичков на сайте, чтобы объяснить, как сайт работает для тех, кто к нему не привык, а не потому, что мне нужен был ответ на вопрос.
DJClayworth

1

Простое решение: использовать экземпляр Singleton. Это позволит переопределения и наследования.

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

Haxe языковой класс:

package rflib.common.utils;
import haxe.ds.ObjectMap;



class SingletonsRegistry
{
  public static var instances:Map<Class<Dynamic>, Dynamic>;

  static function __init__()
  {
    StaticsInitializer.addCallback(SingletonsRegistry, function()
    {
      instances = null;
    });

  } 

  public static function getInstance(cls:Class<Dynamic>, ?args:Array<Dynamic>)
  {
    if (instances == null) {
      instances = untyped new ObjectMap<Dynamic, Dynamic>();      
    }

    if (!instances.exists(cls)) 
    {
      if (args == null) args = [];
      instances.set(cls, Type.createInstance(cls, args));
    }

    return instances.get(cls);
  }


  public static function validate(inst:Dynamic, cls:Class<Dynamic>)
  {
    if (instances == null) return;

    var inst2 = instances[cls];
    if (inst2 != null && inst != inst2) throw "Can\'t create multiple instances of " + Type.getClassName(cls) + " - it's singleton!";
  }

}

Очень круто, я впервые слышу о языке программирования Haxe :)
cyc115

1
Это намного лучше реализовано в Java как статический метод самого класса; Singleton.get(), Реестр - это просто накладные расходы, и он исключает GC для классов.
Лоуренс Дол

Вы правы, это классическое решение. Я точно не помню, почему я выбрал реестр, вероятно, имел некоторые рамки, которые привели к этому результату.
Райво Фишмейстер

1

Статический метод, переменная, блок или вложенный класс принадлежит всему классу, а не объекту.

Метод в Java используется для представления поведения объекта / класса. Здесь, поскольку метод является статическим (т. Е. Статический метод используется для представления поведения только класса.) Изменение / переопределение поведения всего класса нарушит феномен одного из фундаментальных элементов объектно-ориентированного программирования, то есть высокой сплоченности , (помните, конструктор - это особый метод в Java.)

Высокая сплоченность - у одного класса должна быть только одна роль. Например: класс автомобилей должен производить только автомобильные объекты, а не велосипед, грузовики, самолеты и т. Д. Но класс автомобилей может иметь некоторые особенности (поведение), которые принадлежат только ему.

Поэтому при разработке языка программирования Java. Разработчики языка решили позволить разработчикам сохранять некоторые поведения класса при себе, делая метод статичным по своей природе.


Приведенный ниже фрагмент кода пытается переопределить статический метод, но не встретит никакой ошибки компиляции.

public class Vehicle {
static int VIN;

public static int getVehileNumber() {
    return VIN;
}}

class Car extends Vehicle {
static int carNumber;

public static int getVehileNumber() {
    return carNumber;
}}

Это потому, что здесь мы не переопределяем метод, а просто объявляем его заново . Java позволяет повторно объявить метод (статический / нестатический).

Удаление статического ключевого слова из метода getVehileNumber () класса Car приведет к ошибке компиляции, поскольку мы пытаемся изменить функциональность статического метода, который принадлежит только классу Vehicle.

Кроме того, если getVehileNumber () объявлен как final, код не будет компилироваться, поскольку ключевое слово final ограничивает программиста от повторного объявления метода.

public static final int getVehileNumber() {
return VIN;     }

В целом, это зависит от разработчиков программного обеспечения, где использовать статические методы. Лично я предпочитаю использовать статические методы для выполнения некоторых действий без создания какого-либо экземпляра класса. Во-вторых, скрыть поведение класса от внешнего мира.


1

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


Как это противоречит интуитивно public abstract IBox createBox();понятному интерфейсу IBox? Box может реализовать IBox для переопределения createBox и иметь результат создания объекта в допустимом IBox, в противном случае вернуть ноль. Конструкторы не могут возвращать «ноль», и поэтому вы вынуждены (1) использовать исключения ВЕЗДЕ (что мы делаем сейчас) или (2) создавать фабричные классы, которые делают то, что я говорил ранее, но таким образом, что это не имеет смысла для новичков или экспертов Java (что мы и делаем сейчас). Статические нереализованные методы решают эту проблему.
Дмитрий

-1

Теперь, увидев приведенные выше ответы, все знают, что мы не можем переопределить статические методы, но не следует неправильно понимать концепцию доступа к статическим методам из подкласса .

Мы можем получить доступ к статическим методам суперкласса с помощью ссылки на подкласс, если этот статический метод не был скрыт новым статическим методом, определенным в подклассе.

Например, см. Ниже код: -

public class StaticMethodsHiding {
    public static void main(String[] args) {
        SubClass.hello();
    }
}


class SuperClass {
    static void hello(){
        System.out.println("SuperClass saying Hello");
    }
}


class SubClass extends SuperClass {
    // static void hello() {
    // System.out.println("SubClass Hello");
    // }
}

Вывод:-

SuperClass saying Hello

См. Документацию Java oracle и поиск « Что можно сделать в подклассе» для получения подробной информации о сокрытии статических методов в подклассе.

Спасибо


-3

Следующий код показывает, что это возможно:

class OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriden Meth");   
}   

}   

public class OverrideStaticMeth extends OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriding Meth");   
}   

public static void main(String[] args) {   
OverridenStaticMeth osm = new OverrideStaticMeth();   
osm.printValue();   

System.out.println("now, from main");
printValue();

}   

} 

1
Нет, это не так; объявленный статический тип osm- OverridenStaticMethнет OverrideStaticMeth.
Лоуренс Дол

2
Кроме того, я бы старался не использовать так много Meth при программировании <большой усмешки>;
Лоуренс Дол
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.