2014-12-28 13 views
5

Wie bekannt, gibt "reduce" bei der Akkumulation immer ein neues unveränderliches Objekt zurück, während "collect" Änderungen an einem veränderlichen Objekt vornimmt.Java 8 collect vs. reduce

Wenn ich jedoch versehentlich eine Methodenreferenz sowohl für die Methode "Reduzieren" als auch für die Methode "Sammeln" zuweist, wird sie ohne Fehler kompiliert. Warum?

Werfen Sie einen Blick auf den folgenden Code:

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

Antwort

5

Grundsätzlich verringert sich die Frage dazu: BiConsumer eine funktionale Schnittstelle ist deren Funktion wie folgt erklärt:

void accept(T t, U u) 

Sie gegeben haben Es ist eine Methodenreferenz mit den richtigen Parametern, aber der falsche Rückgabetyp:

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

[Der T Parameter ist eigentlich das this Objekt, wenn reduce aufgerufen wird, da reduce eine Instanzmethode und keine statische Methode ist. Deshalb sind die Parameter korrekt.] Der Rückgabetyp ist jedoch und nicht void. Die Frage ist also, warum akzeptiert der Compiler das?

Intuitiv, ich denke, das liegt daran, eine Methode Referenz, mehr oder weniger, das entspricht eine anonyme Klasse, die wie folgt aussieht:

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

Beachten Sie, dass t.reduce(u) ein Ergebnis zurück. Das Ergebnis wird jedoch verworfen. Da es OK ist, eine Methode mit einem Ergebnis aufzurufen und das Ergebnis zu verwerfen, denke ich, dass es daher sinnvoll ist, eine Methodenreferenz zu verwenden, bei der die Methode das Ergebnis für eine funktionale Schnittstelle zurückgibt, deren Methode void zurückgibt.

Legal, glaube ich, der Grund ist in JLS 15.12.2.5. Dieser Abschnitt ist schwierig und ich es nicht verstehen, aber irgendwo in diesem Abschnitt heißt es

Wenn e eine genaue Methode Referenz Ausdruck ist ... R nichtig ist.

wo, wenn ich es richtig gelesen, R 2 ist der Ergebnistyp einer funktionellen Schnittstellenverfahren. Ich denke, das ist die Klausel, die es ermöglicht, eine nicht-void-Methodenreferenz zu verwenden, wenn eine void-Methodenreferenz erwartet wird.

(Edit: Wie Ismail in den Kommentaren darauf hingewiesen, kann JLS 15.13.2 die richtige Klausel hier sein, es spricht von einem Verfahren Bezug mit einem Funktionstyp deckungsgleich, und eine der Voraussetzungen dafür ist, dass das Ergebnis der der Funktionstyp ist void.)

Wie auch immer, das sollte hoffentlich erklären, warum es kompiliert. Natürlich kann der Compiler nicht immer feststellen, wenn Sie etwas tun, das zu falschen Ergebnissen führt.

+2

Ich denke, [15.13.2] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.2) ist in diesem Fall relevanter - - "Ein Methodenreferenzausdruck ist in einem Zuordnungskontext [...] mit einem Zieltyp T [...] kompatibel, wenn [...]] Das Ergebnis des Funktionstyps [von T] ist ungültig " –

+0

ok, ich denke, die Erklärung ist ziemlich klar. Nicht sicher, warum dies akzeptiert wird, da es in den meisten Fällen einen Fehler verursachen wird. Starke Typprüfung sollte diese Art von Fehler verhindern – ning

+6

Sie haben Recht auf die Motivation.Wie Sie eine Methode aufrufen und den Rückgabetyp verwerfen können, können Sie eine result-pairing-Methode in eine void-zurückkehrende funktionale Schnittstelle konvertieren.Der vorherige Kommentator befürchtet, dass diese Flexibilität zu Fehlern führt, aber die Alternative war schlimmer - man konnte "aList :: add" nicht einmal zu einem "Consumer" konvertieren, da 'add' etwas zurückgibt - und das wäre wirklich ärgerlich, wenn man nicht' x.forEach sagen könnte (liste: : add) '! Entweder Lösung würde jemanden ärgern. Dieser Weg wurde als das kleinere Übel angesehen. Und wirklich, es war nicht einmal ein knapper Ruf. –