Java - 将一个对象列表映射到一个值为它们属性属性的列表

53

我有一个名为ViewValue的类,定义如下:

class ViewValue {

private Long id;
private Integer value;
private String description;
private View view;
private Double defaultFeeRate;

// getters and setters for all properties
}

我在代码中需要将一个ViewValue实例列表转换为一个包含相应ViewValue id字段值的列表。

我使用foreach循环来完成:

List<Long> toIdsList(List<ViewValue> viewValues) {

   List<Long> ids = new ArrayList<Long>();

   for (ViewValue viewValue : viewValues) {
      ids.add(viewValue.getId());
   }

   return ids;

}

这个问题有更好的解决方法吗?

8个回答

62

使用Java 8,我们可以用一行代码实现它。

List<Long> ids = viewValues.stream().map(ViewValue::getId).collect(Collectors.toList());

更多信息请参考: Java 8 - Streams


幸运的是,随着我们的开发,事情变得更加简化了:) - Radhakrishna Sharma Gorenta
7
好的,让我直接说吧:我会加上注释,说明这需要使用Java8,而当时写作时还不可用。这个答案实际上是对7年前所写内容的一个巨大进步。 - Alexander Oh
你的回答太棒了。 - Salathiel Genèse

33
你可以使用Commons BeanUtils和Collections在一行代码中完成它:
(为什么要自己编写代码,当别人已经为你编写好了呢?)
import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.collections.CollectionUtils;

...

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

5
对于“为什么要自己写代码,别人已经为你做好了?”的评论点赞。这是我们每天都面临的问题——不得不为已经有解决方案的问题创建新的解决方案。 - divesh premdeep
PropertyUtils.getProperty(object, propertyName); 在底层使用了反射,这会增加额外的性能成本。因此,我会改用Jon Skeet的方法。 - Juan Carlos Blanco Martínez
4
我唯一不喜欢的是缺乏类型安全性——如果viewValue.id字段被重命名,代码仍然可以编译,但在运行时会失败。 - Steve Chambers

32
使用Google Collections。示例:
    Function<ViewValue, Long> transform = new Function<ViewValue, Long>() {
        @Override
        public Long apply(ViewValue from) {
            return from.getId();
        }
    };
    List<ViewValue> list = Lists.newArrayList();
    List<Long> idsList = Lists.transform(list, transform);

更新:

在Java 8中,您不需要Guava。您可以执行以下操作:

import com.example.ViewValue;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

Function<ViewValue, Long> transform = ViewValue::getId;
List<ViewValue> source = new ArrayList<>();
List<Long> result = source.stream().map(transform).collect(Collectors.toList());

或者只需:

List<ViewValue> source= new ArrayList<>();
List<Long> result = source.stream().map(ViewValue::getId).collect(Collectors.toList());

下一次更新(在 Javaslang 更名为 Vavr 后的最后一次更新):

