Perché non campi astratti?


100

Perché le classi Java non possono avere campi astratti come possono avere metodi astratti?

Ad esempio: ho due classi che estendono la stessa classe base astratta. Queste due classi hanno ciascuna un metodo identico ad eccezione di una costante String, che sembra essere un messaggio di errore, al loro interno. Se i campi potessero essere astratti, potrei rendere astratta questa costante e inserire il metodo nella classe base. Invece, devo creare un metodo astratto, chiamato getErrMsg()in questo caso, che restituisca la String, sovrascriva questo metodo nelle due classi derivate e quindi posso richiamare il metodo (che ora chiama il metodo astratto).

Perché non potevo semplicemente rendere astratto il campo per cominciare? Java potrebbe essere stato progettato per consentire questo?


3
Sembra che avresti potuto eludere l'intera questione rendendo il campo non costante e semplicemente fornendo il valore tramite il costruttore, ottenendo 2 istanze di una classe anziché 2 classi.
Nate

5
Rendendo un campo astratto in una super classe, hai specificato che ogni sottoclasse deve avere questo campo, quindi questo non è diverso da un campo non astratto.
Peter Lawrey

@peter, non sono sicuro di seguire il tuo punto. se una costante non astratta è stata specificata nella classe abstract, il suo valore è costante anche in tutte le sottoclassi. se fosse astratto, il suo valore dovrebbe essere implementato / fornito da ciascuna sottoclasse. quindi, non sarebbe affatto lo stesso.
liltitus27

1
@ liltitus27 Penso che il mio punto 3,5 anni fa fosse che avere campi astratti non cambierebbe molto se non rompendo l'intera idea di separare l'utente di un'interfaccia dall'implementazione.
Peter Lawrey

Ciò sarebbe utile perché potrebbe consentire annotazioni di campi personalizzati nella classe figlio
geg

Risposte:


103

Puoi fare ciò che hai descritto avendo un campo finale nella tua classe astratta che è inizializzato nel suo costruttore (codice non testato):

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

Se la tua classe figlia "dimentica" di inizializzare la finale tramite il super costruttore, il compilatore darà un avviso di errore, proprio come quando un metodo astratto non è implementato.


Non ho un editor per provarlo qui, ma questo era anche quello che stavo per postare ..
Tim

6
"il compilatore darà un avviso". In realtà, il costruttore Child tenterebbe di utilizzare un costruttore noargs inesistente e questo è un errore di compilazione (non un avviso).
Stephen C

E aggiungere al commento di @Stephen C, "come quando un metodo astratto non è implementato" è anche un errore, non un avvertimento.
Laurence Gonsalves

4
Buona risposta: questo ha funzionato per me. So che è passato un po 'di tempo da quando è stato pubblicato, ma penso che Basedebba essere dichiarato abstract.
Steve Chambers,

