Pourquoi nous ne devrions pas utiliser de statique protégé en Java

122

J'étais en train de passer en revue cette question. Existe-t-il un moyen de remplacer les variables de classe en Java? Le premier commentaire avec 36 votes positifs était:

Si jamais vous voyez un protected static, courez.

Quelqu'un peut-il expliquer pourquoi un est protected staticmal vu?

Zeeshan
la source
6
Il n'y a rien de mal avec un champ statique protégé, tant que c'est final. Un champ statique mutable partagé entre les classes est certainement un sujet de préoccupation. Plusieurs classes mettant à jour un champ statique ne seront probablement pas fiables ou faciles à suivre, d'autant plus que la présence de tout champ ou méthode protégé implique que la classe est censée être étendue par des classes dans d'autres packages, éventuellement des classes non sous le contrôle du auteur de la classe contenant le champ protégé.
VGR
6
@VGR, finalne signifie pas que le champ est immuable. Vous pouvez toujours modifier le objectréférencé par une finalvariable de référence.
Zeeshan
@VGR Je ne suis pas d'accord. La SEULE raison pour laquelle vous créeriez une variable statique est d'y avoir accès depuis un autre package uniquement par héritage, et l'accès à un seul champ ne devrait PAS être la raison de l'héritage. C'est une conception imparfaite, IMO, et si vous y avez recours, vous devriez probablement repenser la structure de votre application. C'est juste mon avis.
Dioxin
@LoneRider Vous avez raison. Je pensais immuable, et le final ne le garantit certainement pas.
VGR
Même moi, je suis venu ici de la même question.
Raj Rajeshwar Singh Rathore

Réponses:

86

C'est plus un problème stylistique qu'un problème direct. Cela suggère que vous n'avez pas bien réfléchi à ce qui se passe avec la classe.

Pensez à ce que staticsignifie:

Cette variable existe au niveau de la classe, elle n'existe pas séparément pour chaque instance et elle n'a pas d'existence indépendante dans les classes qui me prolongent .

Pensez à ce que protectedsignifie:

Cette variable peut être vue par cette classe, les classes dans le même package et les classes qui me prolongent .

Les deux significations ne sont pas exactement mutuellement exclusives, mais elles sont assez proches.

Le seul cas que je peux voir où vous pourriez utiliser les deux ensemble est si vous aviez une classe abstraite qui a été conçue pour être étendue et que la classe d'extension pourrait alors modifier le comportement en utilisant les constantes définies dans l'original. Ce genre d'arrangement finirait probablement par être très compliqué et indique une faiblesse dans la conception des classes.

Dans la plupart des cas, il serait préférable que les constantes soient publiques, car cela rend tout plus propre et permet aux personnes de sous-classer plus de flexibilité. Indépendamment de toute autre chose, dans de nombreux cas, la composition est préférable à l'héritage, tandis que les classes abstraites forcent l'héritage.

Pour voir un exemple de la façon dont cela pourrait casser les choses et pour illustrer ce que je veux dire par la variable n'ayant pas d'existence indépendante, essayez cet exemple de code:

public class Program {
    public static void main (String[] args) throws java.lang.Exception {
        System.out.println(new Test2().getTest());
        Test.test = "changed";
        System.out.println(new Test2().getTest());
    }
}

abstract class Test {
    protected static String test = "test";
}

class Test2 extends Test {
    public String getTest() {
        return test;
    }
}

Vous verrez les résultats:

test
changed

Essayez-le vous-même sur: https://ideone.com/KM8u8O

La classe Test2peut accéder au membre statique à testpartir de Testsans avoir besoin de qualifier le nom - mais elle n'hérite ni n'obtient sa propre copie. Il regarde exactement le même objet en mémoire.

