2014-12-28 2 views
5

Как известно, при выполнении накоплений «сокращение» всегда возвращает новый неизменяемый объект, а «collect» будет вносить изменения в изменяемый объект.Java 8 collect vs reduce

Однако, когда я случайно присваиваю одну ссылку методу методу уменьшения и сбора, он компилируется без каких-либо ошибок. Зачем?

Взгляните на следующий код:

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

ответ

5

В принципе, вопрос сводится к следующему: BiConsumer представляет собой функциональный интерфейс, функция которого объявлена ​​так:

void accept(T t, U u) 

Вы дали это ссылка на метод с правильными параметрами, но неправильный тип возврата:

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

[Параметр T на самом деле является объектом this, когда вызывается reduce, поскольку reduce - это метод экземпляра, а не статический метод. Вот почему параметры верны.] Тип возврата - MutableContainer, а не void. Итак, вопрос в том, почему компилятор принимает это?

Наглядно, я думаю, это потому, что эталонный метод является более или менее эквивалентно анонимный класс, который выглядит следующим образом:

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

Обратите внимание, что t.reduce(u) будет возвращать результат. Однако результат отбрасывается. Так как нормально вызывать метод с результатом и отбрасывать результат, я думаю, что, по расширению, именно поэтому вполне нормально использовать ссылку метода, где метод возвращает результат, для функционального интерфейса, метод которого возвращает void.

Юридически, я считаю, причина в JLS 15.12.2.5. Этот раздел трудно, и я не до конца понимаю, но где-то в этом разделе говорит

Если е точный метод выражения ссылки ... R является недействительным.

, где, если я прочитал это правильно, R 2 является типом результата функционального метода интерфейса. Я думаю, что это предложение, которое позволяет использовать ссылку на не-void-метод, где ожидается ссылка на метод void.

(Edit: Как Исмаил отметил в комментариях, JLS 15.13.2 может быть правильное положение здесь, это говорит о том, эталонный метод будучи конгруэнтны с типом функции, и одним из условий для этого является то, что в результате тип функции - void.)

В любом случае, это должно надеяться объяснить, почему это компилирует. Конечно, компилятор не всегда может сказать, когда вы делаете что-то, что приведет к неправильным результатам.

+2

Я думаю, что [15.13.2] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.2) более уместен в этом случае - - «Ссылочное выражение метода совместимо в контексте назначения [...] с целевым типом T [...], если [...] Результат типа функции [из T] недействителен « –

+0

ОК, я думаю, что объяснение достаточно ясное. Не уверен, почему это принято, поскольку в большинстве случаев это вызовет ошибку. Сильная проверка типов должна предотвратить такую ​​ошибку – ning

+6

Вы правы в мотивации. Так же, как вы можете вызвать метод и отказаться от типа возврата, вы можете преобразовать метод, основанный на результатах, в функциональный интерфейс, возвращающий пустоту. Предыдущий комментатор опасается, что эта гибкость вызывает ошибки, но альтернатива была хуже - вы даже не могли преобразовать 'aList :: add' в' Consumer', так как 'add' возвращает что-то - и это было бы очень неприятно, если вы не могли бы сказать' x.forEach (list: : add) '! Любое решение должно было раздражать кого-то. Этот путь был признан меньшим количеством зла. И действительно, это был даже не звонок. –

Смежные вопросы