Warum nicht abstrakte Felder?

99

Warum können Java-Klassen keine abstrakten Felder haben, wie sie abstrakte Methoden haben können?

Zum Beispiel: Ich habe zwei Klassen, die dieselbe abstrakte Basisklasse erweitern. Diese beiden Klassen haben jeweils eine identische Methode, mit Ausnahme einer String-Konstante, die zufällig eine Fehlermeldung enthält. Wenn Felder abstrakt sein könnten, könnte ich diese Konstante abstrakt machen und die Methode in die Basisklasse hochziehen. Stattdessen muss ich eine abstrakte Methode erstellen, die getErrMsg()in diesem Fall aufgerufen wird, die den String zurückgibt, diese Methode in den beiden abgeleiteten Klassen überschreiben und dann die Methode aufrufen (die jetzt die abstrakte Methode aufruft).

Warum konnte ich das Feld nicht gleich abstrakt machen? Könnte Java entwickelt worden sein, um dies zu ermöglichen?

Paul Reiners
quelle
3
Es hört sich so an, als hätten Sie dieses gesamte Problem umgehen können, indem Sie das Feld nicht konstant gemacht und den Wert einfach über den Konstruktor angegeben haben. Am Ende stehen 2 Instanzen einer Klasse anstelle von 2 Klassen zur Verfügung.
Nate
5
Indem Sie ein Feld in einer Superklasse abstrakt machen, müssen Sie festlegen, dass jede Unterklasse dieses Feld haben muss. Dies unterscheidet sich also nicht von einem nicht abstrakten Feld.
Peter Lawrey
@ Peter, ich bin nicht sicher, ob ich deinem Standpunkt folge. Wenn in der abstrakten Klasse eine nicht abstrakte Konstante angegeben wurde, ist ihr Wert auch in allen Unterklassen konstant. Wenn es abstrakt wäre, müsste sein Wert von jeder Unterklasse implementiert / bereitgestellt werden. es wäre also überhaupt nicht dasselbe.
Liltitus27
1
@ liltitus27 Ich denke, mein Punkt vor 3,5 Jahren war, dass sich abstrakte Felder nicht sehr ändern würden, außer die ganze Idee zu brechen, den Benutzer einer Schnittstelle von der Implementierung zu trennen.
Peter Lawrey
Dies wäre hilfreich , weil es benutzerdefiniertes Feld Anmerkungen in der Kindklasse ermöglichen könnte
geg

Antworten:

102

Sie können das tun, was Sie beschrieben haben, indem Sie ein letztes Feld in Ihrer abstrakten Klasse haben, das in ihrem Konstruktor initialisiert ist (nicht getesteter Code):

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

Wenn Ihre untergeordnete Klasse "vergisst", das Finale über den Superkonstruktor zu initialisieren, gibt der Compiler eine Warnung einen Fehler aus, genau wie wenn eine abstrakte Methode nicht implementiert ist.

rsp
quelle
Ich habe keinen Editor, der es hier ausprobieren könnte, aber das wollte ich auch posten.
Tim
6
"Der Compiler gibt eine Warnung aus". Tatsächlich würde der untergeordnete Konstruktor versuchen, einen nicht vorhandenen noargs-Konstruktor zu verwenden, und dies ist ein Kompilierungsfehler (keine Warnung).
Stephen C
Das Hinzufügen zu @Stephen Cs Kommentar "wie wenn eine abstrakte Methode nicht implementiert ist" ist ebenfalls ein Fehler und keine Warnung.
Laurence Gonsalves
4
Gute Antwort - das hat bei mir funktioniert. Ich weiß, dass es eine Weile her ist, seit es veröffentlicht wurde, aber ich denke Base, es muss deklariert werden abstract.
Steve Chambers
2
Ich mag diesen Ansatz immer noch nicht (obwohl es der einzige Weg ist), weil er einem Konstruktor ein zusätzliches Argument hinzufügt. In der Netzwerkprogrammierung mache ich gerne Nachrichtentypen, bei denen jede neue Nachricht einen abstrakten Typ implementiert, aber ein eindeutiges Zeichen wie 'A' für AcceptMessage und 'L' für LoginMessage haben muss. Diese stellen sicher, dass ein benutzerdefiniertes Nachrichtenprotokoll dieses Unterscheidungsmerkmal auf die Leitung setzen kann. Diese sind für die Klasse implizit, daher sollten sie am besten als Felder und nicht als an den Konstruktor übergebene Felder verwendet werden.
Adam Hughes
7

Darin sehe ich keinen Sinn. Sie können die Funktion in die abstrakte Klasse verschieben und einfach ein geschütztes Feld überschreiben. Ich weiß nicht, ob dies mit Konstanten funktioniert, aber der Effekt ist der gleiche:

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

Ihr Punkt ist also, dass Sie die Implementierung / Überschreiben / was auch immer errorMsgin den Unterklassen erzwingen möchten ? Ich dachte, Sie wollten nur die Methode in der Basisklasse haben und wussten damals nicht, wie Sie mit dem Feld umgehen sollten.

