2013-12-09 15 views
42

Gradirei aiuto per comprendere il seguente da 'Java Concurrency in Practice':Permettere l'questo riferimento per sfuggire

Chiamare un metodo di istanza ignorabile (uno che non è né privato, né finale) dal costruttore può consentire anche a questo riferimento di uscire.

  1. fa 'fuga' qui semplicemente significa che possiamo probabilmente chiamando un metodo di istanza, prima che l'istanza sia completamente costruito?
    Non vedo "questo" sfuggire allo scopo dell'istanza in nessun altro modo.
  2. In che modo "final" impedisce che ciò accada? C'è qualche aspetto di "final" nella creazione dell'istanza che mi manca?
+2

Domanda bella e ben fatta. – Maroun

risposta

26
  1. Significa chiamare codice esterno alla classe, e passando this.
    Quel codice presuppone che l'istanza sia completamente inizializzata e potrebbe interrompersi se non lo è.
    Analogamente, la classe potrebbe presumere che alcuni metodi verranno chiamati solo dopo che l'istanza è stata completamente inizializzata, ma è probabile che il codice esterno interrompa tali ipotesi.

  2. final i metodi non possono essere ignorati, quindi è possibile fidarsi di loro di non passare this in giro.
    Se si chiama un metodo diverso da final nel costruttore per una classe diversa da final, una classe derivata potrebbe sovrascrivere quel metodo e passare this ovunque.
     
    Anche quando si chiama final metodi, è ancora necessario per assicurarsi che essi sono in modo sicuro scritti – che non passano this da nessuna parte, e che si non chiamare nessun non final metodi.

+0

Il codice "può" presupporre che la classe sia completamente inizializzata, ma il contratto del metodo potrebbe proibire esplicitamente tale deduzione. In alcuni casi, il costruttore della classe base potrebbe aver bisogno di informazioni sulla classe derivata e il modo più pratico per ottenere quella informazione potrebbe essere per chiamare un metodo virtuale il cui * scopo documentato * dovrebbe essere chiamato durante la base- costruzione di classe. In questi casi, può essere utile per il costruttore della classe base accettare un qualche tipo di oggetto e passarlo al metodo virtuale; ciò consentirà al costruttore della classe derivata ... – supercat

+0

... di incapsulare informazioni sui propri parametri in un oggetto e quindi utilizzare tali informazioni nel calcolo dei dati necessari al costruttore della classe base. Si noti che il design delle classi sub-derivate può essere alquanto complicato, ma ci sono alcuni schemi che consentono che tali cose vengano gestite in modo pulito e coerente. Si noti inoltre che un tale progetto si baserebbe su classi derivate per rispettare i loro contratti, ma che la dipendenza non è propriamente unica in questa situazione. Pochissime classi non sigillate possono comportarsi in modo robusto se le classi derivate non rispettano i loro contratti. – supercat

11

Credo che l'esempio è qualcosa di simile

public class Foo { 
    public Foo() { 
     doSomething(); 
    } 

    public void doSomething() { 
     System.out.println("do something acceptable"); 
    } 
} 

public class Bar extends Foo { 
    public void doSomething() { 
     System.out.println("yolo"); 
     Zoom zoom = new Zoom(this); // at this point 'this' might not be fully initialized 
    } 
} 

poiché il costruttore eccellente è sempre chiamato prima (implicitamente o esplicitamente), il doSomething Otterrete sempre chiamato per una classe figlia. Poiché il metodo sopra riportato non è né finalprivate, è possibile sovrascriverlo in una classe figlio e fare ciò che si desidera, il che potrebbe essere in conflitto con ciò che Foo#doSomething() è stato pensato per fare.

5

Per secure coding

Esempio BAD codice:

final class Publisher { 
    public static volatile Publisher published; 
    int num; 

    Publisher(int number) { 
    published = this; 
    // Initialization 
    this.num = number; 
    // ... 
    } 
} 

Se l'inizializzazione di un oggetto (e, di conseguenza, la sua costruzione) dipende da un controllo di sicurezza all'interno del costruttore, il controllo di sicurezza potrà prescindere un chiamante non attendibile ottiene l'istanza parzialmente inizializzata. Vedi regola OBJ11-J. Fai attenzione a lasciare che i costruttori generino eccezioni per ulteriori informazioni.

final class Publisher { 
    public static Publisher published; 
    int num; 

    Publisher(int number) { 
    // Initialization 
    this.num = number; 
    // ... 
    published = this; 
    } 
} 

