Dlaczego nie pola abstrakcyjne?

102

Dlaczego klasy Java nie mogą mieć pól abstrakcyjnych, tak jak mogą mieć metody abstrakcyjne?

Na przykład: Mam dwie klasy, które rozszerzają tę samą abstrakcyjną klasę bazową. Każda z tych dwóch klas ma metodę, która jest identyczna, z wyjątkiem stałej String, która jest komunikatem o błędzie. Gdyby pola mogły być abstrakcyjne, mógłbym uczynić tę stałą abstrakcją i przeciągnąć metodę do klasy bazowej. Zamiast tego muszę utworzyć metodę abstrakcyjną, wywoływaną getErrMsg()w tym przypadku, która zwraca String, przesłania tę metodę w dwóch klasach pochodnych, a następnie mogę wywołać metodę (która teraz wywołuje metodę abstrakcyjną).

Dlaczego nie mogłem po prostu zrobić abstrakcji na początku? Czy Java mogła na to pozwolić?

Paul Reiners
źródło
3
Wygląda na to, że mogłeś ominąć ten cały problem, ustawiając pole jako zmienne i po prostu dostarczając wartość za pomocą konstruktora, uzyskując 2 wystąpienia jednej klasy zamiast 2 klas.
Nate,
5
Tworząc abstrakcyjne pola w superklasie, musisz określić, że każda podklasa musi mieć to pole, więc nie różni się to od pola nieabstrakcyjnego.
Peter Lawrey
@Peter, nie jestem pewien, czy rozumiem twój punkt widzenia. jeśli w klasie abstrakcyjnej określono stałą nieabstrakcyjną, to jej wartość jest stała również we wszystkich podklasach. gdyby była abstrakcyjna, to jej wartość musiałaby zostać zaimplementowana / dostarczona przez każdą podklasę. więc wcale nie byłby taki sam.
liltitus 27
1
@ liltitus27 Myślę, że 3,5 lata temu moim celem było to, że posiadanie pól abstrakcyjnych nie zmieniłoby się zbytnio poza zerwaniem całej idei oddzielenia użytkownika interfejsu od implementacji.
Peter Lawrey
Byłoby to pomocne, ponieważ mogłoby zezwolić na niestandardowe adnotacje w polach w klasie podrzędnej
geg

Odpowiedzi:

104

Możesz zrobić to, co opisałeś, mając końcowe pole w swojej klasie abstrakcyjnej, które jest inicjowane w jego konstruktorze (nieprzetestowany kod):

abstract class Base {

    final String errMsg;

    Base(String msg) {
        errMsg = msg;
    }

    abstract String doSomething();
}

class Sub extends Base {

    Sub() {
        super("Sub message");
    }

    String doSomething() {

        return errMsg + " from something";
    }
}

Jeśli twoja klasa potomna „zapomni” o zainicjowaniu finału przez superkonstruktor, kompilator wyświetli ostrzeżenie o błędzie, tak jak wtedy, gdy metoda abstrakcyjna nie jest zaimplementowana.

rsp
źródło
Nie mam edytora, który mógłby to wypróbować, ale to też zamierzałem opublikować…
Tim
6
„kompilator wyświetli ostrzeżenie”. W rzeczywistości konstruktor podrzędny próbowałby użyć nieistniejącego konstruktora noargs i jest to błąd kompilacji (nie ostrzeżenie).
Stephen C
Dodanie do komentarza @Stephena C „tak jak wtedy, gdy metoda abstrakcyjna nie jest zaimplementowana” jest również błędem, a nie ostrzeżeniem.
Laurence Gonsalves,
4
Dobra odpowiedź - to zadziałało dla mnie. Wiem, że minęło trochę czasu, odkąd został opublikowany, ale myślę, że Basenależy to zgłosić abstract.
Steve Chambers
2
Nadal nie podoba mi się to podejście (mimo że jest to jedyna droga), ponieważ dodaje dodatkowy argument do konstruktora. W programowaniu sieciowym lubię tworzyć typy wiadomości, w których każda nowa wiadomość implementuje typ abstrakcyjny, ale musi mieć unikalny wyznaczony znak, taki jak „A” dla AcceptMessage i „L” dla LoginMessage. Zapewniają one, że niestandardowy protokół wiadomości będzie w stanie umieścić ten wyróżniający znak na kablu. Są one niejawne dla klasy, więc najlepiej byłoby je podawać jako pola, a nie coś przekazywanego do konstruktora.
Adam Hughes,
7

Nie widzę w tym sensu. Możesz przenieść funkcję do klasy abstrakcyjnej i po prostu przesłonić niektóre chronione pola. Nie wiem, czy to działa ze stałymi, ale efekt jest taki sam:

public abstract class Abstract {
    protected String errorMsg = "";

    public String getErrMsg() {
        return this.errorMsg;
    }
}

public class Foo extends Abstract {
    public Foo() {
       this.errorMsg = "Foo";
    }

}

public class Bar extends Abstract {
    public Bar() {
       this.errorMsg = "Bar";
    }
}

Chodzi ci więc o to, że chcesz wymusić implementację / nadpisanie / cokolwiek errorMsgw podklasach? Myślałem, że chciałeś mieć metodę w klasie bazowej i nie wiedziałeś wtedy, jak sobie radzić z polem.

