Question Pourquoi Java ne permet-il pas de substituer les méthodes statiques?


Pourquoi n'est-il pas possible de remplacer les méthodes statiques?

Si possible, veuillez utiliser un exemple.


448
2018-02-08 17:08


origine


Réponses:


La substitution dépend de l'instance d'une classe. Le point de polymorphisme est que vous pouvez sous-classer une classe et que les objets qui implémentent ces sous-classes auront des comportements différents pour les mêmes méthodes définies dans la super-classe (et remplacées dans les sous-classes). Une méthode statique n'est associée à aucune instance d'une classe, de sorte que le concept n'est pas applicable.

Deux considérations ont influencé la conception de Java. L'un était un souci de performance: il y avait eu beaucoup de critiques sur Smalltalk à propos de sa lenteur (la collecte des ordures et les appels polymorphes en faisaient partie) et les créateurs de Java étaient déterminés à éviter cela. Un autre était la décision que le public cible pour Java était les développeurs C ++. Faire fonctionner les méthodes statiques comme elles le faisaient avait l'avantage d'être familier pour les programmeurs C ++ et était également très rapide, car il n'est pas nécessaire d'attendre l'exécution pour déterminer la méthode à appeler.


435
2018-02-08 17:12



Personnellement, je pense que c'est une faille dans la conception de Java. Oui, oui, je comprends que les méthodes non statiques sont attachées à une instance alors que les méthodes statiques sont attachées à une classe, etc. Et bien, considérez le code suivant:

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");
    }
}

Ce code ne fonctionnera pas comme prévu. À savoir, SpecialEmployee obtiennent un bonus de 2%, tout comme les employés réguliers. Mais si vous supprimez les "statiques", alors SpecialEmployee obtient un bonus de 3%.

(Certes, cet exemple est un style de codage médiocre, car dans la vraie vie, vous voudrez probablement que le multiplicateur de bonus soit placé dans une base de données plutôt que codé en dur. Mais ce n’est que parce que je ne voulais pas de code sans rapport avec le point.)

Il me semble tout à fait plausible que vous puissiez vouloir rendre getBonusMultiplier statique. Vous souhaitez peut-être afficher le multiplicateur de bonus pour toutes les catégories d’employés, sans devoir avoir une instance d’un employé dans chaque catégorie. Quel serait l'intérêt de rechercher de tels exemples? Que se passe-t-il si nous créons une nouvelle catégorie d'employés et qu'aucun employé n'y est encore affecté? C'est logiquement une fonction statique.

Mais ça ne marche pas.

Et oui, oui, je peux penser à un certain nombre de façons de réécrire le code ci-dessus pour le faire fonctionner. Ce que je veux dire, c’est que cela ne crée pas un problème insoluble, mais qu’il crée un piège pour le programmeur imprudent, car le langage ne se comporte pas comme une personne raisonnable le pense.

Peut-être que si j'essayais d'écrire un compilateur pour un langage OOP, je verrais rapidement pourquoi l'implémenter pour que les fonctions statiques puissent être remplacées serait difficile ou impossible.

