代码之家  ›  专栏  ›  技术社区  ›  Remo

Java 8中如何避免多流

  •  0
  • Remo  · 技术社区  · 3 年前

    我有下面的代码

    trainResponse.getIds().stream()
            .filter(id -> id.getType().equalsIgnoreCase("Company"))
            .findFirst()
            .ifPresent(id -> {
                domainResp.setId(id.getId());
            });
    
    trainResponse.getIds().stream()
            .filter(id -> id.getType().equalsIgnoreCase("Private"))
            .findFirst()
            .ifPresent(id ->
                domainResp.setPrivateId(id.getId())
            );
    

    在这里,我正在迭代/流式传输 Id 物体 2 时报。

    这两条溪流之间的唯一区别在于 filter() 活动

    如何实现这一目标 单次迭代 ,最好的方法是什么( 依据 时间 空间复杂性 )这么做?

    1 回复  |  直到 3 年前
        1
  •  2
  •   Alexander Ivanchenko    3 年前

    使用流IPA可以在一次通过给定数据集的过程中实现这一点,而不会增加内存消耗( i、 e.结果将仅包含 id s 有必要的属性 ).

    为此,您可以创建一个自定义 Collector 这将成为它的一个参数 Collection 要查找的属性和 Function 负责从流元素中提取属性。

    这就是这个通用收集器的实现方式。

    /** *
     * @param <T> - the type of stream elements
     * @param <F> - the type of the key (a field of the stream element)
     */
    class CollectByKey<T, F> implements Collector<T, Map<F, T>, Map<F, T>> {
        private final Set<F> keys;
        private final Function<T, F> keyExtractor;
        
        public CollectByKey(Collection<F> keys, Function<T, F> keyExtractor) {
            this.keys = new HashSet<>(keys);
            this.keyExtractor = keyExtractor;
        }
        
        @Override
        public Supplier<Map<F, T>> supplier() {
            return HashMap::new;
        }
        
        @Override
        public BiConsumer<Map<F, T>, T> accumulator() {
            return this::tryAdd;
        }
        
        private void tryAdd(Map<F, T> map, T item) {
            F key = keyExtractor.apply(item);
            if (keys.remove(key)) {
                map.put(key, item);
            }
        }
        
        @Override
        public BinaryOperator<Map<F, T>> combiner() {
            return this::tryCombine;
        }
        
        private Map<F, T> tryCombine(Map<F, T> left, Map<F, T> right) {
            right.forEach(left::putIfAbsent);
            return left;
        }
        
        @Override
        public Function<Map<F, T>, Map<F, T>> finisher() {
            return Function.identity();
        }
        
        @Override
        public Set<Characteristics> characteristics() {
            return Collections.emptySet();
        }
    }
    

    main() -演示(虚拟) Id 类(未显示)

    public class CustomCollectorByGivenAttributes {
        public static void main(String[] args) {
            List<Id> ids = List.of(new Id(1, "Company"), new Id(2, "Fizz"),
                                   new Id(3, "Private"), new Id(4, "Buzz"));
            
            Map<String, Id> idByType = ids.stream()
                    .collect(new CollectByKey<>(List.of("Company", "Private"), Id::getType));
            
            idByType.forEach((k, v) -> {
                if (k.equalsIgnoreCase("Company")) domainResp.setId(v);
                if (k.equalsIgnoreCase("Private")) domainResp.setPrivateId(v);
            });
        
            System.out.println(idByType.keySet()); // printing keys - added for demo purposes
        }
    }
    

    输出

    [Company, Private]
    

    笔记 ,在密钥集变为空(即所有结果数据都已提取)后,流的其他元素将被忽略,但仍需要处理所有剩余的数据。

        2
  •  1
  •   WJS    3 年前

    对于ID列表,您可以使用一个映射,然后在检索后分配它们(如果存在)。

    Map<String, Integer> seen = new HashMap<>();
    
    for (Id id : ids) {
        if (seen.size() == 2) {
            break;
        }
        seen.computeIfAbsent(id.getType().toLowerCase(), v->id.getId());
    }
    

    如果要测试它,可以使用以下方法:

    record Id(String getType, int getId) {
        @Override
        public String toString() {
            return String.format("[%s,%s]", getType, getId);
        }
    }
    
    Random r = new Random();
    List<Id> ids = r.ints(20, 1, 100)
            .mapToObj(id -> new Id(
                    r.nextBoolean() ? "Company" : "Private", id))
            .toList();
    

    编辑为只允许检查某些类型

    如果您有两种以上的类型,但只想检查某些类型,您可以按如下操作。

    • 过程是一样的,只是你有一个 Set 允许的类型。
    • 您只需使用 contains .
    Map<String, Integer> seen = new HashMap<>();
    
    Set<String> allowedTypes = Set.of("company", "private");
    for (Id id : ids) {
        String type = id.getType();
    
        if (allowedTypes.contains(type.toLowerCase())) {
            if (seen.size() == allowedTypes.size()) {
                break;
            }
            seen.computeIfAbsent(type,
                    v -> id.getId());
        }
    }
    

    测试与此类似,只是需要包括其他类型。

    • 创建可能存在的某些类型的列表。
    • 并像以前一样建立一个清单。
    • 请注意,允许类型的大小将替换该值 2 允许在退出循环之前检查两种以上的类型。
    List<String> possibleTypes = 
          List.of("Company", "Type1", "Private", "Type2");
    Random r = new Random();
    List<Id> ids =
            r.ints(30, 1, 100)
                    .mapToObj(id -> new Id(possibleTypes.get(
                            r.nextInt((possibleTypes.size()))),
                            id))
                    .toList();
    
    
        3
  •  1
  •   Stephen C    3 年前

    在我看来,双流解决方案是最具可读性的。它甚至可能是使用流的最有效的解决方案。

    在我看来,避免多个流的最佳方法是使用经典循环。例如:

    // There may be bugs ...
    
    boolean seenCompany = false;
    boolean seenPrivate = false;
    for (Id id: getIds()) {
       if (!seenCompany && id.getType().equalsIgnoreCase("Company")) {
          domainResp.setId(id.getId());
          seenCompany = true;
       } else if (!seenPrivate && id.getType().equalsIgnoreCase("Private")) {
          domainResp.setPrivateId(id.getId());
          seenPrivate = true;
       }
       if (seenCompany && seenPrivate) {
          break;
       }
    }
    

    目前尚不清楚这是执行一次迭代还是两次迭代更有效。它将取决于 getIds() 以及迭代代码。

    有两个标志的复杂内容是如何复制电路的短路行为 findFirst() 在你的2流解决方案中。我不知道是否有可能做到这一点 完全 使用一条流。如果可以,它将涉及一些非常狡猾的代码。

    但是正如你所看到的,使用2流的原始解决方案显然比上面的更容易理解。


    使用流的主要目的是简化代码。这与效率无关。当你试图做一些复杂的事情来提高流的效率时,你很可能首先就违背了使用流的(真正的)目的。

        4
  •  0
  •   frascu    3 年前

    可以按类型分组并检查生成的映射。 我想 ids IdType .

    Map<String, List<IdType>> map = trainResponse.getIds()
                                    .stream()
                                    .collect(Collectors.groupingBy(
                                                         id -> id.getType().toLowerCase()));
    
    Optional.ofNullable(map.get("company")).ifPresent(ids -> domainResp.setId(ids.get(0).getId()));
    Optional.ofNullable(map.get("private")).ifPresent(ids -> domainResp.setPrivateId(ids.get(0).getId()));
    
        5
  •  0
  •   Antoine Marques    3 年前

    我推荐一个传统的for loop。除了易于扩展之外,这还可以防止多次遍历集合。 你的代码看起来像是将来会被泛化的东西,因此我的泛型方法。

    下面是一些伪代码(有错误,只是为了说明)

    Set<String> matches = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
    for(id : trainResponse.getIds()) {
    
        if (! matches.add(id.getType())) {
            continue;
        }
    
        switch (id.getType().toLowerCase()) {
    
            case "company":
                domainResp.setId(id.getId());
                break;
    
            case "private":
                ...
        }
    }
    
        6
  •  0
  •   Bentaye    3 年前

    沿着这些路线的东西可能会起作用,但它会贯穿整个流程,不会在第一次出现时停止。 但是假设一个小流,每种类型只有一个Id,为什么不呢?

    Map<String, Consumer<String>> setters = new HashMap<>();
    setters.put("Company", domainResp::setId);
    setters.put("Private", domainResp::setPrivateId);
    
    trainResponse.getIds().forEach(id -> {
        if (setters.containsKey(id.getType())) {
            setters.get(id.getType()).accept(id.getId());
        }
    });
    
        7
  •  0
  •   Remo    3 年前

    我们可以使用 Collectors.filtering 从…起 Java 9 继续,根据条件收集值。

    对于这个场景,我修改了如下代码

    final Map<String, String> results = trainResponse.getIds()
                .stream()
                .collect(Collectors.filtering(
                    id -> id.getType().equals("Company") || id.getIdContext().equals("Private"),
                    Collectors.toMap(Id::getType, Id::getId, (first, second) -> first)));
    

    并且得到 id 从…起 results 地图