Java 8函数式接口(Consumer)的额外参数

3

有没有办法给Java 8中的Consumer参数化呢?我想要一个可重用的Consumer,在使用它的地方可以放置额外的参数。

List<DateTime> dates = new ArrayList<DateTime>();
Set<Alarm> alarms = new HashSet<Alarm>();

Consumer<Entry> entryConsumer1 = entry -> {
    LocalTime time = entry.getDate().toLocalTime();
    Alarm alarm = new Alarm(time, calendar1.getPattern());
    alarms.add(alarm);
    dates.add(entry.getDate());
};

Consumer<Entry> entryConsumer2 = entry -> {
    LocalTime time = entry.getDate().toLocalTime();
    Alarm alarm = new Alarm(time, calendar2.getPattern());
    alarms.add(alarm);
    dates.add(entry.getDate());
};

calendar1.generateEntries(criteria).forEach(entryConsumer1);
calendar2.generateEntries(criteria).forEach(entryConsumer2);

calendar1和calendar2是同一类型

如您所见,这两个消费者只有一个参数不同。是否可能简化此代码/不重复?

2个回答

5

为您的消费者创建一个工厂方法:

public Consumer<Entry> createConsumer(Calendar calendar, Set<Alarm> alarms, List<DateTime> dates) {
    return entry -> {
        LocalTime time = entry.getDate().toLocalTime();
        Alarm alarm = new Alarm(time, calendar.getPattern());
        alarms.add(alarm);
        dates.add(entry.getDate());
    }
}

然后像这样使用它:
calendar1.generateEntries(criteria).forEach(createConsumer(calendar1, alarms, dates));
calendar2.generateEntries(criteria).forEach(createConsumer(calendar2, alarms, dates));

同时:使用带有副作用的lambda表达式或函数(例如将闹钟添加到闹钟集合中或将日期添加到lambda内部的日期列表中)是不好的实践(违反了函数式编程原则)。更加函数式的方法是使用转换方法,例如map,然后收集结果。例如:

Set<Alarm> alarms = calendar1.generateEntries(criteria)
    .map(entry -> new Alarm(entry.getDate().toLocalTime(), calendar1.getPattern()))
    .collect(Collectors.toSet());

1
虽然使用collect更好,但Java没有一种很好的方法可以从一个流中产生两个结果,即alarmsdates - Peter Lawrey
3
尽管“函数式编程原则”反对过多依赖副作用,但需要注意的是,函数式接口Consumer<T>的存在是为了建模具有副作用的计算。它不返回任何内容,因此,如果它代表的是纯函数,那么这种抽象就是无用的。(确实,像forEach()这样的stream方法通常在collect()reduce()可以更好地完成工作时使用,但是假设使用Consumer是正确的选择,那么批评实现Consumer中固有的副作用就毫无意义了。) - Brian Goetz
@BrianGoetz 感谢您的评论,我理解了。我想提一下这个问题,因为我经常看到没有函数式编程经验的人们使用带有副作用的lambda表达式来执行所有操作,而不是考虑“函数式方式”。 - Jesper

4
我们所做的是更改API,但这里并不是一个简单的选项。
BiConsumer<String, Entry> entryConsumer = (pattern, entry) -> {
    LocalTime time = entry.getDate().toLocalTime();
    Alarm alarm = new Alarm(time, pattern);
    alarms.add(alarm);
    dates.add(entry.getDate());
};

并且可以像下面这样调用API(其中第一个参数将传递给entryConsumer的每次调用):

.forEach(calendar1.getPattern, entryConsumer);

如果您不能更改API,您可以使用以下方法:

calendar1.generateEntries(criteria).forEach(e -> entryConsumer(calendar1, e));
calendar2.generateEntries(criteria).forEach(e -> entryConsumer(calendar2, e));

public static void entryConsumer(Calendar cal, Entry e) {
    LocalTime time = entry.getDate().toLocalTime();
    Alarm alarm = new Alarm(time, cal.getPattern());
    alarms.add(alarm);
    dates.add(entry.getDate());
};

3
我更喜欢使用“external”方法,但您也可以在不改变API的情况下使用BiConsumer,因为它有一个:.forEach(e -> entryConsumer.accept(calendar1, e)) - zapl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接