2014-12-28 7 views
5

Come noto, quando si effettua l'accumulo, "reduce" restituisce sempre un nuovo oggetto immutabile mentre "collect" apporta le modifiche su un oggetto mutabile.Java 8 collect vs reduce

Tuttavia, quando accidentalmente assegno un riferimento al metodo per ridurre e raccogliere il metodo, esso viene compilato senza errori. Perché?

Date un'occhiata al codice seguente:

public class Test { 
    @Test 
    public void testReduce() { 

     BiFunction<MutableContainer,Long,MutableContainer> func = 
      MutableContainer::reduce; 

     // Why this can compile? 
     BiConsumer<MutableContainer,Long> consume = 
      MutableContainer::reduce; 

     // correct way: 
     //BiConsumer<MutableContainer,Long> consume = 
     // MutableContainer::collect; 


     long param=10; 

     MutableContainer container = new MutableContainer(0); 


     consume.accept(container, param); 
     // here prints "0",incorrect result, 
     // because here we expect a mutable change instead of returning a immutable value 
     System.out.println(container.getSum()); 

     MutableContainer newContainer = func.apply(container, param); 
     System.out.println(newContainer.getSum()); 
    } 
} 

class MutableContainer { 
    public MutableContainer(long sum) { 
     this.sum = sum; 
    } 

    public long getSum() { 
     return sum; 
    } 

    public void setSum(long sum) { 
     this.sum = sum; 
    } 

    private long sum; 

    public MutableContainer reduce(long param) { 
     return new MutableContainer(param); 
    } 

    public void collect(long param){ 
     this.setSum(param); 
    } 
} 

risposta

5

In sostanza, la questione si riduce a questo: BiConsumer è un'interfaccia funzionale la cui funzione è dichiarata in questo modo:

void accept(T t, U u) 

Hai dato è un riferimento al metodo con i parametri corretti, ma il tipo di ritorno errato:

public MutableContainer reduce(long param) { 
    return new MutableContainer(param); 
} 

[Il parametro T è in realtà l'oggetto this quando viene chiamato reduce, poiché reduce è un metodo di istanza e non un metodo statico. Ecco perché i parametri sono corretti.] Il tipo restituito è MutableContainer e non void, tuttavia. Quindi la domanda è: perché il compilatore lo accetta?

Intuitivamente, credo che questo è dovuto al fatto un riferimento metodo è, più o meno, equivalente ad una classe anonima che assomiglia a questo:

new BiConsumer<MutableContainer,Long>() { 
    @Override 
    public void accept(MutableContainer t, Long u) { 
     t.reduce(u); 
    } 
} 

noti che t.reduce(u) restituirà un risultato. Tuttavia, il risultato viene scartato. Poiché è corretto chiamare un metodo con un risultato e scartare il risultato, penso che, per estensione, è per questo che è OK utilizzare un riferimento al metodo in cui il metodo restituisce il risultato, per un'interfaccia funzionale il cui metodo restituisce void.

Legalisticamente, credo che il motivo sia JLS 15.12.2.5. Questa sezione è difficile e io non capiscono fino in fondo, ma da qualche parte in questa sezione si dice

Se e è un espressione esatta metodo di riferimento ... R è vuoto.

dove, se ho letto correttamente, R 2 è il tipo di risultato di un metodo di interfaccia funzionale. Penso che questa sia la clausola che consente di utilizzare un riferimento al metodo non void in cui è previsto un riferimento al metodo void.

(Edit: Come Ismail sottolineato nei commenti, JLS 15.13.2 può essere la clausola corretta qui; parla di un riferimento metodo congruente con un tipo di funzione, ed una delle condizioni per questo è che il risultato di il tipo di funzione è void.)

Ad ogni modo, si spera che spieghi perché è compila. Ovviamente, il compilatore non può sempre dire quando stai facendo qualcosa che produce risultati errati.

+2

Penso che [15.13.2] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.2) sia più rilevante in questo caso - - "Un'espressione di riferimento del metodo è compatibile in un contesto di assegnazione [...] con un tipo di destinazione T [...] se [...]] Il risultato del tipo di funzione [di T] è vuoto " –

+0

ok, penso che la spiegazione sia abbastanza chiara. Non sono sicuro del motivo per cui questo è accettato, poiché causerà errori nella maggior parte dei casi. – ning

+6

La motivazione è corretta: proprio come è possibile invocare un metodo e scartare il tipo restituito, è possibile convertire un metodo risultante in un'interfaccia funzionale che restituisce un vuoto. Il commentatore precedente teme che questa flessibilità inviti bug, ma il l'alternativa era peggiore - non si poteva nemmeno convertire 'aList :: add' in un' Consumer', dato che 'add' restituisce qualcosa - e questo sarebbe davvero fastidioso, se non si potesse dire' x.for Ogni (lista: : add) '! O una soluzione stava per infastidire qualcuno, in questo modo è stato giudicato il minore dei mali e, in realtà, non era nemmeno una chiamata ravvicinata –