目前值得提到的是使用 Javaslang 库 (http://www.javaslang.io/) Vavr 库 (http://www.vavr.io/) 的解决方案。我们假设有一个包含真正对象的列表:

List<ViewValue> source = newArrayList(new ViewValue(1), new ViewValue(2), new ViewValue(2));

我们可以使用Javaslang库中的List类进行转换(长期来看,collect方法不太方便):

List<Long> result = io.vavr.collection.List.ofAll(source).map(ViewValue::getId).toJavaList();

只使用Javaslang列表,您就能看到它的威力:

io.vavr.collection.List<ViewValue> source = javaslang.collection.List.of(new ViewValue(1), new ViewValue(2), new ViewValue(3));
io.vavr.collection.List<Long> res = source.map(ViewValue::getId);

我鼓励你查看该库中可用的集合和新类型(我特别喜欢Try类型)。您可以在以下地址找到文档:http://www.javaslang.io/javaslang-docs/ http://www.vavr.io/vavr-docs/

附注:由于Oracle公司和“Java”一词在名称中,他们不得不将库名从javaslang改为其他名称。他们决定更名为Vavr。


27

编辑:本答案基于这样一个想法,即您需要在代码的其他地方针对不同实体和不同属性执行类似的操作。如果您需要通过ID将ViewValues列表转换为Longs列表,则应坚持使用原始代码。但是,如果您想要更具可重用性的解决方案,请继续阅读...

我会声明一个投影的接口,例如:

public interface Function<Arg,Result>
{
    public Result apply(Arg arg);
}

然后您可以编写一个通用的转换方法:

public <Source, Result> List<Result> convertAll(List<Source> source,
    Function<Source, Result> projection)
{
    ArrayList<Result> results = new ArrayList<Result>();
    for (Source element : source)
    {
         results.add(projection.apply(element));
    }
    return results;
}

你可以这样定义简单的投影:

private static final Function<ViewValue, Long> ID_PROJECTION =
    new Function<ViewValue, Long>()
    {
        public Long apply(ViewValue x)
        {
            return x.getId();
        }
    };

然后就像这样应用它:

List<Long> ids = convertAll(values, ID_PROJECTION);

(显然使用K&R大括号风格和更长的行可以使投影声明稍微变短一些:)

坦白地说,如果使用lambda表达式,所有这些都会更好,但没关系...


6
非常Java风格的解决方案,但并不是好的方式。它并没有真正减少代码的数量或复杂度,是吧? - Michael Borgwardt
1
我个人认为它更像LINQ而不是Java。我宁愿创建转换代码然后隔离投影方面,但如果你愿意多次复制转换逻辑,那也可以。当然,使用lambda表达式会更好。 - Jon Skeet
1
这不是类似于Java中的Comparator吗? - Adeel Ansari
1
并不是 - 我记得那只能从 int 进行转换。然而,我采用了可重用的思路,这可能并非必需 - 我正在编辑我的答案以反映这一点。 - Jon Skeet
(注意,我的“很多次”评论是假设这将适用于不同的属性等,而不仅仅是一个。) - Jon Skeet
显示剩余2条评论

4

我已经为这个用例实现了一个小型的功能库。其中一个方法具有以下签名:

<T> List<T> mapToProperty(List<?> objectList, String property, Class<T> returnType)

该方法接收一个字符串并使用反射创建一个对属性的调用,然后返回由objectList支持的列表,其中get和iterator使用此属性调用实现。

mapToProperty函数是基于一个通用的map函数实现的,该函数接受一个Function作为映射器,就像另一篇帖子所描述的那样。非常有用。

我建议您学习基本的函数式编程,特别是查看Functors(实现map函数的对象)。

编辑:反射真的不必太昂贵。 JVM在这个领域有了很大的改进。只需确保编译调用一次并重复使用即可。

编辑2:示例代码

public class MapExample {
    public static interface Function<A,R>
    {
        public R apply(A b);
    }

    public static <A,R> Function<A,R> compilePropertyMapper(Class<A> objectType, String property, Class<R> propertyType)
    {
        try {
            final Method m = objectType.getMethod("get" + property.substring(0,1).toUpperCase() + property.substring(1));

            if(!propertyType.isAssignableFrom(m.getReturnType()))
                throw new IllegalArgumentException(
                    "Property "+property+" on class "+objectType.getSimpleName()+" is not a "+propertyType.getSimpleName()
                );

            return new Function<A,R>() 
            {
                @SuppressWarnings("unchecked")
                public R apply(A b)
                {
                    try {
                        return (R)m.invoke(b);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                };
            };

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T1,T2> List<T2> map(final List<T1> list, final Function<T1,T2> mapper)
    {
        return new AbstractList<T2>()
        {
            @Override
            public T2 get(int index) {
                return mapper.apply(list.get(index));
            }

            @Override
            public int size() {
                return list.size();
            }
        };
    }

    @SuppressWarnings("unchecked")
    public static <T1,T2> List<T2> mapToProperty(List<T1> list, String property, Class<T2> propertyType)
    {
        if(list == null)
            return null;
        else if(list.isEmpty())
            return Collections.emptyList();

        return map(list,compilePropertyMapper((Class<T1>)list.get(0).getClass(), property, propertyType));
    }
}

3

You could use a wrapper:

public class IdList impements List<Long>
{
    private List<ViewValue> underlying;

    pubic IdList(List<ViewValue> underying)
    {
        this.underlying = underying;
    }

    public Long get(int index)
    {
        return underlying.get(index).getId()
    }

    // other List methods
}

虽然这更为繁琐,但这可以提高性能。

您也可以使用反射来通用地实现您和我的解决方案,但这对性能非常不利。

很遗憾,在Java中没有简短易行的通用解决方案。在Groovy中,您只需使用collect(),但我相信那也涉及到反射。


2
这取决于您对 List<Long>List<ViewValue> 的后续操作。例如,您可以通过创建自己的列表实现来获取足够的功能,该实现包装了 List<ViewValue>,使用迭代器实现来迭代 ViewValues 并返回 id。

2

以下是从对象列表的属性(以id作为键和某些属性作为值)填充地图的方法:

您可以像下面这样从对象列表的属性填充地图(例如将id作为键和某些属性作为值):

Map<String, Integer> mapCount = list.stream().collect(Collectors.toMap(Object::get_id, Object::proprty));

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