Чому Java не дозволяє переосмислити статичні методи?


534

Чому не можна перекрити статичні методи?

Якщо можливо, скористайтеся прикладом.


3
Більшість мов OOP цього не дозволяють.
jmucchiello

7
@jmucchiello: дивіться мою відповідь. Я думав так само, як і ви, але потім дізнався про методи "класу" Ruby / Smalltalk, і тому є інші справжні мови OOP, які роблять це.
Кевін Брок

5
@jmucchiello Більшість мов OOP не є справжньою мовою OOP (я думаю про 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 це відбувається під час виклику статичного методу з об'єкта ( 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");
    }
}

Цей код не працюватиме, як ви могли очікувати. А саме, SpecialE zaposee отримує бонус у розмірі 2%, як і постійні працівники. Але якщо ви видалите "статичні", то SpecialE zaposee отримає 3% бонус.

(Правда, цей приклад - поганий стиль кодування, оскільки в реальному житті ви, швидше за все, хочете, щоб мультиплікатор бонусів знаходився десь у базі даних, а не в жорсткому коді. Але це лише тому, що я не хотів багато збивати приклад коду, не має значення для пункту.)

Мені здається цілком правдоподібним, що ви, можливо, захочете зробити getBonusMultiplier статичним. Можливо, ви хочете мати можливість відображати мультиплікатор бонусу для всіх категорій працівників, не потребуючи примірника працівника в кожній категорії. Який би був сенс пошуку таких прикладів? Що робити, якщо ми створюємо нову категорію працівника і не маємо до неї жодних працівників? Це цілком логічно статична функція.

Але це не працює.

І так, так, я можу придумати будь-яку кількість способів переписати вищевказаний код, щоб він працював. Моя думка не в тому, що це створює нерозв’язну проблему, а в тому, що створює пастку для необережного програміста, оскільки мова веде себе не так, як я думаю, що розумна людина очікувала б.

Можливо, якби я спробував написати компілятор для мови OOP, я швидко зрозумів би, чому реалізувати його, щоб статичні функції можна було перекрити було б складно чи неможливо.

Або, можливо, є якась вагома причина, чому Java поводиться так. Чи може хтось вказати на перевагу такої поведінки, якусь категорію проблем, яку це полегшує? Я маю на увазі, не просто вказуйте мені на специфікацію мови Java і скажіть "бачте, це документально підтверджено, як вона поводиться". Я знаю це. Але чи є вагома причина, чому вона ДОЛЖЕН би поводитись так? (Окрім очевидного "змусити його працювати правильно було занадто важко" ...)

Оновлення

@VicKirk: Якщо ви маєте на увазі, що це "поганий дизайн", оскільки він не відповідає тому, як Java обробляє статику, моя відповідь: "Ну, так, звичайно". Як я вже говорив у своєму початковому дописі, він не працює. Але якщо ви маєте на увазі, що це поганий дизайн в тому сенсі, що було б щось принципово неправильно з мовою, де це працює, тобто там, де статику можна було б замінити так само, як і віртуальні функції, то це якось внесе двозначність, або це буде неможливо Ефективно впроваджуючи щось подібне, я відповідаю: "Чому? Що не так з концепцією?"

Я думаю, що приклад, який я даю, - це дуже природна річ, яку хочу зробити. У мене є клас, який має функцію, яка не залежить від будь-яких даних про екземпляр, і яку, можливо, я б дуже хотів викликати незалежно від екземпляра, а також хотів зателефонувати з методу екземпляра. Чому це не повинно працювати? Я зіткнувся з цією ситуацією досить багато разів за ці роки. На практиці я її обходжу, роблячи функцію віртуальною, а потім створюю статичний метод, єдиною метою якого в житті є статичний метод, який передає виклик віртуальному методу з манекеном. Це здається дуже крутим способом дістатися туди.


11
@Bemrose: Але це моя думка: чому мені не дозволяють це робити? Можливо, моя інтуїтивна концепція того, що повинен робити "статичний", відрізняється від вашої, але в основному я вважаю статику як метод, який МОЖЕ бути статичним, оскільки він не використовує жодних даних про екземпляр, і який ДОЛЖЕН бути статичним, тому що ви можете хотіти називати його незалежно від екземпляра. Статика чітко прив’язана до класу: я очікую, що Integer.valueOf буде прив’язаний до Integers, а Double.valueOf - прив'язаним до парних.
Джей