Felix Kling
quelle
4
Nun, das könnte ich natürlich tun. Und ich hatte daran gedacht. Aber warum muss ich einen Wert in der Basisklasse festlegen, wenn ich weiß, dass ich diesen Wert in jeder abgeleiteten Klasse überschreiben werde? Ihr Argument könnte auch verwendet werden, um zu argumentieren, dass es keinen Wert hat, abstrakte Methoden zuzulassen.
Paul Reiners
1
Nun diese Art und Weise nicht jede Unterklasse hat den Wert außer Kraft zu setzen. Ich kann auch einfach definieren, protected String errorMsg;was irgendwie erzwingt, dass ich den Wert in den Unterklassen setze. Es ähnelt den abstrakten Klassen, die tatsächlich alle Methoden als Platzhalter implementieren, sodass Entwickler nicht jede Methode implementieren müssen, obwohl sie sie nicht benötigen.
Felix Kling
7
Wenn Sie sagen, protected String errorMsg;wird nicht erzwungen, dass Sie den Wert in Unterklassen festlegen.
Laurence Gonsalves
1
Wenn der Wert null ist, wenn Sie ihn nicht festlegen, zählt er als "Durchsetzung". Was würden Sie dann als Nichtdurchsetzung bezeichnen?
Laurence Gonsalves
9
@felix, es gibt einen Unterschied zwischen Durchsetzung und Verträgen . Dieser ganze Beitrag konzentriert sich auf abstrakte Klassen, die wiederum einen Vertrag darstellen, der von jeder Unterklasse erfüllt werden muss. Wenn wir also von einem abstrakten Feld sprechen, sagen wir, dass jede Unterklasse den Wert dieses Feldes pro Vertrag implementieren muss . Ihr Ansatz garantiert keinen Wert und macht nicht deutlich, was erwartet wird, wie es ein Vertrag tut. sehr, sehr unterschiedlich.
Liltitus27
3

Natürlich hätte es dies ermöglichen können, aber unter dem Deckmantel müsste es immer noch einen dynamischen Versand und damit einen Methodenaufruf durchführen. Javas Design (zumindest in den frühen Tagen) war bis zu einem gewissen Grad ein Versuch, minimalistisch zu sein. Das heißt, die Designer haben versucht, das Hinzufügen neuer Funktionen zu vermeiden, wenn sie durch andere Funktionen, die bereits in der Sprache vorhanden sind, problemlos simuliert werden können.

Laurence Gonsalves
quelle
@Laurence - es ist mir nicht klar, dass abstrakte Felder enthalten sein könnten. So etwas hat wahrscheinlich tiefgreifende und grundlegende Auswirkungen auf das Typensystem und die Sprachverwendbarkeit. (Auf die gleiche Weise, wie Mehrfachvererbung tiefe Probleme mit sich bringt ... die den unachtsamen C ++ - Programmierer beißen können.)
Stephen C
0

Als ich Ihren Titel las, dachte ich, Sie beziehen sich auf abstrakte Instanzmitglieder. und ich konnte nicht viel Verwendung für sie sehen. Aber abstrakte statische Elemente sind eine ganz andere Sache.

Ich habe mir oft gewünscht, dass ich eine Methode wie die folgende in Java deklarieren könnte:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Grundsätzlich möchte ich darauf bestehen, dass konkrete Implementierungen meiner übergeordneten Klasse eine statische Factory-Methode mit einer bestimmten Signatur bereitstellen. Dies würde es mir ermöglichen, einen Verweis auf eine konkrete Klasse mit zu erhalten Class.forName()und sicher zu sein, dass ich eine in einer Konvention meiner Wahl konstruieren könnte.

Drew Wills
quelle
1
Ja gut ... das bedeutet wahrscheinlich, dass Sie viel zu viel Reflexion verwenden !!
Stephen C
Klingt für mich nach einer seltsamen Programmiergewohnheit. Class.forName (..) riecht nach einem Designfehler.
whiskeysierra
Heutzutage verwenden wir im Grunde immer Spring DI, um so etwas zu erreichen. Bevor dieses Framework jedoch bekannt und beliebt wurde, haben wir Implementierungsklassen in Eigenschaften oder XML-Dateien angegeben und sie dann mit Class.forName () geladen.
Drew Wills
Ich kann Ihnen einen Anwendungsfall für sie geben. Das Fehlen von "abstrakten Feldern" ist nahezu unmöglich, ein Feld eines generischen Typs in einer abstrakten generischen Klasse zu deklarieren : @autowired T fieldName. Wenn dies als "Typ löschen" Tdefiniert ist T extends AbstractController, wird das Feld als Typ AbstractControllergelöscht und kann nicht automatisch verdrahtet werden, da es nicht konkret und nicht eindeutig ist. Ich stimme im Allgemeinen zu, dass sie nicht viel nützen und daher nicht in die Sprache gehören. Was ich nicht zustimmen würde, ist, dass es KEINE Verwendung für sie gibt.
DaBlick
0

Eine andere Möglichkeit besteht darin, das Feld in der Basisklasse als öffentlich (endgültig, wenn Sie möchten) zu definieren und dieses Feld dann im Konstruktor der Basisklasse zu initialisieren, je nachdem, welche Unterklasse derzeit verwendet wird. Es ist ein bisschen zwielichtig, da es eine zirkuläre Abhängigkeit einführt. Aber zumindest ist es keine Abhängigkeit, die sich jemals ändern kann - dh die Unterklasse wird entweder existieren oder nicht existieren, aber die Methoden oder Felder der Unterklasse können den Wert von nicht beeinflussen 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
quelle