Dlaczego nie powinniśmy używać chronionych plików statycznych w java

122

Przechodziłem przez to pytanie Czy istnieje sposób na przesłonięcie zmiennych klas w Javie? Pierwszy komentarz z 36 głosami za:

Jeśli kiedykolwiek zobaczysz protected static, uciekaj.

Czy ktoś może wyjaśnić, dlaczego się nie protected staticzgadza?

Zeeshan
źródło
6
Nie ma nic złego w chronionym polu statycznym, o ile jest final. Zmienne pole statyczne współdzielone między klasami jest zdecydowanie powodem do zmartwień. Wiele klas aktualizujących pole statyczne prawdopodobnie nie będzie niezawodne lub łatwe do naśladowania, zwłaszcza że obecność jakiegokolwiek chronionego pola lub metody oznacza, że ​​klasa ma zostać rozszerzona o klasy w innych pakietach, być może klasy nie będące pod kontrolą autor klasy zawierającej chronione pole.
VGR
6
@VGR, finalnie oznacza, że ​​pole jest niezmienne. Zawsze możesz zmodyfikować objectodwołanie za pomocą finalzmiennej odniesienia.
Zeeshan
@VGR Nie zgadzam się. JEDYNYM powodem, dla którego tworzysz zmienną statyczną, jest dostęp do niej z innego pakietu tylko przez dziedziczenie, a dostęp do pojedynczego pola NIE powinien być powodem dziedziczenia. To wadliwy projekt, IMO i jeśli się do tego zastosujesz, prawdopodobnie powinieneś przemyśleć strukturę swojej aplikacji. To tylko moja opinia.
Dioxin
@LoneRider Masz rację. Myślałem, że jest niezmienny, a finał z pewnością tego nie gwarantuje.
VGR
Nawet ja przyszedłem tutaj z tego samego pytania.
Raj Rajeshwar Singh Rathore

Odpowiedzi:

86

To bardziej kwestia stylistyczna niż bezpośredni problem. Sugeruje, że nie przemyślałeś właściwie tego, co dzieje się na zajęciach.

Zastanów się, co to staticznaczy:

Ta zmienna istnieje na poziomie klasy, nie istnieje oddzielnie dla każdej instancji i nie istnieje niezależnie w klasach, które mnie rozszerzają .

Zastanów się, co to protectedznaczy:

Tę zmienną można zobaczyć przez tę klasę, klasy w tym samym pakiecie i klasy, które mnie rozszerzają .

Te dwa znaczenia nie wykluczają się wzajemnie, ale są dość bliskie.

Jedynym przypadkiem, w którym mogę zobaczyć, gdzie możesz użyć tych dwóch razem, jest to, że masz klasę abstrakcyjną, która została zaprojektowana do rozszerzania, a klasa rozszerzająca mogłaby następnie zmodyfikować zachowanie za pomocą stałych zdefiniowanych w oryginale. Taki układ najprawdopodobniej skończyłby się jednak bardzo niechlujnie i wskazuje na słabość w projekcie zajęć.

W większości przypadków lepiej byłoby mieć stałe jako publiczne, ponieważ to po prostu czyni wszystko czystszym i pozwala ludziom na tworzenie podklas na większą elastyczność. Zupełnie niezależnie od wszystkiego innego, w wielu przypadkach kompozycja jest lepsza niż dziedziczenie, podczas gdy klasy abstrakcyjne wymuszają dziedziczenie.

Aby zobaczyć jeden przykład, jak to może zepsuć rzeczy i zilustrować, co mam na myśli mówiąc, że zmienna nie ma niezależnego istnienia, spróbuj tego przykładowego kodu:

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

Zobaczysz wyniki:

test
changed

Wypróbuj sam na: https://ideone.com/KM8u8O

Klasa Test2jest w stanie uzyskać dostęp do elementu statycznego testod Testbez konieczności zakwalifikowania nazwy - ale nie dziedziczy albo dostać własną kopię. Patrzy dokładnie na ten sam obiekt w pamięci.

