【问题标题】:Java generics and streamsJava 泛型和流
【发布时间】:2015-12-11 17:11:59
【问题描述】:

我正在构建一种框架以避免重复代码,并且在某个时候我需要将对象 Foo 的列表转换为对象 Bar 的列表。

我有扩展的数据库实体

public class BaseEntity {...}

以及扩展的表示对象

public class BaseDTO<T extends BaseEntity> {...}

所以

public class Foo extends BaseEntity {...}

public class Bar extends BaseDTO<A extends BaseEntity> {
    public Bar(Foo entity, Locale l) {
        ...
    }
}

现在使用流很容易将 Foo 列表转换为 Bar 列表

public abstract ClassThatUsesFooAndBar() {

    public List<Bar> convertFooToBar(List<Foo> list) {
        return list.stream().map(f -> new Bar(f, locale)).collect(...);
    }
}

但是,问题来了,这些 Foo 和 Bar 实际上是泛型(A 和 B),所以使用 Foo 和 Bar 的类实际上是 ClassThatUsesAandB&lt;A extends BaseEntity, B extends BaseDTO&gt;,所以该函数也必须是抽象的并作为样板代码实现使用正确的 A 和 B 实现,因为显然您无法实例化泛型类型。

有没有办法使用泛型/流/lambdas 来创建一个可以编写一次的函数,从而实现类不需要重新实现它?函数签名将是

public List<B> convertAToB(List<A> list);

我希望我已经足够清楚我需要什么,如果您需要进一步的解释,请询问

谢谢!

【问题讨论】:

  • 请不要使用单字母类名,因为很难将它们与类型参数区分开来。如果您对随机名称感到困惑,可以使用FooBarBaz
  • 实际上我使用了 A 和 B 因为在讨论结束时它们实际上是类型参数。我会尝试改写...
  • 您能更具体地了解泛型的使用吗?你写 A/B 扩展 AnObject,但在你的例子中 A/B 没有扩展任何东西
  • 我正在重新表述我的问题以使其更清楚:)

标签: java generics lambda java-stream


【解决方案1】:

我认为最简单的方法是使用 lambdas 进行转换。

public static <A,B> List<B> convertList(List<A> list, Function<A,B> itemConverter) {
    return list.stream().map(f -> itemConverter.apply(f)).collect(...);
}

然后你可以像这样使用它:

List<Bar> l = convertList(fooList,foo -> new Bar(foo.getBaz()));

或者,如果您愿意,可以将其提取到自己的命名类中:

public class Foo2BarConverter implements Function<Foo,Bar> {
    @Override
    public Bar apply(Foo f) {
       return new Bar(f.getBaz());
    }
}

顺便说一句,考虑到我们可以用流媒体做什么,创建一个新列表只是为了拥有Bar 对象的具体化列表似乎有点浪费。我可能会在转换后立即链接我想要对列表执行的任何操作。

【讨论】:

  • 浪费是什么意思?您建议返回一个流以供以后处理,而不是创建一个结果列表?
  • @AdamSkywalker 是的,它更适合函数式方法。
  • 我明白了。无论如何,名称 BaseDTO 表示结果很可能会被序列化并发送到某个地方
  • @AdamSkywalker 是的,在某些情况下这是不可避免的。但这些模式通常是天真的努力“升级”代码以使用 lambdas 的结果,通常是通过更改方法的实现而不是它们相互调用的方式。同样,有时这是您唯一的选择(例如,如果您的方法是公共 API 的一部分,并且说它应该返回 List),但最好知道这些限制不适用的地方,您可以去一个更好。
  • @AdamSkywalker 是的,DTO 将在网格中使用,所以过程是 ORM 给出实体列表 -> 函数处理并返回 DTO -> 将列表传递给显示结果的网格
【解决方案2】:

您的问题中最困难的问题实际上不是样板文件或流,而是泛型。尝试做new B 有点乱。你不能直接这样做,任何解决方法都不是太干净。

然而,对于样板文件,由于 Java 8 在接口中的默认方法,您可以做得更好。考虑以下接口:

public interface ConversionHandler<A,B> {

  B constructB(A a, Locale locale);

  default List<B> convertAToB(List<A> list, Locale locale) {
    return list.stream().map(a -> constructB(a, locale)).collect(Collectors.toCollection(ArrayList::new));
  }
}

列表转换样板现已完成,您所要做的就是在子类中实现B 构造。但是,如果 B 仍然是通用的,这仍然很棘手。

public class ClassThatUsesAandB<A, B> implements ConversionHandler<A,B> {

  @Override
  public B constructB(A a, Locale locale) {
    return null; //This is tricky
  }
} 

但是,如果子类是具体的,那就很简单了

public class ConverterClass implements ConversionHandler<String,Integer> {

  @Override
  public Integer constructB(String s, Locale locale) {
    return s.length();
  }
}

因此,您可能想要搜索的后续内容是一种很好的设计模式,可以使通用对象的构造尽可能可维护和可读。

【讨论】:

  • 是的,我正在考虑做这样的事情,所以在子类中我只需要创建特定的 B 类型。我稍微改述了这个问题,所以你说 B 都是泛型的。我会在周末考虑一下,但我认为你的答案最终会是正确的!谢谢:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多