Ou peut-être qu'il y a une bonne raison pour laquelle Java se comporte de cette façon. Quelqu'un peut-il souligner un avantage à ce comportement, une catégorie de problème qui est facilitée par cela? Je veux dire, ne me pointez pas seulement vers la spécification du langage Java et dites "voir, c'est documenté comment ça se comporte". Je le sais. Mais y a-t-il une bonne raison pour laquelle il DEVRAIT se comporter de cette façon? (En plus de l'évidence "le faire fonctionner correctement était trop difficile" ...)

Mettre à jour

@VicKirk: Si vous voulez dire qu'il s'agit d'une "mauvaise conception" car elle ne correspond pas à la manière dont Java gère les statistiques, je réponds "Bien sûr, bien sûr". Comme je l'ai dit dans mon post original, ça ne marche pas. Mais si vous voulez dire qu’il s’agit d’une mauvaise conception en ce sens qu’il y aurait quelque chose de fondamentalement erroné dans un langage où cela fonctionnait, c’est-à-dire mettre en œuvre efficacement ou une telle, je réponds, "Pourquoi? Quel est le problème avec le concept?"

Je pense que l'exemple que je donne est une chose très naturelle à vouloir faire. J'ai une classe qui a une fonction qui ne dépend d'aucune donnée d'instance et que je pourrais très raisonnablement vouloir appeler indépendamment d'une instance, ainsi que vouloir appeler depuis une méthode d'instance. Pourquoi cela ne devrait-il pas fonctionner? J'ai rencontré cette situation un bon nombre de fois au cours des années. En pratique, je contourne le problème en rendant la fonction virtuelle, puis en créant une méthode statique dont le seul but dans la vie est d’être une méthode statique qui transmet l'appel à la méthode virtuelle avec une instance fictive. Cela semble être un moyen très détourné d'y arriver.


164
2018-02-08 18:14



La réponse courte est: c'est tout à fait possible, mais Java ne le fait pas.

Voici un code qui illustre la état actuel des choses en Java:

Fichier 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();
  }
}

Fichier 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();
  }
}

Si vous exécutez ceci (je l'ai fait sur un Mac, à partir d'Eclipse, en utilisant Java 1.6), vous obtenez:

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.

Ici le seulement les cas qui pourraient être une surprise (et dont la question est sur) semblent être les premier Cas:

"Le type d'exécution n'est pas utilisé pour déterminer quelles méthodes statiques sont appelées, même lorsqu'elles sont appelées avec une instance d'objet (obj.staticMethod()). "

et le dernier Cas:

"Lorsque vous appelez une méthode statique à partir d'une méthode objet d'une classe, la méthode statique choisie est celle accessible depuis la classe elle-même et ne pas de la classe définissant le type d'exécution de l'objet. "

Appel avec une instance d'objet

L'appel statique est résolu au moment de la compilation, tandis qu'un appel de méthode non statique est résolu au moment de l'exécution. Notez que bien que les méthodes statiques sont hérité (du parent) ils ne sont pas substitué (par enfant). Cela pourrait être une surprise si vous vous attendiez autrement.

Appel à partir d'une méthode d'objet

Objet les appels de méthode sont résolus en utilisant le type d'exécution, mais statiques (classeLes appels de méthode sont résolus en utilisant le type compilé (déclaré).

Changer les règles

Pour modifier ces règles, de sorte que le dernier appel de l'exemple appelé Child.printValue(), les appels statiques devraient être fournis avec un type à l'exécution, plutôt que le compilateur résolvant l'appel à la compilation avec la classe déclarée de l'objet (ou du contexte). Les appels statiques peuvent alors utiliser la hiérarchie de type (dynamique) pour résoudre l'appel, tout comme les appels de méthode d'objet le font aujourd'hui.

Ce serait facilement réalisable (si nous avons changé Java: -O), et n'est pas du tout déraisonnable, cependant, il a quelques considérations intéressantes.

La principale considération est que nous devons décider lequel Les appels de méthode statique devraient le faire.

En ce moment, Java a cette "bizarrerie" dans la langue par laquelle obj.staticMethod() les appels sont remplacés par ObjectClass.staticMethod() appels (normalement avec un avertissement). [Remarque:  ObjectClass est le type de compilation obj.] Ceux-ci seraient de bons candidats pour passer de cette manière, en prenant le type d'exécution de obj.

Si nous le faisions, cela rendrait les corps de méthode plus difficiles à lire: les appels statiques dans une classe parente pourraient potentiellement être dynamiquement "re-routed". Pour éviter cela, il faudrait appeler la méthode statique avec un nom de classe - et cela rend les appels plus clairement résolus avec la hiérarchie de type à la compilation (comme maintenant).

Les autres façons d'invoquer une méthode statique sont plus délicates: this.staticMethod() devrait signifier la même chose que obj.staticMethod(), en prenant le type d'exécution de this. Cependant, cela peut causer des maux de tête avec les programmes existants, qui appellent des méthodes statiques (apparemment locales) sans décoration (ce qui est sans doute équivalent à this.method()).