Tim B.
źródło
4
Jesteście uzależnieni od dziedziczenia. Typowym przypadkiem, w którym jest to widoczne, jest chęć dostępu do pakietu bez upublicznienia go (np. Test jednostkowy).
spudone
3
@spudone, ale testy jednostkowe są zwykle umieszczane w tym samym pakiecie. Aby dać im dostęp, wystarczy użyć domyślnego (pakietu) poziomu dostępu. Protected daje również dostęp do podklas, które nie są wymagane ani istotne dla testu jednostkowego.
Tim B
1
@Kamafeather Protected oznacza, że ​​dzieci mogą Cię widzieć z różnych klas, a wszystkie klasy z tego samego pakietu mogą Cię widzieć. Więc tak Program znajduje się w tym samym pakiecie i dlatego może zobaczyć chronionego członka.
Tim B
2
klasa rozszerzająca mogłaby wówczas zmodyfikować zachowanie ”. Naruszyłoby to zasadę Substytucji Liskova. Podklasa nie powinna modyfikować zachowania swojego nadtypu. Odpowiada to całemu argumentowi „kwadrat nie jest prostokątem”: dostosowanie nadklasy ( Rectangle) w celu dostosowania szerokości / wysokości w celu zapewnienia równości (for Square) przyniesie niepożądane rezultaty, jeśli każdy nadtyp zostanie zastąpiony instancją jego podtypu.
Dioxin
1
Jedynym przypadkiem, w którym widzę, gdzie możesz użyć tych dwóch razem, jest to, że masz klasę abstrakcyjną, która została zaprojektowana do rozszerzania, a klasa rozszerzająca mogłaby następnie zmodyfikować zachowanie przy użyciu stałych zdefiniowanych w oryginale ” Trochę ukryty, ale jest w tam. Przykład kodu również wyraża stwierdzenie
Dioxin
32

Jest źle widziany, ponieważ jest sprzeczny.

Utworzenie zmiennej protectedoznacza, że ​​będzie ona używana w pakiecie lub dziedziczona w ramach podklasy .

Uczynienie zmiennej staticczyni ją członkiem klasy, eliminując zamiary jej dziedziczenia . Pozostawia to tylko zamiar użycia w pakiecie i mamy package-privatedo tego (bez modyfikatora).