9
@ewernli & Bemrose: Так, так воно і є. Я не обговорюю це. Оскільки код у моєму прикладі не працює, я, звичайно, не намагався би його написати. Моє запитання, чому саме так. (Я боюся, що це перетворюється на одну з тих розмов, де ми просто не спілкуємося. Вибачте, містер Продавець, чи можу я отримати один з них червоним кольором? "" Ні, коштує 5 доларів ". 5 доларів, але чи можу я отримати червоний? "" Сер, я щойно сказав вам, що коштує $ 5 "." Добре, я знаю ціну, але я розпитував про колір "." Я вже сказав вам ціну! " і т. д.)
Джей

6
Я думаю, що зрештою цей код заплутаний. Поміркуйте, чи передається екземпляр як параметр. Тоді ви говорите, що екземпляр виконання повинен диктувати, який статичний метод викликається. Це в основному робить цілу окрему ієрархію паралельною існуючій інстанції. Що робити, якщо підклас визначає той самий метод підпису, що і нестатичний? Я думаю, що правила ускладнюватимуть справи. Саме подібних мовних ускладнень Java намагається уникнути.
Ісіхай

6
@Yishai: RE "Екземпляр запуску диктує, який статичний метод називається": Рівно. Я не бачу, чому ти не можеш нічого зробити зі статикою, яку ти можеш зробити з віртуальною. "Окрема ієрархія": Я б сказав, зробіть це частиною тієї ж ієрархії. Чому статику не включають до однієї ієрархії? "Підклас визначає той самий підпис нестатичний": Я припускаю, що це було б незаконно, подібно до того, як це незаконно, скажімо, мати підклас, який замінює функцію з ідентичною підписом, але іншого типу повернення, або не викидати всі винятки, які батько кидає, або мати більш вузьку сферу.
Джей

28
Думаю, у Джея є сенс - і мене це здивувало, коли я виявив, що статику неможливо переоцінити. Частково тому, що якщо у мене A з методом, someStatic()а B поширюється на A, то B.someMethod() прив'язується до методу в А. Якщо я згодом додаю someStatic()до B, викликовий код все ще викликає, A.someStatic()поки я не перекомпілюю код виклику. Також мене здивувало, що bInstance.someStatic()використовує оголошений тип bInstance, а не тип виконання, оскільки він прив'язується при компіляції не посиланням, тому A bInstance; ... bInstance.someStatic()викликає A.someStatic (), якщо існує B.someStatic ().
Лоуренс Дол,

42

Коротка відповідь: це цілком можливо, але 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 не дозволяє переосмислити статичні методи за замовчуванням, якщо детально ознайомитись з документацією класів Class і Method на 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 як RegularEслужбовця та призначимо їй екземпляр SpecialE Employee, ми все одно матимемо виклик методу RegularE Employee у першому випадку та виклик методу SpecialE Employee у другому випадку

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.

EDIT

Ви можете перевантажувати статичний метод, це нормально. Але ви не можете перекрити статичний метод, тому що клас не є об'єктом першого класу. Ви можете використовувати відображення для отримання класу об'єкта під час виконання, але отриманий вами об'єкт не паралельний ієрархії класів.

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
Навіть багато сучасних мов, як Рубі, мають методи класу і дозволяють їх переосмислити.
Чандра Секар

3
Класи існують як об'єкти на Java. Дивіться клас "Клас". Я можу сказати myObject.getClass (), і він поверне мені екземпляр відповідного об'єкта класу.
Джей

5
Ви отримуєте лише "опис" класу - не сам клас. Але різниця тонка.
ewernli

Ви все ще маєте клас, але він прихований у VM (біля завантажувача класів), користувач майже не має до нього доступу.
mathk

Для clazz2 instanceof clazz1правильного використання ви можете замість цього використовувати class2.isAssignableFrom(clazz1), що, на мою думку, повернеться правдою у вашому прикладі.
Саймон Форсберг

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. Ви читатимете всюди, що неможливо спробувати самостійно компілювати та працювати. ви отримаєте свою відповідь. Наприклад, якщо у вас є тварини класу і статичний метод eat (), і ви перекриєте цей статичний метод у своєму підкласі, він називає його Dog. Тоді, коли б ви не присвоїли об'єкту Собаку Довідку про тварин і зателефонували eat () відповідно до Java Dog’s 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 використовує пізнє прив'язування, що означає, що методи викликаються лише під час виконання, але не у випадку статичних методів. У статичних методах компілятор викликає методи на час компіляції, а не на час виконання, тому ми отримуємо методи відповідно до посилання, а не відповідно до об'єкта, посилання, що містить, тому ви можете сказати, що він практично підтримує статичне перекриття, але теоретично це не робить 'т.