Alors qu'en est-il des appels sans fioritures staticMethod()? Je suggère qu'ils fassent la même chose qu'aujourd'hui et utilisent le contexte de classe local pour décider quoi faire. Sinon, une grande confusion s'ensuivrait. Bien sûr, cela signifie que method() signifierait this.method() si method était une méthode non statique, et ThisClass.method() si method étaient une méthode statique. Ceci est une autre source de confusion.

Autres considérations

Si nous changions ce comportement (et que nous faisions des appels statiques potentiellement dynamiquement non locaux), nous voudrions probablement revoir la signification de final, private et protected comme qualificatifs sur static méthodes d'une classe. Nous devrions alors tous nous habituer au fait que private static et public final Les méthodes ne sont pas remplacées et peuvent donc être résolues en toute sécurité au moment de la compilation, et sont "sûres" à lire en tant que références locales.


37
2017-10-10 11:19



En fait, nous avions tort.
Bien que Java ne vous permette pas de surcharger les méthodes statiques par défaut, si vous examinez attentivement la documentation des classes Class et Method en Java, vous pouvez toujours trouver un moyen d'émuler les méthodes statiques en procédant comme suit:

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());
    }
}

Résultat résultant:

0.02
0.02
0.02
0.03

ce que nous essayions de réaliser :)

Même si nous déclarons la troisième variable Carl comme RegularEmployee et lui assignons l'instance de SpecialEmployee, nous aurons toujours l'appel de la méthode RegularEmployee dans le premier cas et l'appel de la méthode SpecialEmployee dans le second cas

RegularEmployee Carl = new SpecialEmployee();

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

il suffit de regarder la console de sortie:

0.02
0.03

;)


22
2018-02-20 12:43



Les méthodes statiques sont traitées comme globales par la machine virtuelle Java, elles ne sont pas liées à une instance d'objet.

Cela pourrait être conceptuellement possible si vous pouviez appeler des méthodes statiques à partir d'objets de classe (comme dans des langages comme Smalltalk), mais ce n'est pas le cas en Java.

MODIFIER

Vous pouvez surcharge méthode statique, ça va. Mais tu ne peux pas passer outre une méthode statique, car les classes ne sont pas des objets de première classe. Vous pouvez utiliser la réflexion pour obtenir la classe d'un objet au moment de l'exécution, mais l'objet que vous obtenez ne correspond pas à la hiérarchie des classes.

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

Vous pouvez réfléchir sur les cours, mais ça s'arrête là. Vous n'appelez pas une méthode statique en utilisant clazz1.staticMethod()mais en utilisant MyClass.staticMethod(). Une méthode statique n'est pas liée à un objet et il n'y a donc aucune notion de this ni super dans une méthode statique. Une méthode statique est une fonction globale; par conséquent, il n'y a pas non plus de notion de polymorphisme et, par conséquent, le dépassement de méthode n'a aucun sens.

Mais cela pourrait être possible si MyClass était un objet à l'exécution sur lequel vous invoquiez une méthode, comme dans Smalltalk (ou peut-être JRuby comme un commentaire le suggère, mais je ne connais rien à JRuby).

Oh ouais ... encore une chose. Vous pouvez appeler une méthode statique via un objet obj1.staticMethod() mais ce sucre vraiment syntaxique pour MyClass.staticMethod() et devrait être évité. Il déclenche généralement un avertissement dans l'IDE moderne. Je ne sais pas pourquoi ils ont jamais autorisé ce raccourci.


16
2018-02-08 17:12



La substitution de méthode est rendue possible par répartition dynamique, ce qui signifie que le type déclaré d'un objet ne détermine pas son comportement, mais plutôt son type d'exécution:

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

Même si les deux lassie et kermit sont déclarés comme objets de type Animal, leur comportement (méthode .speak()) varie parce que la répartition dynamique sera seulement lier l'appel de méthode .speak() à une implémentation au moment de l'exécution - pas au moment de la compilation.