Tim B
la source
4
Vous êtes accro à l'héritage. Le cas typique où cela est vu est quelqu'un qui veut accéder au paquet sans le rendre public (par exemple un test unitaire).
spudone
3
@spudone mais les tests unitaires sont généralement placés dans le même package. Pour leur donner accès, utilisez simplement le niveau d'accès par défaut (package). Protected donne également accès aux sous-classes qui ne sont pas requises ou pertinentes pour les tests unitaires.
Tim B
1
@Kamafeather Protected signifie que les enfants peuvent vous voir à partir de différentes classes et que toutes les classes du même package peuvent vous voir. Donc oui, le programme est dans le même package et peut donc voir le membre protégé.
Tim B
2
" la classe qui s'étendait alors pourrait modifier le comportement " Cela violerait le principe de Subsitution de Liskov. Une sous-classe ne doit pas modifier le comportement de son supertype. Cela relève de tout l'argument "un carré n'est pas un rectangle": ajuster la superclasse ( Rectangle) pour ajuster la largeur / hauteur pour garantir l'égalité produira (pour Square) des résultats indésirables si vous remplaciez chaque supertype par une instance de son sous-type.
Dioxin
1
" Le seul cas où je peux voir où vous pourriez utiliser les deux ensemble est si vous aviez une classe abstraite qui a été conçue pour être étendue et que la classe d'extension pourrait alors modifier le comportement en utilisant les constantes définies dans l'original " Un peu caché, mais c'est dans Là. L'exemple de code exprime également la déclaration
Dioxin
32

C'est mal vu parce que c'est contradictoire.

La création d'une variable protectedimplique qu'elle sera utilisée dans le package ou qu'elle sera héritée dans une sous-classe .

Faire de la variable en staticfait un membre de la classe, éliminant les intentions d'en hériter . Cela ne laisse que l'intention d'être utilisé dans un package , et nous avons package-privatepour cela (pas de modificateur).

La seule situation pour laquelle je pourrais trouver cela utile est si vous déclariez une classe qui devrait être utilisée pour lancer l'application (comme celle de JavaFX Application#launch, et que vous vouliez uniquement pouvoir lancer à partir d'une sous-classe. Si vous le faites, assurez-vous que la méthode est également finalà disallow cacher . Mais ce n'est pas « la norme », et a probablement été mis en œuvre pour empêcher l' ajout d'une plus grande complexité en ajoutant une nouvelle façon de lancer des applications.

Pour voir les niveaux d'accès de chaque modificateur, voir ceci: Les didacticiels Java - Contrôle de l'accès aux membres d'une classe

Dioxine
la source
4
Je ne comprends pas comment staticéliminer les intentions d'en hériter. Parce que ma sous-classe dans un autre package nécessite toujours le champ de super pour être protectedaccessible, même si c'est static. package-privatene peut pas aider
Aobo Yang
@AoboYang Vous avez raison, c'est pourquoi certaines personnes utilisent protected static. Mais c'est une odeur de code, d'où la partie " run ". Les modificateurs d'accès et l'héritage sont deux sujets différents. Oui, vous ne pourrez pas accéder à un membre statique d'une super classe si c'était le cas package-private. Mais vous ne devez pas vous fier à l'héritage pour référencer les staticchamps en premier lieu; c'est un signe de mauvaise conception. Vous remarquerez que les tentatives de substitution de staticméthodes ne donnent aucun résultat, ce qui est un signe clair que l'héritage n'est pas basé sur une classe. Si vous avez besoin d'un accès en dehors de la classe ou du package, il devrait êtrepublic
Dioxin
3
J'ai une classe avec quelques private staticfonctions util au début, mais je pense que quelqu'un voudra peut-être améliorer ou personnaliser à partir de ma classe et ces fonctions util peuvent également leur être utiles. publicpeut ne pas convenir car les méthodes util ne sont pas destinées à l'utilisateur des instances de ma classe. Pourriez-vous m'aider à trouver un bon design au lieu de protected? Merci
Aobo Yang
Dans ce lien, il est dit «Utilisez le niveau d'accès le plus restrictif qui a du sens pour un membre particulier. Utilisez privé sauf si vous avez une bonne raison de ne pas le faire. , qui est en contradiction avec cette question, des pensées?
Cecilia
13

Je ne vois pas de raison particulière pour laquelle cela devrait être mal vu. Il peut toujours y avoir des alternatives pour obtenir le même comportement, et cela dépendra de l'architecture réelle si ces alternatives sont "meilleures" qu'une méthode statique protégée ou non. Mais un exemple où une méthode statique protégée serait raisonnable, au moins, pourrait être le suivant:

(Modifié pour être divisé en packages séparés, pour rendre l'utilisation de protectedplus claire)

package a;
import java.util.List;

public abstract class BaseClass
{
    public Integer compute(List<Integer> list)
    {
        return computeDefaultA(list)+computeDefaultB(list);
    }

    protected static Integer computeDefaultA(List<Integer> list)
    {
        return 12;
    }
    protected static Integer computeDefaultB(List<Integer> list)
    {
        return 34;
    }
}

Dérivé de cela:

package a.b;

import java.util.List;

import a.BaseClass;

abstract class ExtendingClassA extends BaseClass
{
    @Override
    public Integer compute(List<Integer> list)
    {
        return computeDefaultA(list)+computeOwnB(list);
    }

    private static Integer computeOwnB(List<Integer> list)
    {
        return 56;
    }
}

Une autre classe dérivée:

package a.b;

import java.util.List;

import a.BaseClass;

abstract class ExtendingClassB extends BaseClass
{
    @Override
    public Integer compute(List<Integer> list)
    {
        return computeOwnA(list)+computeDefaultB(list);
    }

    private static Integer computeOwnA(List<Integer> list)
    {
        return 78;
    }
}

Le protected staticmodificateur peut certainement être justifié ici:

  • Les méthodes peuvent l'être static, car elles ne dépendent pas des variables d'instance. Ils ne sont pas destinés à être utilisés directement comme une méthode polymorphe, mais sont plutôt des méthodes «utilitaires» qui offrent des implémentations par défaut qui font partie d'un calcul plus complexe et servent de «blocs de construction» de l'implémentation réelle.
  • Les méthodes ne devraient pas l'être public, car elles sont un détail d'implémentation. Et ils ne peuvent pas l'être privatecar ils devraient être appelés par les classes étendues. Ils ne peuvent pas non plus avoir de visibilité "par défaut", car ils ne seront alors pas accessibles pour les classes d'extension dans d'autres packages.

(EDIT: on pourrait supposer que le commentaire original ne faisait référence qu'à des champs , et non à des méthodes - alors, cependant, c'était trop général)

Marco13
la source
Dans ce cas, vous devez définir les implémentations par défaut comme protected final(puisque vous ne voulez pas qu'elles soient remplacées), pas static. Le fait qu'une méthode n'utilise pas de variables d'instance ne signifie pas qu'elle devrait l'être static(bien qu'elle le puisse ).
barfuin
2
@Thomas Bien sûr, dans ce cas, cela serait également possible. En général: Ceci est certainement partiellement subjectif, mais ma règle de base est la suivante: lorsqu'une méthode n'est pas destinée à être utilisée de manière polymorphe, et qu'elle peut être statique, alors je la rends statique. Contrairement à ce qui en fait final, il ne fait pas seulement clair que la méthode ne vise pas à être surchargée, il en plus clairement au lecteur que la méthode n'utilise pas les variables d'instance. Si succinctement: il n'y a tout simplement aucune raison de ne pas le rendre statique.
Marco13
1
Lorsqu'une méthode n'est pas destinée à être utilisée de manière polymorphe et qu'elle peut être statique, alors je la rends statique. - Cela vous causera des problèmes lorsque vous commencerez à utiliser des frameworks simulés pour les tests unitaires. Mais cela nous amène à un sujet différent ...
barfuin
@ Marco13 il y a aussi le cas lorsque vous faites quelque chose protected, non pas pour montrer l'héritage, mais pour le garder sous un seul paquet - non exposé. Je suppose que les interfaces scellées deviendraient utiles de cette manière lorsqu'elles le seront en java
Eugene
@Eugene Ce commentaire m'irrite un peu. La visibilité des packages peut être obtenue sans aucun modificateur, alors que cela protectedsignifie "Hériter des classes (même dans des packages différents)" ...
Marco13
7

Les membres statiques ne sont pas hérités, et les membres protégés ne sont visibles que par les sous-classes (et bien sûr la classe contenant), donc a protected statica la même visibilité que static, suggérant un malentendu de la part du codeur.

Bohème
la source
1
Pourriez-vous s'il vous plaît fournir une source pour votre première déclaration? Dans un test rapide, un int statique protégé a été hérité et réutilisé dans une sous-classe sans problème.
hiergiltdiestfu
La statique protégée a la même visibilité que la statique privée du package, et non la statique privée. Pour ajouter à cela, si vous utilisez protected et static, il est préférable de simplement supprimer le modificateur d'accès pour le rendre package-private (si votre intention était de le rendre accessible dans le package)
Dioxin
2
Euh ... non. package privé et protectedne sont pas les mêmes. Si vous dites simplement static, le champ n'est visible que pour les sous-classes du même package.
Aaron Digulla
1
@AaronDigulla protected staticpermet l'accès au sein du package ou à partir d'une sous-classe . Rendre une variable statique supprime les intentions de sous-classification, laissant la seule intention étant l'accès depuis l'intérieur du package.
Dioxin
@VinceEmigh: Désolé, je parlais à Bohemian. J'aurais dû rendre cela plus clair.
Aaron Digulla
5

En fait, il n'y a rien de fondamentalement mal avec protected static. Si vous voulez vraiment une variable statique ou une méthode visible pour le package et toutes les sous-classes de la classe déclarante, allez-y et créez-la protected static.

Certaines personnes évitent généralement d'utiliser protectedpour diverses raisons et certaines personnes pensent que les staticvariables non finales devraient être évitées par tous les moyens (je sympathise personnellement avec ces dernières dans une certaine mesure), donc je suppose que la combinaison de protectedet staticdoit paraître mauvaise ^ 2 à ceux qui appartiennent aux deux groupes.

x4u
la source
3

Protected est utilisé pour pouvoir être utilisé dans les sous-classes. Il n'y a pas de logique dans la définition d'une statique protégée lors de l'utilisation dans le contexte de classes concrètes car vous pouvez accéder à la même variable de manière statique.Cependant, le complicateur donnera un avertissement pour accéder à la variable statique de la super classe de manière statique.

Mithun Kannoth
la source
1

Eh bien, comme la plupart des gens ont répondu:

  • protectedsignifie - ' package-private + visibilité aux sous-classes - la propriété / le comportement est HÉRITÉ '
  • staticsignifie - ' l'opposé de l'instance - c'est une propriété / un comportement de CLASSE, c'est-à-dire qu'elle n'est PAS HÉRITÉE '

Par conséquent, ils sont légèrement contradictoires et incompatibles.

Cependant, récemment, je suis venu à un cas d'utilisation où il pourrait être judicieux d'utiliser ces deux ensemble. Imaginez que vous vouliez créer une abstractclasse qui est un parent pour les types immuables et qui a un tas de propriétés qui sont communes aux sous-types. Pour implémenter correctement l' immuabilité et conserver la lisibilité, vous pouvez décider d'utiliser le modèle Builder .

package X;
public abstract class AbstractType {
    protected Object field1;
    protected Object field2;
    ...
    protected Object fieldN;

    protected static abstract class BaseBuilder<T extends BaseBuilder<T>> {
        private Object field1; // = some default value here
        private Object field2; // = some default value here
        ...
        private Object fieldN; // = some default value here

        public T field1(Object value) { this.field1 = value; return self();}
        public T field2(Object value) { this.field2 = value; return self();}
        ...
        public T fieldN(Object value) { this.fieldN = value; return self();}
        protected abstract T self(); // should always return this;
        public abstract AbstractType build();
    }

    private AbstractType(BaseBuilder<?> b) {
        this.field1 = b.field1;
        this.field2 = b.field2;
        ...
        this.fieldN = b.fieldN;
    }
}

Et pourquoi protected static? Parce que je veux un sous-type non abstrait AbstactTypequi implémente son propre constructeur non abstrait et est situé à l'extérieur package Xpour pouvoir accéder et réutiliser le BaseBuilder.

package Y;
public MyType1 extends AbstractType {
    private Object filedN1;

    public static class Builder extends AbstractType.BaseBuilder<Builder> {
        private Object fieldN1; // = some default value here

        public Builder fieldN1(Object value) { this.fieldN1 = value; return self();}
        @Override protected Builder self() { return this; }
        @Override public MyType build() { return new MyType(this); }
    }

    private MyType(Builder b) {
        super(b);
        this.fieldN1 = b.fieldN1;
    }
}

Bien sûr, nous pouvons rendre le BaseBuilderpublic mais ensuite nous arrivons à une autre déclaration contradictoire:

  • Nous avons une classe non instanciatable (abstraite)
  • Nous fournissons un constructeur public pour cela

Donc, dans les deux cas avec protected staticet publicconstructeur d'un,abstract class nous combinons des déclarations contradictoires. C'est une question de préférences personnelles.

Cependant, je préfère toujours le publicconstructeur d'unabstract class car cela protected staticme semble plus artificiel dans un monde OOD et OOP!

Egelev
la source
0

Il n'y a rien de mal à avoir protected static. Une chose que beaucoup de gens oublient est que vous voudrez peut-être écrire des cas de test pour des méthodes statiques que vous ne voulez pas exposer dans des circonstances normales. J'ai remarqué que cela est particulièrement utile pour écrire des tests de méthode statique dans des classes utilitaires.

Aelphaeis
la source