Perché non dovremmo usare la statica protetta in java


122

Stavo affrontando questa domanda C'è un modo per sovrascrivere le variabili di classe in Java? Il primo commento con 36 voti positivi è stato:

Se mai vedrai un protected static, corri.

Qualcuno può spiegare perché è protected staticdisapprovato?


6
Non c'è niente di sbagliato in un campo statico protetto, purché lo sia final. Un campo statico mutevole condiviso tra le classi è sicuramente motivo di preoccupazione. È improbabile che più classi che aggiornano un campo statico siano affidabili o facili da seguire, soprattutto perché la presenza di qualsiasi campo o metodo protetto implica che la classe è destinata ad essere estesa da classi in altri pacchetti, possibilmente classi non sotto il controllo del autore della classe contenente il campo protetto.
VGR

6
@ VGR, finalnon significa che il campo sia immutabile. È sempre possibile modificare l' objectelemento a cui fa finalriferimento una variabile di riferimento.
Zeeshan

@ VGR Non sono d'accordo. L'UNICO motivo per cui creeresti una variabile statica è per avervi accesso dall'interno di un altro pacchetto solo per eredità, e l'accesso a un singolo campo NON dovrebbe essere il motivo dell'ereditarietà. È un design imperfetto, IMO, e se ricorri a questo, dovresti probabilmente ripensare alla struttura della tua applicazione. Questa però è solo la mia opinione.
Dioxin

@LoneRider Hai ragione. Pensavo immutabile e il finale di certo non lo garantisce.
VGR

Anche io sono venuto qui dalla stessa domanda.
Raj Rajeshwar Singh Rathore

Risposte:


86

È più una cosa stilistica che un problema diretto. Suggerisce che non hai riflettuto adeguatamente su cosa sta succedendo con la classe.

Pensa a cosa staticsignifica:

Questa variabile esiste a livello di classe, non esiste separatamente per ogni istanza e non ha un'esistenza indipendente nelle classi che mi estendono .

Pensa a cosa protectedsignifica:

Questa variabile può essere vista da questa classe, classi nello stesso pacchetto e classi che mi estendono .

I due significati non si escludono esattamente a vicenda, ma sono piuttosto vicini.

L'unico caso che posso vedere dove potresti usare i due insieme è se avessi una classe astratta che è stata progettata per essere estesa e la classe che si estende potrebbe quindi modificare il comportamento utilizzando le costanti definite nell'originale. Questo tipo di accordo molto probabilmente finirebbe per essere molto disordinato e indica debolezza nel design delle classi.

Nella maggior parte dei casi sarebbe meglio avere le costanti come pubbliche poiché ciò rende tutto più pulito e consente alle persone di sottoclassare una maggiore flessibilità. A prescindere da qualsiasi altra cosa, in molti casi la composizione è preferibile all'ereditarietà, mentre le classi astratte forzano l'ereditarietà.

Per vedere un esempio di come questo potrebbe rompere le cose e per illustrare cosa intendo per la variabile che non ha un'esistenza indipendente, prova questo codice di esempio:

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

Vedrai i risultati:

test
changed

Provalo tu stesso su: https://ideone.com/KM8u8O

La classe Test2è in grado di accedere al membro statico testda Testsenza la necessità di qualificare il nome - ma non eredita o ottenere la propria copia. Sta guardando lo stesso identico oggetto nella memoria.


4
Ragazzi, siete legati all'eredità. Il caso tipico in cui si vede questo è qualcuno che vuole l'accesso al pacchetto senza renderlo pubblico (es. Uno unit test).
spudone

3
@spudone ma gli unit test sono generalmente inseriti nello stesso pacchetto. Per dare loro l'accesso basta usare il livello di accesso predefinito (pacchetto). Protetto dà accesso anche a sottoclassi che non sono richieste o rilevanti per lo unit test.
Tim B

1
@Kamafeather Protected significa che i bambini possono vederti da classi diverse e tutte le classi nello stesso pacchetto possono vederti. Quindi sì, il programma è nello stesso pacchetto e quindi può vedere il membro protetto.
Tim B

2
" la classe di estensione potrebbe quindi modificare il comportamento " Ciò violerebbe il principio di sottoscrizione di Liskov. Una sottoclasse non dovrebbe modificare il comportamento del suo supertipo. Questo rientra nell'argomento "un quadrato non è un rettangolo": regolare la superclasse ( Rectangle) per regolare larghezza / altezza per garantire l'uguaglianza (per Square) produrrà risultati indesiderati se dovessi sostituire ogni supertipo con un'istanza del suo sottotipo.
Dioxin

1
" L'unico caso che posso vedere dove potresti usare i due insieme è se avessi una classe astratta progettata per essere estesa e la classe estendibile potrebbe quindi modificare il comportamento utilizzando le costanti definite nell'originale " Un po 'nascosto, ma è in Là. L'esempio di codice esprime anche l'affermazione
Dioxin