3
Позиція статичних методів від об'єкта погана.
Дмитро

6

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


1
Ви не читали жодної з інших відповідей, які вже висвітлювали це, і даючи зрозуміти, що це недостатньо причин для того, щоб знизити переважаючі статики на концептуальному рівні. Ми знаємо, що це не працює. Це абсолютно "чисто бажання переосмислення статичних методів, і це можливо в багатьох інших мовах".
RichieHH

Річард, припустимо на хвилину, що коли я відповів на це запитання 4 роки тому, більшість цих відповідей не було опубліковано, тому не будь дупою! Не потрібно стверджувати, що я не читав уважно. Крім того, ви не читали, що ми лише обговорюємо важливе значення стосовно Java. Кому цікаво, що можливо іншими мовами. Це не має значення. Ідіть троля кудись ще Ваш коментар не додає нічого цінного для цієї теми.
Афіни Холлоуей

6

У Java (і багатьох мовах OOP, але я не можу говорити назавжди; а деякі взагалі не мають статики) усі методи мають фіксовану підпис - параметри та типи. У віртуальному методі мається на увазі перший параметр: посилання на сам об'єкт, і коли його викликає всередині об'єкта, компілятор автоматично додаєthis .

Для статичних методів різниці немає - вони все ще мають фіксований підпис. Однак, оголосивши метод статичним, ви чітко заявили, що компілятор не повинен включати параметр об'єкта, що мається на увазі, на початку цієї підпису. Тому будь-який інший код, який викликає це, не повинен намагатися ставити посилання на об'єкт у стеку . Якби це зробило це, то виконання методу не працюватиме, оскільки параметри опиняться в неправильному місці - зміщеному на один - на стеці.

Через цю різницю між двома; віртуальні методи завжди мають посилання на контекстний об'єкт (тобто this), тож можна посилатися на все, що знаходиться в купі, що належить до цього примірника об'єкта. Але при статичних методах, оскільки немає посилання, цей метод не може отримати доступ до будь-яких змінних об'єктів і методів, оскільки контекст не відомий.

Якщо ви хочете, щоб Java змінила визначення, щоб контекст об'єкта передався для кожного методу, статичного або віртуального, ви, по суті, мали б лише віртуальні методи.

Як хтось запитав у коментарі до оп - яка ваша причина та мета, щоб бажати цієї функції?

Я не знаю Рубі багато, оскільки про це згадував ОП, я робив деякі дослідження. Я бачу, що в класах Ruby - це дійсно особливий вид об’єкта і можна створювати (навіть динамічно) нові методи. Класи - це об'єкти повного класу в Ruby, їх немає на Java. Це лише те, що вам доведеться прийняти під час роботи з Java (або C #). Це не динамічні мови, хоча C # додає деякі форми динаміки. Насправді у Рубі немає «статичних» методів, наскільки я міг знайти - у такому випадку це методи на об'єкті класу синглів. Потім ви можете замінити цей синглтон з новим класом, і методи в об'єкті попереднього класу будуть викликати ті, які визначені в новому класі (правильно?). Отже, якщо ви назвали метод у контексті оригінального класу, він все одно виконає лише оригінальну статику, але виклик методу у похідному класі буде викликати методи або з батьківського або підкласу. Цікаво, і я бачу в цьому певну цінність. Він приймає іншу думку.

Оскільки ви працюєте на Java, вам потрібно буде налаштуватись на такий спосіб виконання дій. Чому вони це зробили? Ну, мабуть, щоб покращити продуктивність на той час, виходячи з наявних технологій та розуміння. Комп'ютерні мови постійно розвиваються. Поверніться досить далеко, і немає такого поняття, як OOP. В майбутньому з’являться інші нові ідеї.