Jedyna sytuacja może znajdę to przydatna jest, jeśli zostały deklarując klasę, która powinna być użyta do uruchomienia aplikacji (jak JavaFX użytkownika Application#launch, a jedynie chciał móc uruchomić z podklasy. Jeśli ten sposób upewnić się, że metoda jest również finaldo nie zezwalaj na ukrywanie . Ale to nie jest „norma” i prawdopodobnie zostało zaimplementowane, aby zapobiec zwiększaniu złożoności poprzez dodanie nowego sposobu uruchamiania aplikacji.

Aby zobaczyć poziomy dostępu każdego modyfikatora, zobacz: Samouczki języka Java - kontrolowanie dostępu do elementów członkowskich klasy

Dioksyna
źródło
4
Nie rozumiem, jak staticwyeliminowałoby to zamiary dziedziczenia. Ponieważ moja podklasa w innym pakiecie nadal wymaga pola super, aby uzyskać protecteddostęp, mimo że jest static. package-privatenie mogę pomóc
Aobo Yang
@AoboYang Masz rację, dlatego niektórzy używają protected static. Ale to zapach kodu, stąd część „ uruchom ”. Modyfikatory dostępu i dziedziczenie to dwa różne tematy. Tak, nie będziesz mógł uzyskać dostępu do statycznego elementu członkowskiego z superklasy, jeśli był package-private. Ale staticprzede wszystkim nie powinieneś polegać na dziedziczeniu w odniesieniu do pól referencyjnych ; to oznaka złego projektu. Zauważysz, że próby zastąpienia staticmetod nie dają żadnych wyników, co jest wyraźnym znakiem, że dziedziczenie nie jest oparte na klasach. Jeśli potrzebujesz dostępu poza zajęciami lub pakietem, powinno to byćpublic
Dioxin
3
Na początku mam klasę z niektórymi private staticfunkcjami użytkowymi, ale myślę, że ktoś może chcieć ulepszyć lub dostosować z mojej klasy, a te funkcje użytkowe mogą im również zapewnić wygodę. publicmoże nie być odpowiednie, ponieważ metody util nie są przeznaczone dla użytkownika instancji mojej klasy. Czy możesz mi pomóc zamiast tego wymyślić dobry projekt protected? Dzięki
Aobo Yang
W tym linku jest napisane „Użyj najbardziej restrykcyjnego poziomu dostępu, który ma sens dla konkretnego członka. Używaj prywatnego, chyba że masz dobry powód, by tego nie robić. , co jest sprzeczne z tym pytaniem, jakieś przemyślenia?
Cecilia
13

Nie widzę konkretnego powodu, dla którego należałoby się na to źle patrzeć. Zawsze mogą istnieć alternatywy umożliwiające osiągnięcie tego samego zachowania i od aktualnej architektury będzie zależeć, czy te alternatywy są „lepsze” niż chroniona metoda statyczna, czy nie. Ale jeden przykład, w którym chroniona metoda statyczna byłaby rozsądna, może być przynajmniej następujący:

(Zredagowane w celu podzielenia na oddzielne pakiety, aby użyć protectedjaśniejszego)

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

Wywodzi się z tego:

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

Inna klasa pochodna:

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

protected staticModyfikator może być z pewnością uzasadnione tutaj:

  • Metody mogą być static, ponieważ nie zależą od zmiennych instancji. Nie są przeznaczone do bezpośredniego stosowania jako metoda polimorficzna, ale są raczej metodami „użytkowymi” oferującymi domyślne implementacje które są częścią bardziej złożonych obliczeń i służą jako „bloki konstrukcyjne” rzeczywistej implementacji.
  • Metody nie powinny być public, ponieważ są szczegółem implementacji. I nie mogą być, privateponieważ powinny być wywoływane przez klasy rozszerzające. Nie mogą też mieć widoczności „domyślnej”, ponieważ wtedy nie będą dostępne dla klas rozszerzających w innych pakietach.

(EDYCJA: można by przypuszczać, że pierwotny komentarz dotyczył tylko pól , a nie metod - wtedy był jednak zbyt ogólny)

Marco13
źródło
W takim przypadku powinieneś zdefiniować domyślne implementacje jako protected final(ponieważ nie chcesz, aby były zastępowane), a nie static. Fakt, że metoda nie używa zmiennych instancji, nie oznacza, że ​​tak powinno być static(chociaż może ).
barfuin
2
@Thomas Jasne, w tym przypadku byłoby to również możliwe. Ogólnie: jest to z pewnością częściowo subiektywne, ale moja praktyczna zasada brzmi: kiedy metoda nie jest przeznaczona do polimorficznego stosowania i może być statyczna, wówczas ustawiam ją jako statyczną. W przeciwieństwie do tego final, nie tylko wyjaśnia, że ​​metoda nie ma być nadpisywana, ale dodatkowo wyjaśnia czytelnikowi, że metoda nie używa zmiennych instancji. Tak zwięźle: po prostu nie ma powodu, aby nie robić tego statycznie.
Marco13
1
Kiedy metoda nie jest przeznaczona do stosowania polimorficznie i może być statyczna, wówczas ustawiam ją jako statyczną. - Spowoduje to kłopoty, gdy zaczniesz używać fałszywych frameworków do testowania jednostkowego. Ale to prowadzi nas do innego tematu ...
barfuin
@ Marco13 jest również przypadek, kiedy coś robisz protected, nie po to, by pokazać dziedziczenie, ale by zachować to w jednym pakiecie - nie ujawnianym. Myślę, że zapieczętowane interfejsy staną się pomocne w ten sposób, gdy będą w Javie
Eugene,
@Eugene Ten komentarz trochę mnie irytuje. Widoczność pakietu można osiągnąć bez żadnego modyfikatora, podczas gdy protectedoznacza „Dziedziczenie klas (nawet w różnych pakietach)” ...
Marco13
7

Statyczne elementy członkowskie nie są dziedziczone, a chronione elementy są widoczne tylko dla podklas (i oczywiście dla klasy zawierającej), więc protected staticma taką samą widoczność jak static, co sugeruje nieporozumienie ze strony kodera.

Czeski
źródło
1
Czy mógłbyś podać źródło swojego pierwszego stwierdzenia? W szybkim teście chronione static int zostało bez problemu odziedziczone i ponownie użyte w podklasie.
hiergiltdiestfu
Chroniony statyczny ma taką samą widoczność jak prywatny statyczny pakiet, a nie prywatny statyczny. Aby dodać do tego, jeśli używasz chronionego i statycznego, najlepiej po prostu usunąć modyfikator dostępu, aby uczynić go prywatnym pakietem (jeśli zamierzałeś uczynić go dostępnym w pakiecie)
Dioxin
2
Hm ... nie. pakiet prywatny i protectednie są takie same. Jeśli powiesz static, pole jest widoczne tylko dla podklas w tym samym pakiecie.
Aaron Digulla
1
@AaronDigulla protected staticumożliwia dostęp w ramach pakietu lub z podklasy . Utworzenie zmiennej statycznej usuwa zamiar tworzenia podklas, pozostawiając jedynym celem jest dostęp z poziomu pakietu.
Dioxin
@VinceEmigh: Przepraszam, rozmawiałem z Bohemian. Powinienem był to wyjaśnić.
Aaron Digulla
5

Właściwie nie ma w tym zasadniczo nic złego protected static. Jeśli naprawdę potrzebujesz statycznej zmiennej lub metody, która jest widoczna dla pakietu i wszystkich podklas deklarującej klasy, to zrób to protected static.

Niektórzy ludzie na ogół unikają używania protectedz różnych powodów, a niektórzy uważają, że staticza wszelką cenę należy unikać zmiennych nieokreślonych (osobiście do pewnego stopnia sympatyzuję z tymi ostatnimi), więc myślę, że połączenie protectedi staticmusi wyglądać źle ^ 2 dla tych, które należą do obu grup.

x4u
źródło
3

Chroniony jest używany, więc może być używany w podklasach. Nie ma żadnej logiki w definiowaniu chronionej statycznej wartości statycznej, gdy jest ona używana w kontekście konkretnych klas, ponieważ dostęp do tej samej zmiennej jest statyczny, jednak mechanizm zgodności wyświetli ostrzeżenie, aby uzyskać dostęp do statycznej zmiennej superklasy w sposób statyczny.

Mithun Kannoth
źródło
1

Cóż, jak odpowiedziała większość ludzi:

  • protected znaczy - ' pakiet-prywatny + widoczność do podklas - właściwość / zachowanie jest dziedziczone '
  • static znaczy - ' przeciwieństwo instancji - jest to właściwość / zachowanie KLASY, tj. NIE JEST DZIEDZICZONY

Dlatego są nieco sprzeczne i niekompatybilne.

Jednak ostatnio doszedłem do przypadku użycia, w którym sensowne może być użycie tych dwóch razem. Wyobraź sobie, że chcesz utworzyć abstractklasę, która jest rodzicem dla typów niezmiennych i ma kilka właściwości wspólnych dla podtypów. Aby poprawnie zaimplementować niezmienność i zachować czytelność, można zdecydować się na użycie wzorca 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;
    }
}

A dlaczego protected static? Ponieważ chcę nieabstrakcyjnego podtypu, AbstactTypektóry implementuje własny nieabstrakcyjny Konstruktor i znajduje się na zewnątrz, package Xaby móc uzyskać dostęp do pliku 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;
    }
}

Oczywiście możemy podać do BaseBuilderwiadomości publicznej, ale potem dochodzimy do kolejnych sprzecznych stwierdzeń:

  • Mamy klasę, której nie można utworzyć instancji (streszczenie)
  • Zapewniamy do tego publicznego budowniczego

Tak więc w obu przypadkach protected statici publicbudowniczymabstract class łączymy sprzeczne oświadczenia. To kwestia osobistych preferencji.

Jednak nadal wolę publickonstruktora,abstract class ponieważ protected staticwydaje mi się bardziej nienaturalny w świecie OOD i OOP!

egelev
źródło
0

Nie ma nic złego w posiadaniu protected static. Wiele osób przeoczyło jedną rzecz, ponieważ możesz chcieć napisać przypadki testowe dla metod statycznych, których nie chcesz ujawniać w normalnych okolicznościach. Zauważyłem, że jest to szczególnie przydatne do pisania testów dla metody statycznej w klasach narzędziowych.

Aelphaeis
źródło