Poiché il campo è volatile e nonfinal, le istruzioni all'interno costruttore possono essere riordinate dal compilatore in modo tale che pubblicazione del presente domanda prima che le istruzioni di inizializzazione sono eseguiti.

codice corretto:

final class Publisher { 
    static volatile Publisher published; 
    int num; 

    Publisher(int number) { 
    // Initialization 
    this.num = number; 
    // ... 
    published = this; 
    } 
} 

Il dice questo riferimento essere sfuggito quando è reso disponibile oltre il suo ambito corrente. Di seguito sono modi comuni con cui il riferimento this può sfuggire:

Returning this from a non-private, overridable method that is invoked from the constructor of a class whose object is being 

costruito. (Per ulteriori informazioni, vedere la regola MET05-J. Assicurarsi che i costruttori non chiamino metodi sovrascrivibili.) Restituisce ciò da un metodo non privato di una classe mutabile, che consente al chiamante di manipolare lo stato dell'oggetto indirettamente. Questo si verifica comunemente nelle implementazioni concatenate di metodi; vedi regola VNA04-J. Assicurarsi che le chiamate ai metodi concatenati siano atomiche per ulteriori informazioni. Passando questo argomento come argomento a un metodo alien richiamato dal costruttore di una classe il cui oggetto è in costruzione. Uso delle classi interne. Una classe interna contiene implicitamente un riferimento all'istanza della sua classe esterna a meno che la classe interna non sia dichiarata statica . Pubblicazione assegnando questo a una variabile statica pubblica dal costruttore di una classe il cui oggetto è in costruzione. Lanciare un'eccezione da un costruttore. Ciò potrebbe causare la vulnerabilità del codice a un attacco finalizzatore; vedi regola OBJ11-J. Fai attenzione a lasciando che i costruttori generino eccezioni per ulteriori informazioni. Passaggio dello stato dell'oggetto interno a un metodo alieno. Ciò consente al metodo di recuperare questo riferimento dell'oggetto membro interno.

Questa regola illustra le possibili conseguenze di consentire il riferimento this per sfuggire durante la costruzione dell'oggetto, tra cui la razza condizioni e l'inizializzazione improprio. Ad esempio, la dichiarazione di un campo final in genere garantisce che tutti i thread vedano il campo in uno stato inizializzato completamente ; tuttavia, consentire a questo riferimento di sfuggire a durante la costruzione dell'oggetto può esporre il campo ad altri thread in uno stato non inizializzato o parzialmente inizializzato . Regola TSM03-J. Non pubblicare oggetti parzialmente inizializzati, che descrive le garanzie fornite da vari meccanismi per la pubblicazione sicura, si basa sulla conformità a questa regola. Di conseguenza, i programmi non devono consentire allo questo riferimento di uscire durante la costruzione dell'oggetto.

In generale, è importante rilevare i casi in cui il riferimento può fuoriuscire oltre l'ambito del contesto corrente. Nello studio , le variabili e i metodi pubblici devono essere attentamente esaminati esaminati.

+0

Direi che uno schema migliore per la tua classe 'Publisher' sarebbe quello di usare una factory di qualche forma (forse un metodo' static') che crea un'istanza e assegna il campo 'published'. Questo sarebbe particolarmente importante se 'Publisher' non fosse' final'. – Jeffrey

+0

@Jeffrey non è la mia classe editore, è stata presa direttamente dal sito di codifica sicuro. – Woot4Moo

+0

Questa risposta è molto più chiara! grazie – asgs

19

"Fuga" significa che un riferimento all'oggetto this parzialmente costruito potrebbe essere passato a qualche altro oggetto nel sistema.Consideriamo questo scenario:

public Foo { 
    public Foo() { 
     setup(); 
    } 

    protected void setup() { 
     // do stuff 
    } 
} 

public Bar extends Foo implements SomeListener { 
    @Override protected void setup() { 
     otherObject.addListener(this); 
    } 
} 

Il problema è che il nuovo oggetto Bar è iscritto su otherObject prima della sua costruzione è stata completata. Ora se otherObject inizia a chiamare i metodi su barObject, i campi potrebbero non essere stati inizializzati oppure barObject potrebbe essere in uno stato incoerente. Un riferimento allo barObject (this a se stesso) è "scappato" nel resto del sistema prima che sia pronto.

Invece, se il metodo è setup()final sul Foo, la classe Bar non può mettere codice lì che renderà l'oggetto visibile prima le Foo finiture costruttore.

+1

+1 per l'esempio – IUnknown

Problemi correlati