【问题标题】:Guava - How to apply a function that returns Void on an IterableGuava - 如何应用在 Iterable 上返回 Void 的函数
【发布时间】:2012-09-07 18:05:03
【问题描述】:

我只是想知道在Iterable/Collection 上应用返回Void 的函数的最佳方法是什么?

我的用例是:

  • 我有一个Animal 对象列表
  • 我想对列表中的每个动物调用eat() 函数

我有一个Function<Animal,Void>,它调用input.eat();

原来当我打电话时:

Collections2.transform(animalList,eatFunction);

我不觉得这很优雅,因为我不是在寻找转换,而只是寻找没有任何输出的应用程序。 最糟糕的是,它甚至不起作用,因为 Guava 转换正在返回视图。

效果很好的是:

Lists.newArrayList( Collections2.transform(animalList,eatFunction) );

但这并不优雅。 使用 Guava 以函数方式将 Void 函数应用于 Iterable/Collection 的最佳方法是什么?

编辑

【问题讨论】:

  • 只需执行传统的 for-each 循环并调用 animal.eat()?如果你不需要懒惰,不变形,那就根本不要使用 Guava 的变形。
  • 我不想使用转换,这是我所要求的。而且使用 Guava 函数式风格将调用应用于每个对象仍然更加简洁易读
  • 我能说什么 - 在这种情况下应用更具可读性是主观的。 And Guava team members think it's not.
  • @Sebastien Lorber:您的eat 方法不返回任何内容,因此从功能的角度来看,调用它毫无意义。您要做的是将功能样式应用于程序性事物,这是不受支持的。如果没有闭包,那么函数式风格就是 Java 太冗长了,难怪它的支持仅限于最适合的地方。

标签: java functional-programming guava


【解决方案1】:

你觉得哪个更优雅?一个普通的 for 循环:

for (Animal animal : animalList)
    animal.eat();

还是“通过以函数式风格编写程序操作来扭曲程序语言”的疯狂?

static final Function<Animal, Void> eatFunction = new Function<Animal, Void>() {
    @Override
    public Void apply(Animal animal) {
        animal.eat();
        return null; // ugly as hell, but necessary to compile
    }
}

Lists.newArrayList(Collections2.transform(animalList, eatFunction));

我会投票给第一个案例。

如果您真的想以函数式风格编写程序,我建议您切换到另一种 JVM 语言。

对于这种情况,

Scala 可能是一个不错的选择:

animalList.foreach(animal => animal.eat)

甚至使用_ 占位符的更短变体:

animalList.foreach(_.eat)

编辑:

在 Eclipse 中尝试代码后,我发现我必须将 return null 语句添加到 eatFunction,因为 1) Voidvoid 不同,并且 2) 它是不可实例化的。比预想的还要丑! :)

同样从性能的角度来看,使用上面的一些复制构造函数调用惰性函数也会毫无意义地分配内存。创建一个与仅填充空值的animalList 大小相同的ArrayList,只是为了立即进行垃圾回收。

如果你真的有一个用例,你想传递一些函数对象并将它们动态应用到一些集合上,我会编写自己的函数式接口和一个 foreach 方法:

public interface Block<T> {
    void apply(T input);
}

public class FunctionUtils {
    public static <T> void forEach(Iterable<? extends T> iterable,
            Block<? super T> block) {
        for (T element : iterable) {
            block.apply(element);
        }
    }

}

然后你可以类似地定义一个void(小写)函数:

static final Block<Animal> eatFunction = new Block<Animal>() {
    @Override
    public void apply(Animal animal) {
        animal.eat();
    }
};

并像这样使用它:

FunctionUtils.forEach(animalList, eatFunction);
// or with a static import:
forEach(animalList, eatFunction);

【讨论】:

  • 当然,类函数类的被黑声明很丑陋,但是在需要应用参数化操作或一系列操作的情况下,如果没有实现访问者模式和类似函子的对象?所以实际上有合理的用例。同样的论点也反对在函数式 API 中使用冗长的真实函数声明,即使是 Guava 团队自己,但他们还是实现了 Function,因为他们意识到有需要它的用例,只要首先仔细权衡缺点。
  • 满足这种需求的一个用例是,当您将一个 Optional 转换为另一个 Optional,然后希望仅在链的最后一个对象存在时才对它应用函数
  • @Natix:您可以通过将Lists.newArrayList 替换为DummyUtils.newEmptyStayingCollection 来优化ArrayList,将add 实现为无操作(恕我直言,这并不违反Collection 合同)。
  • @maaartinus 这只是一个可怕的黑客攻击。 :)
  • @Natix:不,这是一个零容量的非阻塞队列(或集合)。 :D:D
【解决方案2】:

我只是在寻找同样的东西并找到了一个 Java Consumer 接口。 在您的情况下,它将是:

final Consumer<Animal> action = new Consumer<Animal>() {
    @Override
    public void accept(Animal animal) {
        animal.eat();
    }
};    
...
FluentIterable.from(animals).forEach(action);

【讨论】:

  • @dzik...据我所知,FluentIterable 没有forEach 功能...您可能是想在最后一行使用.stream()
  • 是的,forEach() 在 Guava 中不存在(很遗憾)。
  • 很好的答案,只是请注意,在 2012 年首次提出该问题时,这不可用。今天,它可能是该问题最明显和最好的答案。
【解决方案3】:

正如其他人指出的那样,Guava 团队有一个观点,阻止这样做。如果您正在寻找其他类似函子的 API 来做您想做的事情,您可以查看基于Functional Java's Effect、或Jedi's Command class、或Play! framework's F.Callback、或Commons Collections4's Closure [稍后编辑:] 或Java 8+ Consumer 的接口.

【讨论】:

    猜你喜欢
    • 2019-10-08
    • 2011-04-28
    • 2016-08-28
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    • 2013-11-26
    • 1970-01-01
    • 2017-02-23
    相关资源
    最近更新 更多