chiamare setter da un costruttore

Quali sono i pro ei contro di chiamare a un mutatore da un costruttore (se presente)

vale a dire:

public MyConstructor(int x) { this.x = x; } 

contro:

 public MyConstructor(int x) { setX(x); } public void setX(int x) { this.x = x; } 

Hai una preferenza? (Questo non è compito a casa, basta guardare il nostro doc di standard di codifica dove dice di chiamare sempre i mutatori quando si impostano le variabili di istanza nel costruttore e non sempre lo faccio)

Personalmente, imposterò la variabile direttamente nella maggior parte dei casi.

I metodi solitamente si aspettano che l’istanza sia completamente formattata dal momento in cui vengono chiamati. In particolare, chiamare un metodo sottoposto a override da un costruttore è una ricetta per codice difficile da comprendere e bug difficili da individuare.

Detto questo, spesso cerco di rendere le classi sempre immutabili, nel qual caso non solo non c’è setter, ma devi comunque impostare la variabile finale dal costruttore (o da un inizializzatore di variabili) 🙂

Laddove le proprietà hanno una logica, la logica setter è solitamente valida e talvolta modifica la propagazione agli osservatori. Di solito mi aspetto che i parametri del costruttore siano controllati esplicitamente all’inizio del metodo e non vorreste che avvenga alcuna propagazione delle modifiche prima che un’istanza sia completamente creata comunque.

Seguo due regole sui costruttori per ridurre al minimo i problemi, motivo per cui non utilizzerei il metodo del mutatore:

I costruttori (di classi non finali) devono chiamare solo metodi finali o privati . Se si decide di ignorare questa regola e lasciare che il costruttore chiami metodi non definitivi / non privati, allora:

  • quei metodi e tutti i metodi che possono chiamare devono fare attenzione a non presupporre che l’istanza sia completamente inizializzata, e
  • le sottoclassi che sovrascrivono questi metodi (sottoclassi che potrebbero non essere nemmeno consapevoli del fatto che il costruttore della superclass chiama questi metodi) non devono presumere che i costruttori delle sottoclassi e dei costruttori di superclassi siano stati completamente eseguiti. Questo problema peggiora più in profondità nella gerarchia dell’ereditarietà della superclass con il costruttore “malvagio”.

Ne vale la pena? È ansible consentire un’eccezione per i semplici mutatori che assegnano solo un valore a una variabile di istanza, poiché il vantaggio è limitato, anche se non sembra valsa la pena.

[[@Jon Skeet lo menziona nella sua risposta: “… In particolare, chiamare un metodo sovrascritto da un costruttore è una ricetta per codice difficile da comprendere e bug difficili da individuare.” Ma non credo che le ramificazioni di questo problema siano abbastanza accentuate. ]]

I costruttori dovrebbero essere prudenti a perdere this prima che l’istanza sia completamente inizializzata. Mentre la regola precedente riguardava i metodi all’interno della class e le sottoclassi che accedono ad ivars, devi anche fare attenzione ai metodi (anche finali / privati) che passano ad altre classi e funzioni di utilità prima che this sia completamente inizializzato. Maggiore è il numero di metodi non privati ​​e sovrascrivibili richiamati dal costruttore, maggiore è il rischio di perdite.


Alcuni riferimenti ai costruttori che chiamano metodi non definitivi e non privati:

https://www.securecoding.cert.org/confluence/display/java/MET05-J.+Ensure+that+constructors+do+not+call+overridable+methods

http://www.javaworld.com/article/2074669/core-java/java-netbeans–overridable-method-call-in-constructor.html

http://www.javaspecialists.eu/archive/Issue210.html

Raramente lo faccio, e non ho mai avuto problemi a causa di ciò. Può anche avere conseguenze non volute, specialmente se i tuoi setter sono sovrascritti in una sottoclass, o se i tuoi setter sparano ulteriori chiamate di metodo che potrebbero non essere appropriate durante l’inizializzazione.

Dipende da come tratti gli inservienti. Se ritieni che la class possa essere derivata, puoi consentire tale chiamata in modo che tu possa ignorare il comportamento altrove. Altrimenti, puoi impostarlo come

