使用Guava高效地转换泛型数组

4
我正在使用Java 7,并且正在在Guava API中搜索一种方法,可以将函数应用于数组而无需先将其转换为Collection。我愿意为此创建自己的类,但我不想重复造轮子。因此,这是目前我发现的关于如何使用Guava将函数应用于数组的总结:
Integer[] someNumbers = new Integer[]{1, 2, 3};
Integer[] returnedNumbers = Collections2.transform(Arrays.asList(someNumbers), squareNumberFunction).toArray(new Integer[0]);

assertThat(returnedNumbers).isEqualTo(new Integer[]{1, 4, 9});//Using AssertJ here

但我希望能够做到这样的事情:
Integer[] someNumbers = new Integer[]{1, 2, 3};
Integer[] returnedNumbers = Arrays.transform(someNumbers, squareNumberFunction);

assertThat(returnedNumbers).isEqualTo(new Integer[]{1, 4, 9});

理想情况下,我所说的功能应该是类型安全的。

编辑

为了更进一步地解释问题:

  • 我所说的数组不是原始数组,它们引用复杂对象(我只使用整数来很容易地举例说明我所说的内容)。
  • 我无法控制接收发送结构,它们数组(如果您认为这有助于您更好地理解问题,请想象一个遗留代码的情况)。
  • 转换数组和访问它们时,效率是必须的。

4
泛型和数组不太搭配。为什么首先要使用数组而不是列表?你应该这样做:优先选择集合而不是数组。 - JB Nizet
是的,不幸的是,对于这种情况我确实需要使用数组,并且在转换数组时我需要效率(可以是任何类型)。 - Rodrigo Quesada
2
使用集合可以提高效率,因为它允许避免复制。Guava 是关于集合的,据我所知,你在 Guava 中找不到想要的方法。但是没有什么禁止你编写一个包含你在问题中发布的代码的包装器。 - JB Nizet
3
你可以创建一个仅是另一个列表的视图的列表。但你不能用数组实现同样的功能,唯一的选择是复制一个新数组。这就是为什么集合可以更快的原因。 - JB Nizet
1
通过避免复制,可以从List<Integer>创建List<String>。例如: List <String> listOfStrings = Lists.transform(listOfIntegers, i -> i.toString()),但是这在数组中是不可能的。您无法创建一个String []数组,该数组是Integer[]数组的视图。 - JB Nizet
显示剩余5条评论
2个回答

5

数组比集合快很多并不是真的,因为有些集合只是数组的包装器,所以你不会失去任何东西,而且可以得到两者的最佳结合

回到你的情况,我认为你可以使用Arrays.asList(由你的数组支持的列表)或ImmutableList(如果你不需要增长数组),无论哪种方式都可以,但不要使用Arrays。第一个迹象是你必须编写不必要的代码(你的答案和将来更多),这些代码将来需要维护/调整,在我的经验中,这是不值得的。

Java是一种高级语言,这样微小的优化应该留给Java,出于所有良好的原因,但即使在这之后,如果你仍然有疑问,我建议你编写一个微基准来验证你的假设。


另一方面,我不需要“两全其美”的解决方案,因为我的问题只存在于“一个世界”中(我在帖子中添加了进一步的说明,所以您可能需要检查一下)。 - Rodrigo Quesada
另外,我不打算编写微基准测试,因为我非常确定没有什么东西能够击败迭代数组并按顺序访问其元素(很多,很多次)。 - Rodrigo Quesada
3
@RodrigoQuesada,我相信在原生的C语言中,没有什么能够比得过数组访问。但在像Java这样的托管语言中,并且有JIT编译器的情况下,我不太确定。我建议不要轻视微基准测试——它们是探讨这类问题的完美工具。 - Kirk Woll
1
@RodrigoQuesada,我认为你误以为JVM中的数组与C语言中的数组非常相似。在前者中,访问数组元素仍然需要通过JVM字节码进行 - 你并没有通过直接指针直接访问基础内存空间。 - Kirk Woll
1
根本问题在于你正在使用一个对象数组:在Java中,这意味着最好是一系列存储在单个内存块中的引用。这些引用可能指向任何其他地方,这意味着快速数组迭代带来的任何好处都会受到堆访问的阻碍。 - Kenogu Labz
显示剩余4条评论

4

好的,为了解决上述问题,我最终编写了自己的代码。因此,我希望其他人也会发现它有用,而不仅仅是使用集合(来吧,你为什么要这样做呢?)。

public static <A, B> B[] transform(Class<B> theReturnedValueType, Function<A, B> functionToApply, A... theValues) {

    B[] transformedValues = (B[]) Array.newInstance(theReturnedValueType, theValues.length);

    for (int i = 0; i < theValues.length; i++) {
        transformedValues[i] = functionToApply.apply(theValues[i]);
    }

    return transformedValues;
}

如何使用它的示例(实际上这是一个测试):

Integer[] returnedNumbers = ArrayTransformer.transform(Integer.class, squareNumberFunction, 1, 2, 3);

assertThat(returnedNumbers).isEqualTo(new Integer[]{1, 4, 9});

我想指出的是,示例代码使用了可变参数语法(这是一个测试,所以使用它是一种有趣的方式),但你显然可以传递一个数组对象。
此外,我必须补充说明,在使用对反射性能敏感的环境(例如Android)时,需要小心使用此方法,因为会在运行时使用反射创建新数组(java.lang.reflect.newInstance(...))。另一方面,如果使用集合的toArray(T[])方法,并且传递的数组大小不足以容纳所有集合元素(在这种情况下,将在运行时分配新数组,就像我的代码一样),那么可能这段代码并不会造成问题,如果你按照我说的方式使用该方法(无论如何,你都可以轻松地更改此代码以适应你的需求)。

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