Maintenant, voici où le static mot-clé commence à avoir un sens: le mot "statique" est un antonyme de "dynamique". Donc, la raison pour laquelle vous ne pouvez pas remplacer les méthodes statiques est qu'il n'y a pas de distribution dynamique sur les membres statiques - parce que statique signifie littéralement "pas dynamique". S'ils ont envoyé dynamiquement (et pourraient donc être remplacés) le static mot-clé n'aurait tout simplement plus de sens.


10
2017-09-20 05:20



Oui. Pratiquement Java permet de surcharger la méthode statique, et non théoriquement si vous substituez une méthode statique en Java, alors il compilera et fonctionnera en douceur mais il perdra le polymorphisme qui est la propriété de base de Java. Vous allez lire Partout qu'il n'est pas possible de vous essayer à la compilation et à l'exécution. vous obtiendrez votre réponse. par exemple. Si vous avez Class Animal et une méthode statique eat () et vous remplacez cette méthode statique dans sa sous-classe, elle s'appelle Dog. Ensuite, lorsque vous attribuez un objet chien à une référence animale et que vous appelez eat () en fonction de Java Dog's eat (), vous devez avoir appelé, mais en statique Overriding Animals 'eat () sera appelé.

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

Selon le principe de polymorphisme de Java, le résultat devrait être Dog Eating.
Mais le résultat était différent car, pour prendre en charge Polymorphism, Java utilise Late Binding, ce qui signifie que les méthodes sont appelées uniquement au moment de l'exécution, mais pas dans le cas des méthodes statiques. Dans les méthodes statiques, le compilateur appelle les méthodes au moment de la compilation plutôt qu'à l'exécution, donc nous obtenons des méthodes selon la référence et non selon l'objet une référence contenant ce qui explique pourquoi on peut dire Pratiquement il supporte le static static mais théoriquement 't.


7
2017-10-29 10:33



