如何根据另一个列表中对象的属性创建新列表

98
Imagine that 我有一系列特定对象的列表:
List<Student>

我需要生成另一个列表,其中包括上述列表中学生id:

List<Integer>

如何避免使用循环,是否可以通过使用apache collectionsguava来实现?

哪些方法对我的情况有用?


嘿,我刚刚找到了这个:https://dev59.com/0XRB5IYBdhLWcg3wCjnO - javatar
Jon的答案很棒,但是如果你仔细看,它也使用了一个循环。任何解决方案都将使用循环 - 尽管可能对您不可见,但在内部它将会使用。 - hage
有人必须应用循环来完成它。不是你就是你可能使用的某个库。 - sudmong
实际上,Jon的答案并不适用于我的情况,因为我不想使用for循环。我认为还有更方便的方法,只是等待我找到它 :) - javatar
8个回答

209

Java 8的方法:

List<Integer> idList = students.stream().map(Student::getId).collect(Collectors.toList());

3
如果我想要一个元组列表怎么办?类似于 [(Id, name),(Id, name)]。 - Coder95
2
并非所有的 Android API 都支持 1.8 版本的功能。特别地,java.util.stream 直到 API 24 才被支持。 - The Black Horse

42

使用Guava库,您可以像这样使用Function -

private enum StudentToId implements Function<Student, Integer> {
        INSTANCE;

        @Override
        public Integer apply(Student input) {
            return input.getId();
        }
    }

你可以使用这个函数将学生列表转换为id,例如 -

Lists.transform(studentList, StudentToId.INSTANCE);
当然,它会循环以提取所有ID,但请记住,Guava方法返回视图(View),并且只有在尝试迭代List<Integer>时才会应用Function。如果您不迭代,则永远不会应用循环。
注意:请记住这是视图(View),如果要多次迭代,最好将其复制到其他List<Integer>中。
ImmutableList.copyOf(Iterables.transform(students, StudentToId.INSTANCE));

我认为单独定义一个函数是不够优雅的。我认为这也可以匿名地一步完成。 - Marc
你如何提取List中的字符串(浮点数等)成员? - sepehr

22

感谢Premraj提供这个很棒的替代方案,我已经点赞了。

我使用过apache CollectionUtils和BeanUtils。因此,我对以下代码的性能感到满意:

List<Long> idList = (List<Long>) CollectionUtils.collect(objectList, 
                                    new BeanToPropertyValueTransformer("id"));

值得一提的是,我将比较Guava (由Premraj提供)和我之前使用的CollectionUtils的性能,并决定哪个更快。


3
无论性能如何,我更喜欢使用Guava而不是Apache Collections,原因是它更现代化,支持泛型,且在转换过程中不使用反射等。总体来说,Guava比Apache Collections更加现代化 - 详见此处 - Premraj
1
是的,但实际上,我认为强制要求为每种要转换的对象类型声明额外的枚举声明来限制其使用并不是一个好主意。 - javatar
这取决于你的设计 - 你可以有一个名为 Identifiable 的接口,它定义了 getId() 方法,然后你可以使用这个单例模式来通用地提取 Id。 - Premraj
2
Guava 明显比 Bean Utils 更高效,因为后者使用了反射。 - chendu
2
在构造函数new BeanToPropertyValueTransformer("id")中使用属性名称作为字符串是我绝对会避免的。重构将会很困难! 但是我正在寻找解决方法来解决这个问题。同时,我将采用Guava解决方案,虽然冗长但安全可靠。 - redochka

10

Java 8 lambda表达式解决方案:

List<Integer> iDList = students.stream().map((student) -> student.getId()).collect(Collectors.toList());

1
如果Java版本小于1.8,则使用Apache CollectionUtils。例如:Collection<String> firstnames = CollectionUtils.collect(userlist, TransformerUtils.invokerTransformer("getFirstname")); - Alexei

6
如果有人几年后来到这里:
List<String> stringProperty = (List<String>) CollectionUtils.collect(listOfBeans, TransformerUtils.invokerTransformer("getProperty"));

你刚刚救了我 :) - Behrad3d

2

1
你可以使用Eclipse Collections来实现这个目的。
Student first = new Student(1);
Student second = new Student(2);
Student third = new Student(3);

MutableList<Student> list = Lists.mutable.of(first, second, third);
List<Integer> result = list.collect(Student::getId);

System.out.println(result); // [1, 2, 3]

-7

如果没有循环,这是数学上不可能完成的。为了创建一个映射F,将离散值集合映射到另一个离散值集合,F必须对原始集合中的每个元素进行操作。(基本上需要使用循环来完成这个过程。)

话虽如此:

你为什么需要一个新列表?你可能正在错误地解决问题。

如果你有一个Student列表,那么当你遍历这个列表时,只需要再迭代一两步就可以遍历学生的ID号码。

for(Student s : list)
{
    int current_id = s.getID();
    // Do something with current_id
}

如果您有其他类型的问题,请在评论/更新问题后,我们会尽力帮助您。

首先感谢您的回复。然而,您说需要一个新列表是错误的方式,这是非常戏剧性的。因为它取决于需求的容量和范围。因此,您的方法是错误的 :) 再次提到我的“无需循环即可实现”的能力,是为了更方便地避免使代码难以阅读和性能低下。无论如何,我已经找到了一种实现它的方法,稍后会分享。 - javatar
我并没有说这是错误的方法,但根据你要解决的问题,它可能是错误的方法。 - Zéychin

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