EDIT : Ще один коментар. Тепер, коли я бачу відмінності і як сам Java / C # розробник, я можу зрозуміти, чому відповіді, які ви отримуєте від розробників Java, можуть збивати з пантелику, якщо ви йдете з такої мови, як Ruby. staticМетоди Java не такі, як classметоди Ruby . Java-розробникам буде важко це зрозуміти, як, навпаки, тим, хто працює переважно з такою мовою, як Ruby / Smalltalk. Я бачу, як це також сильно заплутає той факт, що Java також використовує "метод класу" як інший спосіб говорити про статичні методи, але цей самий термін Рубі використовує по-різному. У Java немає методів класичного стилю Ruby (вибачте); У Ruby немає статичних методів стилю Java, які насправді є лише старими функціями процедурного стилю, як це знайдено у C.

До речі - дякую за запитання! Сьогодні я дізнався щось нове для мене про методи класу (стиль Ruby).


1
Звичайно, немає причин, чому Java не могла передати об'єкт "Class" як прихований параметр статичним методам. Це просто не було розроблено для цього.
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 ()" на об'єкті типу виконання як "Підклас", а не як "Суперклас". Це підтверджує, що статичні методи завжди вирішуються, використовуючи лише інформацію про час компіляції.


5

Взагалі не має сенсу допускати "переосмислення" статичних методів, оскільки не було б хорошого способу визначити, який з них викликати під час виконання. Беручи до прикладу Співробітник, якщо ми називаємо RegularE Employee.getBonusMultiplier () - який метод слід виконати?

У випадку з Java, можна уявити визначення мови, де можна "переосмислити" статичні методи, доки вони викликаються через об'єкт об'єкта. Однак все це робиться - це повторна реалізація методів звичайного класу, додаючи надлишок мови, не додаючи жодної користі.


2
Інтуїтивно, я думаю, він повинен працювати як віртуальна функція. Якщо B поширює A і обидва A і B мають віртуальні функції з назвою doStuff, компілятор знає, що екземпляри A повинні використовувати A.doStuff, а екземпляри B повинні використовувати B.doStuff. Чому він не може зробити те ж саме зі статичними функціями? Зрештою, компілятор знає, для якого класу кожен об'єкт є екземпляром.
Джей

Ем ... Джей, статичний метод не потрібно викликати (як правило, не) викликати екземпляр ...
meriton

2
@meriton, але тоді ще простіше, ні? Якщо статичний метод викликається за допомогою імені класу, ви використовуєте метод, відповідний для класу.
CPerkins

Але тоді те, що головне робить для вас. Якщо ви зателефонуєте на A.doStuff (), чи слід використовувати версію, яку було замінено на "B extends A", або версію, яку було замінено на "C, поширюється A". І якщо у вас є C або B, ви все одно зателефонуєте до цих версій ... не потрібно переоцінювати.
PSpeed

@meriton: Це правда, що статичний метод, як правило, не викликається екземпляром, але я припускаю, що це викликає те, що такий виклик не робить нічого корисного, враховуючи сучасний дизайн Java! Я припускаю, що альтернативна конструкція могла бути кращою ідеєю. До речі, статичні функції в реальному сенсі викликаються з екземпляром досить звичайно: Коли ви викликаєте статичну функцію всередині віртуальної функції. Тоді ви неявно отримуєте this.function (), тобто поточний екземпляр.
Джей

5

Переосмислюючи, ми можемо створити поліморфну ​​природу залежно від типу об'єкта. Статичний метод не має відношення до об'єкта. Тож java не може підтримувати переосмислення статичного методу.


5