Felix Kling
źródło
4
Cóż, oczywiście mógłbym to zrobić. I pomyślałem o tym. Ale dlaczego muszę ustawiać wartość w klasie bazowej, skoro wiem, że zastąpię tę wartość w każdej klasie pochodnej? Twój argument może być również użyty do argumentowania, że ​​dopuszczanie metod abstrakcyjnych również nie ma wartości.
Paul Reiners,
1
W ten sposób nie każda podklasa musi nadpisywać wartość. Mogę też po prostu zdefiniować, protected String errorMsg;co w jakiś sposób wymusza ustawienie wartości w podklasach. Jest podobny do tych klas abstrakcyjnych, które w rzeczywistości implementują wszystkie metody jako symbole zastępcze, dzięki czemu programiści nie muszą implementować każdej metody, chociaż jej nie potrzebują.
Felix Kling
7
Mówiąc protected String errorMsg;ma nie dochodzić, że można ustawić wartość w podklasach.
Laurence Gonsalves,
1
Jeśli wartość jest zerowa, jeśli nie ustawisz jej, liczy się jako „wymuszanie”, to jak nazwałbyś brak wymuszania?
Laurence Gonsalves,
9
@felix, istnieje różnica między egzekwowaniem prawa a umowami . cały ten post koncentruje się na klasach abstrakcyjnych, które z kolei reprezentują kontrakt, który musi zostać spełniony przez jakąkolwiek podklasę. więc kiedy mówimy o posiadaniu pola abstrakcyjnego, mówimy, że każda podklasa musi implementować wartość tego pola na kontrakt. Twoje podejście nie gwarantuje żadnej wartości i nie określa jasno, czego się oczekuje, tak jak robi to umowa. bardzo, bardzo różne.
liltitus 27
3

Oczywiście można było to zaprojektować, aby na to pozwolić, ale pod osłonami nadal musiałby wykonywać dynamiczne wysyłanie, a zatem wywołanie metody. Projekt Javy (przynajmniej na początku) był do pewnego stopnia próbą bycia minimalistą. Oznacza to, że projektanci starali się unikać dodawania nowych funkcji, jeśli można je łatwo symulować za pomocą innych funkcji już w języku.

Laurence Gonsalves
źródło
@Laurence - to nie dla mnie oczywiste, że abstrakcyjne pola mogły zostać uwzględnione. Coś takiego może mieć głęboki i fundamentalny wpływ na system typów i na użyteczność języka. (W ten sam sposób, w jaki wielokrotne dziedziczenie przynosi głębokie problemy ... które mogą ugryźć nieostrożnego programistę C ++.)
Stephen C,
0

Czytając swój tytuł, pomyślałem, że odnosisz się do abstrakcyjnych członków instancji; i nie widziałem dla nich pożytku. Ale abstrakcyjne statyczne elementy to zupełnie inna sprawa.

Często marzyłem, żebym mógł zadeklarować metodę podobną do poniższej w Javie:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Zasadniczo chciałbym nalegać, aby konkretne implementacje mojej klasy nadrzędnej zapewniały statyczną metodę fabryki z określonym podpisem. Pozwoliłoby mi to uzyskać odniesienie do konkretnej klasy Class.forName()i mieć pewność, że mogę ją skonstruować w wybranej przeze mnie konwencji.

Drew Wills
źródło
1
No cóż ... to prawdopodobnie oznacza, że ​​zbyt często używasz odbicia !!
Stephen C
Brzmi jak dziwny nawyk programowania. Class.forName (..) pachnie jak wada projektu.
whiskeysierra
W dzisiejszych czasach zasadniczo zawsze używamy Spring DI do osiągnięcia tego rodzaju rzeczy; ale zanim ten framework stał się znany i popularny, określaliśmy klasy implementacji we właściwościach lub plikach XML, a następnie używaliśmy Class.forName () do ich załadowania.
Drew Wills
Mogę podać dla nich przypadek użycia. Nieobecność „streszczenie polach” prawie niemożliwe, aby zadeklarować pola typu rodzajowego w abstrakcyjnej klasy generycznej: @autowired T fieldName. Jeśli Tjest zdefiniowane jako coś podobnego T extends AbstractController, typ erasure powoduje, że pole jest generowane jako typ AbstractControlleri nie może być automatycznie przypisane, ponieważ nie jest konkretne i nie jest unikalne. Generalnie zgadzam się, że nie mają dla nich większego pożytku, a zatem nie pasują do tego języka. Nie zgodziłbym się z tym, że NIE ma dla nich pożytku.
DaBlick
0

Inną opcją jest zdefiniowanie pola jako publicznego (jeśli chcesz, jeśli wolisz) w klasie bazowej, a następnie zainicjowanie tego pola w konstruktorze klasy bazowej, w zależności od aktualnie używanej podklasy. Jest to trochę podejrzane, ponieważ wprowadza zależność cykliczną. Ale przynajmniej nie jest to zależność, która może się zmienić - tj. Podklasa będzie istnieć lub nie będzie istnieć, ale metody lub pola podklasy nie mogą wpływać na wartość field.

public abstract class Base {
  public final int field;
  public Base() {
    if (this instanceof SubClassOne) {
      field = 1;
    } else if (this instanceof SubClassTwo) {
      field = 2;
    } else {
      // assertion, thrown exception, set to -1, whatever you want to do 
      // to trigger an error
      field = -1;
    }
  }
}
ragerdl
źródło