Java 中类似 Python 的列表推导

Posted

技术标签:

【中文标题】Java 中类似 Python 的列表推导【英文标题】:Python-like list comprehension in Java 【发布时间】:2010-10-28 07:13:32 【问题描述】:

既然 Java 不允许将方法作为参数传递,那么在 Java 中你使用什么技巧来实现类似 Python 的列表推导?

我有一个字符串列表 (ArrayList)。我需要使用一个函数来转换每个元素,以便获得另一个列表。我有几个函数将一个字符串作为输入并返回另一个字符串作为输出。如何制作一个可以将列表和函数作为参数提供的通用方法,以便我可以在处理每个元素时返回一个列表。从字面上看是不可能的,但是我应该使用什么技巧呢?

另一种选择是为每个较小的字符串处理函数编写一个新函数,该函数简单地循环整个列表,这有点不太酷。

【问题讨论】:

作为参考,您可以使用 Jython 或 Scala 在 JVM 上获取列表推导 SMH 同时阅读所有关于此的答案。在 Python 中,您可以轻松地在一行 40-60 个字符中编写列表推导式。这里提出的所有解决方案都是多行的,其中大多数比 Python 中的单行更长。 @ArtOfWarfare 我认为 yurez 的 Java 8 replaceAll 解决方案与 Pythonic 非常接近。 @Noumenon - 是的,看起来确实很棒。我想知道当我发表评论时我是否错过了它或其他什么。他的示例在 Python 中只缩短了 8 个字符,行数相同,并且可以说更容易阅读。加上其中 6 个字符来自 Java 的 toUpperCase 与 Python 的 upper,这与问题并不真正相关。 【参考方案1】:

在 Java 8 中,您可以使用方法引用:

List<String> list = ...;
list.replaceAll(String::toUpperCase);

或者,如果你想创建一个新的列表实例:

List<String> upper = list.stream().map(String::toUpperCase).collect(Collectors.toList());

【讨论】:

问题是 7 岁,那时 Java 8 还不存在。现在这应该是公认的答案;)【参考方案2】:

基本上,你创建一个 Function 接口:

public interface Func<In, Out> 
    public Out apply(In in);

然后将匿名子类传递给您的方法。

您的方法可以将函数原地应用于每个元素:

public static <T> void applyToListInPlace(List<T> list, Func<T, T> f) 
    ListIterator<T> itr = list.listIterator();
    while (itr.hasNext()) 
        T output = f.apply(itr.next());
        itr.set(output);
    

// ...
List<String> myList = ...;
applyToListInPlace(myList, new Func<String, String>() 
    public String apply(String in) 
        return in.toLowerCase();
    
);

或者新建一个List(基本上是创建一个从输入列表到输出列表的映射):

public static <In, Out> List<Out> map(List<In> in, Func<In, Out> f) 
    List<Out> out = new ArrayList<Out>(in.size());
    for (In inObj : in) 
        out.add(f.apply(inObj));
    
    return out;

// ...
List<String> myList = ...;
List<String> lowerCased = map(myList, new Func<String, String>() 
    public String apply(String in) 
        return in.toLowerCase();
    
);

哪个更可取取决于您的用例。如果您的列表非常大,就地解决方案可能是唯一可行的解​​决方案;如果您希望将许多不同的函数应用于同一个原始列表以生成许多衍生列表,您将需要map 版本。

【讨论】:

但是您要求我将每个小函数放在不同的类中,因为它们必须具有标准名称(在您的情况下为“应用”)。对吧? 不一定;您的匿名类可以简单地调用 apply() 中的小函数。这与 Java 在不冒险陷入反射的危险的情况下接近函数指针一样接近。 doToList 正在重新发明***。您在这里所做的是通常称为地图的糟糕设计。通常的接口是 public static List map(List, Func f);它所做的是生成另一个列表,而不是在适当的位置修改列表。如果您需要在不破坏引用的情况下修改原始列表,则只需执行 .clear() 后跟 addAll()。不要将所有这些结合在一种方法中。 @Pyrolistical:是的,我确实意识到了这一点。这个例子是专门为这个问题量身定做的,它没有指定是否需要一个新的列表。我想将其重命名为 applyToListInPlace 或其他名称会更清楚。 很好的答案,但我会推荐 apache commons CollectionUtils.transform 而不是自己滚动。仍然 +1 来解释这些概念。【参考方案3】:

Google Collections library 有很多类用于处理集合和迭代器,它们比普通 Java 支持的级别高得多,并且以功能方式(过滤器、映射、折叠等)。它定义了 Function 和 Predicate 接口以及使用它们来处理集合的方法,因此您不必这样做。它还具有方便的功能,使处理 Java 泛型变得不那么困难。

我还使用Hamcrest** 来过滤集合。

这两个库很容易与适配器类结合。


** 利益声明:我与人共同撰写了 Hamcrest

【讨论】:

出于好奇,为什么叫Hamcrest?我还是不知道它听起来好不好吃。 这是“匹配者”的字谜。【参考方案4】:

Apache Commons CollectionsUtil.transform(Collection, Transformer) 是另一种选择。

【讨论】:

不幸的是,它不是通用的。在这种情况下,这意味着一些额外的转换,但在其他情况下可能是一个问题。【参考方案5】:

我正在构建这个项目以用 Java 编写列表理解,现在是 https://github.com/farolfo/list-comprehension-in-java 中的概念证明

示例

//  x | x E 1,2,3,4 ^ x is even 
// gives 2,4

Predicate<Integer> even = x -> x % 2 == 0;

List<Integer> evens = new ListComprehension<Integer>()
    .suchThat(x -> 
        x.belongsTo(Arrays.asList(1, 2, 3, 4));
        x.is(even);
    );
// evens = 2,4;

如果我们想以某种方式转换输出表达式,例如

//  x * 2 | x E 1,2,3,4 ^ x is even 
// gives 4,8

List<Integer> duplicated = new ListComprehension<Integer>()
    .giveMeAll((Integer x) -> x * 2)
    .suchThat(x -> 
        x.belongsTo(Arrays.asList(1, 2, 3, 4));
        x.is(even);
    );
// duplicated = 4,8

【讨论】:

Python 列表理解的部分优点在于它有多短。您的 6 行长 Java 可以写成 [x * 2 for x in 1, 2, 3, 4 if x % 2 == 0]... 1 行 41 个字符。不知道你的代码有多少读起来很糟糕,因为 Java 是多么的冗长,而有多少是因为你的库做得不够简洁。 这里还是比很多其他解决方案好,我其实很喜欢这个【参考方案6】:

您可以对函数使用 lambda,如下所示:

class Comprehension<T> 
    /**
    *in: List int
    *func: Function to do to each entry
    */
    public List<T> comp(List<T> in, Function<T, T> func) 
        List<T> out = new ArrayList<T>();
        for(T o: in) 
            out.add(func.apply(o));
        
        return out;
    

用法:

List<String> stuff = new ArrayList<String>();
stuff.add("a");
stuff.add("b");
stuff.add("c");
stuff.add("d");
stuff.add("cheese");
List<String> newStuff = new Comprehension<String>().comp(stuff, (a) ->  //The <String> tells the comprehension to return an ArrayList<String>
    a.equals("a")? "1": 
            (a.equals("b")? "2": 
                (a.equals("c")? "3":
                    (a.equals("d")? "4": a
    )))
);

将返回:

["1", "2", "3", "4", "cheese"]

【讨论】:

【参考方案7】:
import java.util.Arrays;

class Soft
    public static void main(String[] args)
        int[] nums=range(9, 12);
        System.out.println(Arrays.toString(nums));
    
    static int[] range(int low, int high)
        int[] a=new int[high-low];
        for(int i=0,j=low;i<high-low;i++,j++)
            a[i]=j;
        
        return a;

    

希望能帮到你:)

【讨论】:

以上是关于Java 中类似 Python 的列表推导的主要内容,如果未能解决你的问题,请参考以下文章

Python学习3——Python的简单推导

Kotlin 中的 Python 列表、集合和映射推导等价物是啥?

Python常见数据结构-推导式

Python(15)--推导

python之生成器和列表推导式

详解Python中的生成器表达式(generator expression)