Ovviamente, permettendo a tutti i setter di essere esplicitamente chiamati, si otterrebbero anche modi per iniettare comportamenti addizionali durante l’impostazione (come l’applicazione della sicurezza se necessario).

A meno che il tuo setter non stia facendo qualcosa di più complesso di this.x = x , vorrei semplicemente impostare direttamente la variabile. Il parametro si associa direttamente alla tua variabile di istanza e per me è più rivelatore di intenti.

C’è una sola ragione per cui i nostri standard di codifica richiedono l’uso di accessori (che possono anche essere privati). Se vuoi rapidamente scoprire quale codice sta cambiando un campo, devi semplicemente richiamare la gerarchia di chiamata per il setter in Eclipse!

Questo è un enorme risparmio di tempo in una base di codice che ha raggiunto 2 milioni di linee di codice e facilita anche il refactoring.

Non me ne preoccupo e non ho un prefetto forte [e non rispettare alcuni standard severi], basta usare il tuo giudizio. Ma se si va con setter chiunque abbia la precedenza sulla class deve essere consapevole che l’object potrebbe non essere completamente creato. Detto questo, assicurati che nessun codice aggiuntivo nei setter possa pubblicare this riferimento.

I setter possono essere utili per i controlli di intervallo standard e così via, quindi non è necessario alcun codice aggiuntivo per verificare l’input. Anche in questo caso l’uso di setter è un approccio leggermente più sporco.

L’uso del campo ha vantaggi evidenti (come assicurarsi che chiunque sottoclass non sia in grado di alterare il comportamento) e di solito è preferibile, in particolare. nelle biblioteche.

In realtà c-tor è uno dei 3 possibili modi per creare un object (serializzazione + clone sono gli altri 2), alcuni stati transitori potrebbero dover essere gestiti al di fuori del c-tor, quindi hai ancora cosa riflettere sui campi vs setter. (Modifica, ce n’è un altro a metà strada: non sicuro, ma è quello che usa la serializzazione)

La mia preferenza è impostarli direttamente nel costruttore per alcuni motivi. In primo luogo qualcosa come this.x = x; è altrettanto chiaro, se non di più che chiamare un metodo separato che fa la stessa cosa. In secondo luogo, il metodo potrebbe essere stato sovrascritto a meno che non sia contrassegnato come finale e chiamare i metodi potenzialmente sovrascritti da un costruttore è un grosso no-no nel mio libro. In terzo luogo, la maggior parte dei metodi generalmente presuppone che l’object sia già completo quando è in esecuzione, e non a metà della costruzione. Anche se questo non dovrebbe causare problemi in questo caso semplice, in casi più complessi può causare bug seriamente sottili che impiegano anni per rintracciare.

L’argomento principale per l’utilizzo di setter / getter in tutto il mondo è che ciò significa che è ansible rinominare il campo semplicemente cambiando il suo nome in 3 punti, la sua definizione, i metodi getter / setter e tutti dovrebbero essere compilati e vanno bene. Questo argomento è nullo in questi giorni a mio parere, dal momento che qualsiasi IDE moderno decente rinominerà tutte le occorrenze di tale campo con una semplice scorciatoia da tastiera.

Richiamando qualsiasi metodo public , static , non-final all’interno del costruttore, dipende da te, ma la best practice non invoca mai tali metodi all’interno del costruttore, perché questi metodi possono essere sovrascritti in sottoclassi e verrà invocata solo la versione sovrascritta di questi metodi (se usi il comportamento polimorfico).

Per esempio:

 public class Foo { public Foo() { doSmth(); // If you use polymorphic behavior this method will never be invoked } public void doSmth() { System.out.println("doSmth in super class"); } public static void main(String[] args) { new Bar(200); } } class Bar extends Foo { private int y;; public Bar(int y) { this.y = y; } @Override public void doSmth() { // This version will be invoked even before Barr object initialized System.out.println(y); } } 

Stamperà 0.

Per maggiori dettagli leggi Bruce Eckel “Pensare in Java” capitolo “Polimorfismo”