32

È disapprovato perché è contraddittorio.

La creazione di una variabile protectedimplica che verrà utilizzata all'interno del pacchetto o verrà ereditata all'interno di una sottoclasse .

Rendere la variabile la staticrende un membro della classe, eliminando le intenzioni di ereditarla . Questo lascia solo l'intenzione di essere utilizzato all'interno di un pacchetto , e abbiamo package-privateper questo (nessun modificatore).

L'unica situazione per cui potrei trovarlo utile è se stavi dichiarando una classe che dovrebbe essere usata per lanciare l'applicazione (come JavaFX Application#launch, e volessi solo essere in grado di lanciarlo da una sottoclasse. In tal caso, assicurati che il metodo sia anche finalquello di non consentire l' occultamento, ma questa non è "la norma" ed è stata probabilmente implementata per evitare di aggiungere ulteriore complessità aggiungendo un nuovo modo di avviare le applicazioni.

Per vedere i livelli di accesso di ogni modificatore, vedere questo: Tutorial Java - Controllo dell'accesso ai membri di una classe


4
Non capisco come staticeliminerebbe le intenzioni di ereditarlo. Perché la mia sottoclasse in un altro pacchetto richiede ancora l'accesso al campo super protected, anche se lo è static. package-privatenon può aiutare
Aobo Yang

@AoboYang Hai ragione, ecco perché alcune persone usano protected static. Ma è un odore di codice, da qui la parte " run ". I modificatori di accesso e l'ereditarietà sono due soggetti diversi. Sì, non sarai in grado di accedere a un membro statico da una super classe se lo fosse package-private. Ma non dovresti fare affidamento sull'ereditarietà per fare riferimento ai staticcampi in primo luogo; è un segno di cattivo design. Noterai che i tentativi di sovrascrivere i staticmetodi non danno risultati, il che è un chiaro segno che l'ereditarietà non è basata sulla classe. Se hai bisogno di accedere al di fuori della classe o del pacchetto, dovrebbe esserepublic
Dioxin

3
All'inizio ho una classe con alcune private staticfunzioni di utilità, ma penso che qualcuno potrebbe voler migliorare o personalizzare dalla mia classe e queste funzioni di utilità possono anche fornire loro comodità. publicpotrebbe non essere appropriato poiché i metodi util non sono per l'utente delle istanze della mia classe. Potresti aiutarmi a capire un buon design invece di protected? Grazie
Aobo Yang

In quel collegamento, dice 'Usa il livello di accesso più restrittivo che ha senso per un particolare membro. Usa privato a meno che tu non abbia una buona ragione per non farlo. ' , che è contraddittorio da questa domanda, qualche pensiero?
Cecilia

13

Non vedo un motivo particolare per cui questo dovrebbe essere disapprovato. Ci possono sempre essere alternative per ottenere lo stesso comportamento, e dipenderà dall'architettura attuale se queste alternative sono "migliori" di un metodo statico protetto o meno. Ma un esempio in cui un metodo statico protetto sarebbe ragionevole, almeno, potrebbe essere il seguente:

(Modificato per dividere in pacchetti separati, per rendere protectedpiù chiaro l'uso di )

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

Derivato da quello:

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

Un'altra classe derivata:

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

Il protected staticmodificatore può certamente essere giustificato qui:

  • I metodi possono essere static, perché non dipendono dalle variabili di istanza. Non sono destinati ad essere utilizzati direttamente come un metodo polimorfico, ma piuttosto sono metodi di "utilità" che offrono implementazioni predefinite che fanno parte di un calcolo più complesso e servono come "blocchi di costruzione" dell'attuale implementazione.
  • I metodi non dovrebbero esserlo public, perché sono un dettaglio di implementazione. E non possono essere privateperché dovrebbero essere chiamati dalle classi estendenti. Inoltre non possono avere visibilità "predefinita", perché in tal caso non saranno accessibili per le classi estendibili in altri pacchetti.

(EDIT: si potrebbe presumere che il commento originale si riferisse solo ai campi e non ai metodi - quindi, tuttavia, era troppo generale)


In questo caso, dovresti definire le implementazioni predefinite come protected final(dato che non vuoi che vengano sovrascritte), no static. Il fatto che un metodo non utilizzi variabili di istanza non significa che dovrebbe esserlo static(sebbene possa farlo ).
barfuin

2
@Thomas Certo, in questo caso, anche questo sarebbe possibile. In generale: questo è certamente parzialmente soggettivo, ma la mia regola pratica è: quando un metodo non è concepito per essere utilizzato polimorficamente e può essere statico, lo rendo statico. In contrasto rendendolo final, non solo rende chiaro che il metodo non è destinato ad essere sovrascritta, esso inoltre rende chiaro al lettore che il metodo non utilizza variabili di istanza. Così succintamente: semplicemente non c'è motivo per non renderlo statico.
Marco13

1
Quando un metodo non è concepito per essere utilizzato polimorficamente e può essere statico, lo rendo statico. - Questo ti metterà nei guai quando inizi a utilizzare framework fittizi per i test di unità. Ma questo ci porta a un argomento diverso ...
barfuin

@ Marco13 c'è anche il caso in cui fai qualcosa protected, non per mostrare l'eredità, ma per tenerla sotto un unico pacchetto - non esposto. Immagino che le interfacce sigillate diventerebbero utili in questo modo quando lo faranno in java
Eugene

@Eugene Quel commento mi irrita un po '. La visibilità del pacchetto può essere ottenuta senza alcun modificatore, mentre protectedsignifica "Ereditare classi (anche in pacchetti diversi)" ...
Marco13

7

I membri statici non vengono ereditati ei membri protetti sono visibili solo alle sottoclassi (e ovviamente alla classe contenente), quindi a protected staticha la stessa visibilità di static, suggerendo un malinteso da parte del programmatore.


1
Potresti fornire una fonte per la tua prima dichiarazione? In un rapido test, un int statico protetto è stato ereditato e riutilizzato in una sottoclasse senza problemi.
hiergiltdiestfu

Protected static ha la stessa visibilità di package-private static, non private static. Per aggiungere a questo, se stai usando protetto e statico, è meglio rimuovere semplicemente il modificatore di accesso per renderlo privato del pacchetto (se le tue intenzioni fossero di renderlo accessibile all'interno del pacchetto)
Dioxin

2
Uhm ... no. pacchetto privato e protectednon sono la stessa cosa. Se dici solo static, il campo è visibile solo alle sottoclassi nello stesso pacchetto.
Aaron Digulla

1
@AaronDigulla protected staticconsente l'accesso all'interno del pacchetto o da una sottoclasse . Rendere statica una variabile rimuove le intenzioni di sottoclassi, lasciando l'unica intenzione di accedere dall'interno del pacchetto.
Dioxin

@VinceEmigh: Scusa, stavo parlando con Bohemian. Avrebbe dovuto renderlo più chiaro.
Aaron Digulla

5

In realtà non c'è niente di fondamentalmente sbagliato protected static. Se vuoi davvero una variabile statica o un metodo che sia visibile per il pacchetto e tutte le sottoclassi della classe dichiarante, allora vai avanti e fallo protected static.

Alcune persone generalmente evitano di utilizzare protectedper vari motivi e alcune persone pensano che le staticvariabili non finali dovrebbero essere evitate con tutti i mezzi (personalmente simpatizzo con quest'ultima in una certa misura), quindi immagino che la combinazione di protectede staticdebba sembrare brutta ^ 2 a quelle che appartengono a entrambi i gruppi.


3

Protetto viene utilizzato in modo che possa essere utilizzato nelle sottoclassi. Non c'è logica nella definizione di una statica protetta quando si utilizza nel contesto di classi concrete poiché è possibile accedere alla stessa variabile in modo statico, tuttavia il compilatore darà un avviso per accedere alla variabile statica della super classe in modo statico.


1

Bene, come la maggior parte delle persone ha risposto:

  • protectedsignifica - ' pacchetto-privato + visibilità alle sottoclassi - la proprietà / comportamento è INERITATO '
  • staticsignifica - ' l'opposto dell'istanza - è una proprietà / comportamento CLASS, cioè NON È EREDITATO '

Pertanto sono leggermente contraddittori e incompatibili.

Tuttavia, di recente sono arrivato a un caso d'uso in cui potrebbe avere senso usare questi due insieme. Immagina di voler creare una abstractclasse che è un genitore per i tipi immutabili e ha un mucchio di proprietà che sono comuni ai sottotipi. Per implementare correttamente l' immutabilità e mantenere la leggibilità si potrebbe decidere di utilizzare il pattern 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;
    }
}

E perché protected static? Perché voglio un sottotipo non astratto AbstactTypeche implementi il ​​proprio Builder non astratto e si trovi all'esterno package Xper poter accedere e riutilizzare il file 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;
    }
}

Ovviamente possiamo renderlo BaseBuilderpubblico ma poi arriviamo ad un'altra affermazione contraddittoria:

  • Abbiamo una classe non istantanea (abstract)
  • Forniamo un costruttore pubblico per questo

Quindi in entrambi i casi con protected statice publiccostruttore di unabstract class combiniamo affermazioni contraddittorie. È una questione di preferenze personali.

Tuttavia, preferisco ancora il publicbuilder di unabstract class perché protected staticper me sembra più innaturale in un mondo OOD e OOP!


0

Non c'è niente di sbagliato nell'avere protected static. Una cosa che molte persone trascurano è che potresti voler scrivere casi di test per metodi statici che non vuoi esporre in circostanze normali. Ho notato che questo è particolarmente utile per scrivere test per il metodo statico nelle classi di utilità.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.