En Java (et de nombreux langages OOP, mais je ne peux pas parler pour tous, et certains n'ont pas de statique du tout) toutes les méthodes ont une signature fixe - les paramètres et les types. Dans une méthode virtuelle, le premier paramètre est implicite: une référence à l'objet lui-même et lorsqu'il est appelé depuis l'objet, le compilateur ajoute automatiquement this.

Il n'y a pas de différence pour les méthodes statiques - elles ont toujours une signature fixe. Cependant, en déclarant la méthode static, vous avez explicitement déclaré que le compilateur ne doit pas inclure le paramètre d'objet implicite au début de cette signature. Par conséquent, tout autre code qui appelle cela doit ne doit pas tenter de mettre une référence à un objet sur la pile. Si c'était le cas, alors l'exécution de la méthode ne fonctionnerait pas car les paramètres seraient au mauvais endroit - décalés de un - sur la pile.

A cause de cette différence entre les deux les méthodes virtuelles ont toujours une référence à l'objet de contexte (c.-à-d. this) Il est donc possible de référencer n'importe quel élément du tas qui appartient à cette instance de l’objet. Mais avec les méthodes statiques, comme il n'y a pas de référence passée, cette méthode ne peut accéder à aucune variable ou méthode d'objet car le contexte n'est pas connu.

Si vous souhaitez que Java modifie la définition de façon à ce qu'un contexte d'objet soit passé pour chaque méthode, statique ou virtuelle, alors vous n'aurez essentiellement que des méthodes virtuelles.

Comme quelqu'un a demandé dans un commentaire à l'op - quelle est votre raison et le but de vouloir cette fonctionnalité?

Je ne connais pas beaucoup Ruby, comme cela a été mentionné par le PO, j'ai fait quelques recherches. Je vois que dans Ruby les classes sont vraiment un type d'objet spécial et que l'on peut créer (même dynamiquement) de nouvelles méthodes. Les classes sont des objets de classe complète dans Ruby, elles ne sont pas en Java. C'est juste quelque chose que vous devrez accepter en travaillant avec Java (ou C #). Ce ne sont pas des langages dynamiques, bien que C # ajoute des formes de dynamique. En réalité, Ruby n'a pas de méthodes "statiques" pour autant que je puisse trouver - dans ce cas, ce sont des méthodes sur l'objet de classe singleton. Vous pouvez ensuite remplacer ce singleton par une nouvelle classe et les méthodes de l'objet de classe précédent appelleront celles définies dans la nouvelle classe (correct?). Ainsi, si vous appeliez une méthode dans le contexte de la classe d'origine, elle ne ferait qu'exécuter la statique d'origine, mais en appelant une méthode dans la classe dérivée, elle appellerait des méthodes du parent ou de la sous-classe. Intéressant et je peux voir une certaine valeur dans cela. Cela prend un schéma de pensée différent.

Puisque vous travaillez en Java, vous devrez vous adapter à cette façon de faire les choses. Pourquoi ils ont fait ça? Eh bien, probablement pour améliorer la performance à l'époque en fonction de la technologie et de la compréhension qui était disponible. Les langages informatiques évoluent constamment. Revenez assez loin et il n'y a pas de POO. À l'avenir, il y aura d'autres nouvelles idées.

MODIFIER: Un autre commentaire. Maintenant que je vois les différences et que je suis moi-même développeur Java / C #, je peux comprendre pourquoi les réponses des développeurs Java peuvent être déroutantes si vous venez d'un langage comme Ruby. Java staticles méthodes ne sont pas les mêmes que Ruby class méthodes Les développeurs Java auront du mal à comprendre cela, tout comme ceux qui travaillent principalement avec un langage comme Ruby / Smalltalk. Je peux voir comment cela serait aussi déroutant par le fait que Java utilise également la "méthode de classe" comme un autre moyen de parler de méthodes statiques, mais ce même terme est utilisé différemment par Ruby. Java n'a pas de méthodes de classe de style Ruby (désolé); Ruby n'a pas de méthodes statiques de style Java qui ne sont en réalité que de vieilles fonctions de style procédural, comme dans C.

Au fait, merci pour la question! J'ai appris quelque chose de nouveau pour moi aujourd'hui sur les méthodes de classe (style Ruby).


5
2018-03-04 15:33



Eh bien ... la réponse est NON si vous pensez du point de vue de comment une méthode surchargée devrait se comporter en Java. Mais vous n'obtenez aucune erreur de compilation si vous essayez de remplacer une méthode statique. Cela signifie que si vous essayez de passer outre, Java ne vous empêche pas de le faire; mais vous n'obtenez certainement pas le même effet que pour les méthodes non statiques. Remplacer dans Java signifie simplement que la méthode particulière serait appelée en fonction du type d'exécution de l'objet et non du type de compilation de celui-ci (ce qui est le cas avec les méthodes statiques remplacées). Ok ... toutes les suppositions pour la raison pour laquelle ils se comportent bizarrement? Parce qu'ils sont des méthodes de classe et donc l'accès à eux est toujours résolu pendant la compilation en utilisant uniquement les informations de type heure de compilation. Leur accès à l'aide de références d'objet est juste une liberté supplémentaire donnée par les concepteurs de Java et nous ne devrions certainement pas penser à arrêter cette pratique uniquement lorsqu'ils la restreignent :-)

Exemple: essayons de voir ce qui se passe si nous essayons de remplacer une méthode statique: -

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();
    // ...
}
}

Sortie: -
SuperClass: inside staticMethod 
SuperClass: inside staticMethod 
SubClass: inside staticMethod

Notez la deuxième ligne de la sortie. Si la méthode staticMethod avait été remplacée, cette ligne aurait dû être identique à la troisième ligne car nous invoquions le paramètre 'staticMethod ()' sur un objet de type runtime comme 'SubClass' et non comme 'SuperClass'. Cela confirme que les méthodes statiques sont toujours résolues en utilisant uniquement leurs informations de type heure de compilation.


5
2018-01-14 11:28