【问题标题】:List<Base> to List<Long> ... conditional castList<Base> 到 List<Long> ...条件转换
【发布时间】:2016-08-30 10:35:52
【问题描述】:

假设我有这 3 个课程(大大减少):

public interface Base
{
    String getType();
}

public class Object1 implements Base
{
    String getType() { ... };

    long getSerialNr() { ... };
}

public class Object2 implements Base
{
    String getType() { ... };

    long getPartNr() { ... };
}

我将如何(使用 Java 8 流)使用条件强制转换将 List&lt;Base&gt; 转换为 List&lt;Long&gt;?我从这个开始:

List<Long> result = baseList.stream().filter(Objects::nonNull)
    .filter(Object1.class::isInstance)
    .map(Object1.class::cast)
    .map(o -> o.getSerialNr())
    .collect(Collectors.toList());

...但是如何整合第二种情况,其中元素是Object2 的实例,我想返回getPartNr

【问题讨论】:

  • 为什么不在Base接口中简单地声明long getId()
  • 很遗憾,这是不可能的。这只是我的“现实世界问题”的一个过于简单的例子,在界面中没有 getId() 是有意义的。

标签: lambda java-8 type-conversion java-stream predicate


【解决方案1】:

假设类的数量可能会增加,您可能必须抽象类型到属性的映射:

static Map<Predicate<Base>,Function<Base,Long>> ACCESSORS;
static {
    Map<Predicate<Base>,Function<Base,Long>> m=new HashMap<>();
    m.put(Object1.class::isInstance, base -> ((Object1)base).getSerialNr());
    m.put(Object2.class::isInstance, base -> ((Object2)base).getPartNr());
    ACCESSORS=Collections.unmodifiableMap(m);
}
static Stream<Long> get(Base b) {
    return ACCESSORS.entrySet().stream()
            .filter(e -> e.getKey().test(b))
            .map(e -> e.getValue().apply(b));
}

get 方法假定谓词是互斥的,在测试这些非interface 类型时就是这种情况。

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

List<Long> result = baseList.stream()
    .flatMap(YourClass::get)
    .collect(Collectors.toList());

您也可以内联 get 方法,但这并没有提高可读性:

List<Long> result = baseList.stream()
    .flatMap(b -> ACCESSORS.entrySet().stream()
        .filter(e -> e.getKey().test(b))
        .map(e -> e.getValue().apply(b)))
    .collect(Collectors.toList());

【讨论】:

    【解决方案2】:

    首先,如果接口 Base 中没有一个所有类都应该实现的通用方法,那么接口就没有意义。如果你想那样做,那就不好了。您应该将代码更改为下面的代码:

    List<Long> aList = baseList.stream().filter((Objects::nonNull)
            .filter(obj->(obj instanceof Object1 || obj instanceof Object2))
            .map(num-> (num instanceof Object1) ? ((Object1) num).getPratNr() : ((Object2) num).getPratNr())
            .collect(Collectors.toList());
    

    【讨论】:

    • 这个例子被强烈地简化为核心问题......我当然不会使用没有在实现之间共享方法的接口。谢谢你的sn-p!
    • 针对此问题的其他已发布解决方案过于复杂。这是最简单、最直接的解决方案,使其优于其他解决方案。
    【解决方案3】:

    您可以尝试按元素类对 baseList 进行分组。

    public class MyTest {
    
      @Test
      public void java8_listbase_to_listlong_conditional_cast() {
        List<Base> baseList = new ArrayList<>();
        baseList.add(new Object1());
        baseList.add(new Object2());
    
        Map<?, List<Base>> group = baseList.stream()
          .filter(Objects::nonNull)
          .collect(Collectors.groupingBy(key -> key.getClass()));
    
        Stream<Long> object1stream = group.get(Object1.class)
          .stream()
          .map(Object1.class::cast)
          .map(Object1::getSerialNr);
    
        Stream<Long> object2stream = group.get(Object2.class)
          .stream()
          .map(Object2.class::cast)
          .map(Object2::getPartNr);
    
        List<Long> longList = Stream.concat(object1stream, object2stream).collect(Collectors.toList());
    
        assertArrayEquals(new Long[] {0l, 1l}, longList.toArray());
      }
    
    
    
      public interface Base {
      }
    
      public class Object1 implements Base {
        long getSerialNr() { return 0L; };
      }
    
      public class Object2 implements Base {
        long getPartNr() { return 1L; };
      }
    }
    

    【讨论】:

    • 谢谢,但我想要一个 Long 值列表,而不是一个包含 2 个条目的 Map(分别是 2 个单独的列表)。
    • 好的,我已经更新了示例。您可以内联中间结果以获得一个操作,但可读性会受到影响。
    • 谢谢!现在这是获得预期结果的第三种可能性。我刚开始使用 Java8 流 api,我想在这个线程中这 3 个答案之间的性能影响有多大。与旧的 for(...) 循环相比,这一切都很好阅读,但我认为它要慢得多,不是吗?
    • 您可以搜索不同循环速度的基准。随着 Java 版本的更新,流变得越来越快。如果可读性受到影响并且没有性能问题,请不要过早地对代码进行微优化。永远不要假设而是衡量绩效。
    • 谢谢,我会考虑的!
    猜你喜欢
    • 1970-01-01
    • 2019-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多