Мені подобається і подвійний коментар Джея ( https://stackoverflow.com/a/2223803/1517187 ).
Я згоден, що це поганий дизайн Java.
Багато інших мов підтримують переважаючі статичні методи, як ми бачимо в попередніх коментарях. Я відчуваю, що Джей також прийшов на Яву з Delphi, як я.
Delphi (Object Pascal) була першою мовою, що реалізувала OOP.
Очевидно, що багато людей мали досвід роботи з цією мовою, оскільки в минулому вона була єдиною мовою для написання комерційних GUI-продуктів. І - так, ми могли б у Delphi перекрити статичні методи. Насправді статичні методи в Delphi називають "класовими методами", тоді як у Delphi була інша концепція "статичних методів Delphi", які були методами з ранньою зв'язуванням. Щоб замінити методи, вам довелося скористатися пізнім прив'язкою, оголосити "віртуальну" директиву. Так що це було дуже зручно та інтуїтивно, і я би очікував цього на Java.


3

Яка користь принесе перевагу статичним методам. Статичні методи не можна викликати через екземпляр.

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

EDIT: Схоже, що через невдалий нагляд за мовним дизайном, ви можете викликати статичні методи через екземпляр. Взагалі ніхто цього не робить. Моє ліжко.


8
"Ви не можете викликати статичні методи через екземпляр" Насправді однією з примх Java є те, що ви МОЖЕТЕ викликати статичні методи через екземпляр, навіть якщо це надзвичайно погана ідея.
Powerlord

1
Дійсно, Java дозволяє отримати доступ до статичних членів через екземпляри: див. Статичну змінну на Java
Richard JP Le Guen

Належний сучасний IDE генеруватиме попередження, коли ви це зробите, так що принаймні ви можете його вловити, тоді як Oracle може продовжувати працювати з сумісністю назад.
Гімбі

1
Немає нічого концептуально неправильного в тому, щоб мати можливість викликати статичний метод через екземпляр. Це верещання заради кайблінгу. Чому щось подібне екземпляру дати НЕ повинен викликати власні статичні методи, передаючи дані екземпляра у функцію через інтерфейс виклику?
RichieHH

@RichieHH Це не те, про що вони хизуються. Питання в тому, чому дозволено дзвонити variable.staticMethod()замість того Class.staticMethod(), де variableє змінна із заявленим типом Class. Я згоден, це поганий дизайн мови.
рибалкапоруч

3

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


3

Переосмислюючи, ви досягаєте динамічного поліморфізму. Коли ви говорите переважаючі статичні методи, слова, які ви намагаєтесь використовувати, суперечать один одному.

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

Динамічна поліморфна поведінка виникає, коли програміст використовує об'єкт і звертається до методу екземпляра. JRE буде відображати різні методи екземплярів різних класів на основі того, який тип об'єкта ви використовуєте.

Коли ви говорите про переосмислення статичних методів, до статичних методів ми отримаємо доступ, використовуючи ім’я класу, яке буде пов’язане під час компіляції, тому не існує концепції зв’язування методів під час виконання зі статичними методами. Отже, сам термін "переосмислення" статичних методів не має жодного значення.

Примітка: навіть якщо ви звертаєтесь до методу класу з об'єктом, компілятор Java все-таки досить розумний, щоб знайти його, і він буде робити статичне посилання.


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

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

2

Відповідь на це запитання проста: метод або змінна, позначена як статична, належить лише класу, тому статичний метод не може бути успадкований у підкласі, оскільки вони належать лише до суперкласу.


1
Привіт G4uKu3_Gaurav. Дякуємо, що вирішили зробити свій внесок. Однак ми, як правило, очікуємо більш довгих і детальних відповідей, ніж це.
DJClayworth

@DJClayworth вам слід перейти за цим посиланням для детальної відповіді geeksforgeeks.org/…
g1ji

Дякуємо за посилання Насправді я тут, щоб допомогти новачкам сайту, щоб пояснити, як працює сайт для тих, хто до нього не звик, не тому, що мені потрібна відповідь на питання.
DJClayworth

1

Просте рішення: Використовуйте одиночний екземпляр. Це дозволить скасувати і успадкувати.

У своїй системі у мене є клас SingletonsRegistry, який повертає екземпляр для пройденого класу. Якщо екземпляр не знайдений, він створюється.

Мовний клас Хакс:

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

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 () оголошено як остаточний, код не буде компілюватися, оскільки кінцеве ключове слово обмежує програміст повторно оголошувати метод.

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

Взагалі, це розробники програмного забезпечення, які використовуються, де використовувати статичні методи. Я особисто вважаю за краще використовувати статичні методи для виконання деяких дій, не створюючи жодного примірника класу. По-друге, приховати поведінку класу від зовнішнього світу.


1

Ось просте пояснення. Статичний метод асоціюється з класом, тоді як метод екземпляра асоціюється з певним об'єктом. Заміни дозволяють викликати різну реалізацію методів, що переосмислюються, пов'язаних з конкретним об'єктом. Тож перекрити статичний метод, котрий навіть не пов'язаний з об'єктами, а сам клас в першу чергу, є протиінтуїтивним. Таким чином, статичні методи не можуть бути відмінені на основі того, що об'єкт викликає його, він завжди буде пов'язаний з класом, де він був створений.


Як протилежно інтуїтивно public abstract IBox createBox();зрозуміти наявність внутрішнього інтерфейсу IBox? Box може реалізувати IBox, щоб перекрити createBox, і створити результат об'єкта в дійсному IBox, інакше повернути нуль. Конструктори не можуть повернути "null", і тому ви змушені (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.