2
Non mi piace ancora questo approccio (anche se è l'unico modo per andare) perché aggiunge un argomento in più a un costruttore. Nella programmazione di rete, mi piace creare tipi di messaggio, in cui ogni nuovo messaggio implementa un tipo astratto ma deve avere un carattere designato univoco come "A" per AcceptMessage e "L" per LoginMessage. Questi garantiscono che un protocollo di messaggio personalizzato sarà in grado di inserire questo carattere distintivo sul filo. Questi sono impliciti nella classe, quindi sarebbe meglio servire come campi, non come qualcosa passato nel costruttore.
Adam Hughes

7

Non ci vedo alcun punto. Puoi spostare la funzione nella classe astratta e sovrascrivere semplicemente un campo protetto. Non so se funziona con le costanti ma l'effetto è lo stesso:

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

Quindi il tuo punto è che vuoi imporre l'implementazione / l'override / qualsiasi cosa errorMsgnelle sottoclassi? Pensavo volessi solo avere il metodo nella classe base e non sapessi come affrontare il campo allora.


4
Beh, potrei farlo ovviamente. E ci avevo pensato. Ma perché devo impostare un valore nella classe base quando so per certo che sovrascriverò quel valore in ogni classe derivata? Il tuo argomento potrebbe anche essere usato per sostenere che non c'è alcun valore nemmeno nel consentire metodi astratti.
Paul Reiners,

1
In questo modo non tutte le sottoclassi devono sovrascrivere il valore. Posso anche solo definire protected String errorMsg;quale impone in qualche modo che ho impostato il valore nelle sottoclassi. È simile a quelle classi astratte che implementano effettivamente tutti i metodi come segnaposto in modo che gli sviluppatori non debbano implementare tutti i metodi anche se non ne hanno bisogno.
Felix Kling

7
Dire protected String errorMsg;non non far rispettare che si imposta il valore in sottoclassi.
Laurence Gonsalves,

1
Se il valore è nullo se non lo imposti conta come "applicazione", cosa chiameresti non applicazione?
Laurence Gonsalves

9
@felix, c'è una differenza tra applicazione e contratti . l'intero post è incentrato sulle classi astratte, che a loro volta rappresentano un contratto che deve essere soddisfatto da qualsiasi sottoclasse. quindi, quando parliamo di avere un campo astratto, stiamo dicendo che qualsiasi sottoclasse deve implementare il valore di quel campo, per contratto. il tuo approccio non garantisce alcun valore e non rende esplicito ciò che ci si aspetta, come fa un contratto. molto, molto diverso.
liltitus27

3

Ovviamente avrebbe potuto essere progettato per consentire questo, ma sotto le coperte avrebbe comunque dovuto eseguire un invio dinamico, e quindi una chiamata al metodo. Il design di Java (almeno all'inizio) era, in una certa misura, un tentativo di essere minimalista. Cioè, i progettisti hanno cercato di evitare di aggiungere nuove funzionalità se potevano essere facilmente simulate da altre funzionalità già presenti nel linguaggio.


@Laurence - è non è ovvio per me che i campi astratti avrebbero potuto essere inclusi. È probabile che qualcosa del genere abbia un impatto profondo e fondamentale sul sistema di caratteri e sull'usabilità del linguaggio. (Nello stesso modo in cui l'ereditarietà multipla porta problemi profondi ... che possono mordere lo sprovveduto programmatore C ++.)
Stephen C

0

Leggendo il tuo titolo, ho pensato che ti stessi riferendo a membri di istanze astratte; e non ne vedevo molto utile. Ma i membri statici astratti è tutta un'altra cosa.

Ho spesso desiderato di poter dichiarare un metodo come il seguente in Java:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Fondamentalmente, vorrei insistere sul fatto che le implementazioni concrete della mia classe genitore forniscono un metodo factory statico con una firma specifica. Ciò mi consentirebbe di ottenere un riferimento a una classe concreta con Class.forName()ed essere certo di poterne costruire uno in una convenzione di mia scelta.


1
Sì, beh ... questo probabilmente significa che stai usando troppo il riflesso !!
Stephen C

Mi sembra una strana abitudine di programmazione. Class.forName (..) puzza come un difetto di progettazione.
whiskeysierra

In questi giorni fondamentalmente usiamo sempre Spring DI per realizzare questo genere di cose; ma prima che quel framework diventasse noto e popolare, dovevamo specificare le classi di implementazione nelle proprietà o nei file XML, quindi utilizzare Class.forName () per caricarli.
Drew Wills

Posso darti un caso d'uso per loro. L'assenza di "campi astratti" quasi impossibile dichiarare un campo di tipo generico in una classe generica: @autowired T fieldName. Se Tè definito come qualcosa di simile T extends AbstractController, la cancellazione del tipo fa sì che il campo AbstractControllervenga generato come tipo e che non può essere cablato automaticamente perché non è concreto e non è unico. In genere sono d'accordo che non siano molto utili e quindi non appartengono alla lingua. Quello con cui non sarei d'accordo è che NON ce ne sono.
DaBlick

0

Un'altra opzione è definire il campo come pubblico (finale, se lo desideri) nella classe di base, quindi inizializzare quel campo nel costruttore della classe di base, a seconda della sottoclasse attualmente utilizzata. È un po 'losco, in quanto introduce una dipendenza circolare. Ma almeno non è una dipendenza che può mai cambiare - cioè, la sottoclasse esisterà o non esisterà, ma i metodi oi campi della sottoclasse non possono influenzare il valore di 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;
    }
  }
}
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.