2011-10-26 15 views
5

ho trovato un nuovo modo di chiamare più metodi in Java e io in realtà non capisce cosa sta succedendo dietro:Calling più metodi in Java

public class NutritionFacts { 
private final int servingSize; 
private final int servings; 
private final int calories; 
private final int fat; 
private final int sodium; 
private final int carbohydrate; 

public static class Builder { 
    // Required parameters 
    private final int servingSize; 
    private final int servings; 
    // Optional parameters - initialized to default values 
    private int calories  = 0; 
    private int fat   = 0; 
    private int carbohydrate = 0; 
    private int sodium  = 0; 
    public Builder(int servingSize, int servings) { 
     this.servingSize = servingSize; 
     this.servings = servings; 
    } 
     public Builder calories(int val) 
      { calories = val;  return this; } 
     public Builder fat(int val) 
      { fat = val;   return this; } 
     public Builder carbohydrate(int val) 
      { carbohydrate = val; return this; } 
     public Builder sodium(int val) 
      { sodium = val;  return this; } 
     public NutritionFacts build() { 
      return new NutritionFacts(this); 
     } 
} 

    private NutritionFacts(Builder builder) { 
     servingSize = builder.servingSize; 
     servings  = builder.servings; 
     calories  = builder.calories; 
    } 

} 

Ora la classe viene creata un'istanza utilizzando questa linea, ed ecco dove si confonde:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build(); 

tutto ha un senso fino alla NutritionFacts.Build (int, int), dopo di che, cosa sta succedendo? Perché i metodi-, , carbohydrate dalla classe Builder devono restituire this? Dove entra questo indirizzo di classe?

Grazie!

risposta

9

Non "entra" in nulla.

Questi metodi restituiscono un valore. In questo caso, restituiscono l'istanza corrente, this. Quell'istanza ha metodi, come calories() e carbohydrates().

foo.calories(12) restituisce l'istanza e possiamo chiamare i suoi metodi: foo.calories(12).sodium(35).

Non è diverso dal "valore restituito" dal costruttore, implicitamente definito come nuova istanza. In questo caso è normale, restituendo comunque un'istanza, quella attuale.

È lo stesso di questo:

Builder foo = new Builder(1, 2); // The "return" value of a ctor is the reference, foo 
foo.sodium(10); // Returns foo, but we ignore it 
foo.calories(42); // Returns foo, but we ignore it 

(foo.sodium(10)).calories(42); 
^^^^^^^^^^^^^^^^ foo, with the side effect of setting the sodium value 

Here's an SO question with some good examples.

+0

Questo significa che il valore restituito da foo.calories (12), che è l'istanza, è utilizzata per la chiamata al metodo prossimo in linea , nel nostro caso, sodio (35)? –

+0

@ Cookie503 assolutamente :) –

+0

@ Cookie503 Esattamente. –

2

Questa tecnica si chiama "method chaining" ed è uno stile molto carino con cui familiarizzare. A tale scopo, invece di dover dire qualcosa come:

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 35, 27); 

... che è soggetta a bug, perché l'ordine o il significato di argomenti nel costruttore potrebbe cambiare. O anche, invece di qualcosa di simile:

NutritionFacts cocaCola = new NutritionFacts(240, 8); 
cocaCola.setCalories(100); 
cocaCola.setSodium(35); 
cocaCola.setCarbohydrates(27); 

... che è male per un paio di motivi: in primo luogo perché non è così "fluent" in termini di leggibilità, secondo perché il setter sono chiamati non-atomico (che è indesiderabile quando l'oggetto è condiviso su più thread) e in terzo luogo perché i campi calories, sodium e carbohydrates sono forzati non final. Nella variante Builder, questi campi possono essere facilmente creati final perché vengono impostati solo una volta nel costruttore.

Questo modello, in cui ogni chiamata allo Builder restituisce un riferimento a se stesso, consente di collegare tali chiamate per mantenerle facilmente leggibili e per mantenere atomica la costruzione dell'oggetto risultante.

1

Hai definito Builder come classe nidificata di NutritionFacts.Poiché è statico, non richiede l'esistenza di un'istanza NutritionFacts. Se non fosse statico (una cosiddetta "classe interiore") richiederebbe l'esistenza di un NutritionFacts. Inoltre, alcuni dei tuoi campi Builder nascondono alcuni campi NutritionFact, ma non è questo il caso ora.

Ora, dal momento che hai utilizzato questo oggetto di classe nidificato, non puoi semplicemente chiamarlo come Builder. Dovrai fare riferimento ad esso come NutritionFacts.Builder. Quindi, quando nel tuo secondo estratto di codice fai il numero new NutritionFacts.Builder(240, 8), ciò che ottieni è una nuova istanza di Builder con un servingSize 240 e 8 porzioni. NutritionFacts in realtà non entra in gioco ancora, è solo lì per il nome.

Questa nuova istanza può essere assegnata a qualche variabile oppure può essere utilizzata immediatamente, ad esempio, chiamando un metodo su di essa. Questo è quello che stai facendo, ovvero chiama lo .calories(100) su di esso che imposta il campo di calorie di quel Builder. Ma se andate a dare un'occhiata a quel metodo, noterete che è di tipo Build di ritorno e che cosa restituisce è this. Questa parola chiave si riferisce semplicemente all'istanza corrente: lo stesso Generatore di nuovo.

Lo stesso vale per .sodium(35) e .carbohydrate(27), dopodiché si finisce chiamando .build() sul Generatore. Guarda questo metodo. Chiama il costruttore di NutritionFacts. Quel costruttore prende un'istanza Builder come argomento, e il nostro Builder passa se stesso (di nuovo con this). Ora stiamo finalmente ottenendo un'istanza di NutritionFacts e viene creata utilizzando i valori che sono stati memorizzati nell'istanza Builder.

Come ha detto Jere, i metodi che impostano i nutrienti del Builder utilizzano l'approccio concatenato del metodo per restituire l'oggetto su cui sono stati chiamati, in modo che più metodi possano essere concatenati in modo conveniente su una singola riga. In realtà, il secondo estratto potrebbe benissimo essere scritto in questo modo:

NutritionFacts.Builder builder = new NutritionFacts.Builder(240, 8); 
builder.calories(100); 
builder.sodium(35); 
builder.carbohydrate(27); 
NutritionFacts cocaCola = builder.build();