简体   繁体   English

我什么时候应该使用流?

[英]When should I use streams?

I just came across a question when using a List and its stream() method.我刚刚在使用List及其stream()方法时遇到了一个问题。 While I know how to use them, I'm not quite sure about when to use them.虽然我知道如何使用它们,但我不太确定何时使用它们。

For example, I have a list, containing various paths to different locations.例如,我有一个列表,其中包含到不同位置的各种路径。 Now, I'd like to check whether a single, given path contains any of the paths specified in the list.现在,我想检查一个给定的路径是否包含列表中指定的任何路径。 I'd like to return a boolean based on whether or not the condition was met.我想根据是否满足条件返回一个boolean

This of course, is not a hard task per se.当然,这本身并不是一项艰巨的任务。 But I wonder whether I should use streams, or a for(-each) loop.但我想知道我应该使用流还是 for(-each) 循环。

The List列表

private static final List<String> EXCLUDE_PATHS = Arrays.asList(
    "my/path/one",
    "my/path/two"
);

Example using Stream:使用流的示例:

private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream()
                        .map(String::toLowerCase)
                        .filter(path::contains)
                        .collect(Collectors.toList())
                        .size() > 0;
}

Example using for-each loop:使用 for-each 循环的示例:

private boolean isExcluded(String path){
    for (String excludePath : EXCLUDE_PATHS) {
        if (path.contains(excludePath.toLowerCase())) {
            return true;
        }
    }
    return false;
}

Note that the path parameter is always lowercase .请注意, path参数始终为小写

My first guess is that the for-each approach is faster, because the loop would return immediately, if the condition is met.我的第一个猜测是 for-each 方法更快,因为如果满足条件,循环会立即返回。 Whereas the stream would still loop over all list entries in order to complete filtering.而流仍会遍历所有列表条目以完成过滤。

Is my assumption correct?我的假设正确吗? If so, why (or rather when ) would I use stream() then?如果是这样,那么为什么(或者更确切地说是什么时候)我会使用stream()呢?

Your assumption is correct.你的假设是正确的。 Your stream implementation is slower than the for-loop.您的流实现比 for 循环慢。

This stream usage should be as fast as the for-loop though:这个流的使用应该和 for 循环一样快:

EXCLUDE_PATHS.stream()  
    .map(String::toLowerCase)
    .anyMatch(path::contains);

This iterates through the items, applying String::toLowerCase and the filter to the items one-by-one and terminating at the first item that matches.这将遍历项目,将String::toLowerCase和过滤器逐一应用于项目,并匹配的第一个项目处终止

Both collect() & anyMatch() are terminal operations. collect()anyMatch()都是终端操作。 anyMatch() exits at the first found item, though, while collect() requires all items to be processed. anyMatch()在第一个找到的项目处退出,而collect()要求处理所有项目。

The decision whether to use Streams or not should not be driven by performance consideration, but rather by readability.是否使用 Streams 的决定不应由性能考虑驱动,而是由可读性驱动。 When it really comes to performance, there are other considerations.当谈到性能时,还有其他考虑因素。

With your .filter(path::contains).collect(Collectors.toList()).size() > 0 approach, you are processing all elements and collecting them into a temporary List , before comparing the size, still, this hardly ever matters for a Stream consisting of two elements.使用您的.filter(path::contains).collect(Collectors.toList()).size() > 0方法,您正在处理所有元素并将它们收集到一个临时List ,然后再比较大小,这几乎从来没有对于由两个元素组成的 Stream 很重要。

Using .map(String::toLowerCase).anyMatch(path::contains) can save CPU cycles and memory, if you have a substantially larger number of elements.如果您有大量元素,使用.map(String::toLowerCase).anyMatch(path::contains)可以节省 CPU 周期和内存。 Still, this converts each String to its lowercase representation, until a match is found.尽管如此,这会将每个String转换为其小写表示,直到找到匹配项。 Obviously, there is a point in using显然,有一点使用

private static final List<String> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .collect(Collectors.toList());

private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream().anyMatch(path::contains);
}

instead.反而。 So you don't have to repeat the conversion to lowcase in every invocation of isExcluded .因此,您不必在每次调用isExcluded重复转换为小写isExcluded If the number of elements in EXCLUDE_PATHS or the lengths of the strings becomes really large, you may consider using如果EXCLUDE_PATHS的元素数量或字符串的长度变得非常大,您可以考虑使用

private static final List<Predicate<String>> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .map(s -> Pattern.compile(s, Pattern.LITERAL).asPredicate())
          .collect(Collectors.toList());

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream().anyMatch(p -> p.test(path));
}

Compiling a string as regex pattern with the LITERAL flag, makes it behave just like ordinary string operations, but allows the engine to spent some time in preparation, eg using the Boyer Moore algorithm, to be more efficient when it comes to the actual comparison.将字符串编译为带有LITERAL标志的正则表达式模式,使其行为就像普通的字符串操作一样,但允许引擎花一些时间进行准备,例如使用 Boyer Moore 算法,以便在实际比较时更高效。

Of course, this only pays off if there are enough subsequent tests to compensate the time spent in preparation.当然,这只有在有足够多的后续测试来补偿准备工作所花费的时间时才会有回报。 Determining whether this will be the case, is one of the actual performance considerations, besides the first question whether this operation will ever be performance critical at all.确定是否会是这种情况是实际性能考虑之一,除了第一个问题之外,此操作是否永远是性能关键。 Not the question whether to use Streams or for loops.不是使用 Streams 还是for循环的问题。

By the way, the code examples above keep the logic of your original code, which looks questionable to me.顺便说一句,上面的代码示例保留了原始代码的逻辑,这在我看来是有问题的。 Your isExcluded method returns true , if the specified path contains any of the elements in list, so it returns true for /some/prefix/to/my/path/one , as well as my/path/one/and/some/suffix or even /some/prefix/to/my/path/one/and/some/suffix .如果指定的路径包含列表中的任何元素,则isExcluded方法返回true ,因此它为/some/prefix/to/my/path/one以及my/path/one/and/some/suffix返回true甚至/some/prefix/to/my/path/one/and/some/suffix

Even dummy/path/onerous is considered fulfilling the criteria as it contains the string my/path/one甚至dummy/path/onerous被认为满足条件,因为它contains字符串my/path/one ...

Yeah.是的。 You are right.你是对的。 Your stream approach will have some overhead.您的流方法会有一些开销。 But you may use such a construction:但是你可以使用这样的结构:

private boolean isExcluded(String path) {
    return  EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}

The main reason to use streams is that they make your code simpler and easy to read.使用流的主要原因是它们使您的代码更简单易读。

The goal of streams in Java is to simplify the complexity of writing parallel code. Java 中流的目标是简化编写并行代码的复杂性。 It's inspired by functional programming.它的灵感来自函数式编程。 The serial stream is just to make the code cleaner.串行流只是为了使代码更清晰。

If we want performance we should use parallelStream, which was designed to.如果我们想要性能,我们应该使用parallelStream,它的设计目的是。 The serial one, in general, is slower.一般来说,串行的速度较慢。

There is a good article to read about ForLoop , Stream and ParallelStream Performance .有一篇关于ForLoopStreamParallelStream Performance的好文章可供阅读。

In your code we can use termination methods to stop the search on the first match.在您的代码中,我们可以使用终止方法来停止第一个匹配项的搜索。 (anyMatch...) (任何匹配...)

As others have mentioned many good points, but I just want to mention lazy evaluation in stream evaluation.正如其他人提到的很多优点,但我只想提一下流评估中的惰性评估。 When we do map() to create a stream of lower case paths, we are not creating the whole stream immediately, instead the stream is lazily constructed , which is why the performance should be equivalent to the traditional for loop.当我们使用map()创建小写路径的流时,我们不会立即创建整个流,而是延迟构造流,这就是为什么性能应该等同于传统的 for 循环。 It is not doing a full scanning, map() and anyMatch() are executed at the same time.它没有进行全面扫描, map()anyMatch()是同时执行的。 Once anyMatch() returns true, it will be short-circuited.一旦anyMatch()返回 true,它将被短路。

Radical answer:激进的答案:

Never .从来没有 Ever.曾经。 Ever.曾经。

I almost never iterated a list for anything, especially to find something, yet Stream users and systems seem filled with that way of coding.我几乎从不为任何事情迭代列表,尤其是为了找到一些东西,但 Stream 用户和系统似乎充满了这种编码方式。

Difficult to refactor and organize such code and so redundancy and over iteration is everywhere.很难重构和组织这样的代码,因此冗余和过度迭代无处不在。 In the same method you might see it 5 times.用同样的方法,你可能会看到它 5 次。 Same list, finding different things.同样的清单,发现不同的东西。

It is also not really shorter either.它也不是很短。 Rarely is.很少是。 Definitely not more readable but that is a subjective opinion.绝对不是更具可读性,但这是一个主观意见。 Some people will say it is.有些人会说是。 I don't.我不。 People might like it due to autocompletion but in my editor Intellij, I can just iter or itar and have the for loop auto created for me with types and everything.人们可能会喜欢它,由于自动完成,但在我的IntelliJ的编辑,我可以iteritar ,并有针对我创建了类型,一切都环汽车。

Misused and overused and it is better to avoid completely.误用和过度使用,最好完全避免。 Java is not a true functional language and Java generics sucks and are not expressive enough and certainly more difficult to read, parse and refactor. Java 不是真正的函数式语言,Java 泛型很烂,表达能力不够强,当然更难阅读、解析和重构。

Stream code is not easily extractable or refactorable unless you want to start adding weird methods that return Optionals , Predicates , Consumers and what not and you end up having methods returning and taking all kinds of weird generic constraints with orders and meanings only God knows what.流代码不容易提取或重构,除非您想开始添加返回OptionalsPredicatesConsumers奇怪方法,并且最终返回方法并采用各种奇怪的通用约束,其顺序和含义只有上帝知道。 To much inferrals where you need to visit methods to figure out the types of various things.对于许多需要访问方法以找出各种事物类型的推断。

Trying to make Java behave like a functional language like Haskell or Lisp is a fools errand.试图让 Java 表现得像HaskellLisp这样的函数式语言是愚蠢的差事。 A heavy Streams based Java system is always going to be more complex than a none one, and way less performant and more complex to refactor and maintain.一个沉重的基于 Streams 的 Java 系统总是比没有任何系统更复杂,而且性能更低,重构和维护更复杂。 Therefore also more buggy and filled with patch work.因此也有更多的错误和充满补丁的工作。 Glue everywhere.到处粘。

When OpenJDK got involved they started adding things to the language without really thinking it thoroughly.当 OpenJDK 参与进来时,他们开始在没有真正彻底考虑的情况下向语言添加内容。 It is not just the Java Streams.它不仅仅是Java Streams。 Therefore such systems are inherently more complex because they require more base knowledge.因此,此类系统本质上更复杂,因为它们需要更多的基础知识。 You might have it, but your colleagues don't.你可能有,但你的同事没有。 They sure as hell know what a for loop is and what an if block is.他们肯定知道 for 循环是什么以及 if 块是什么。

Furthermore, since you also can not assign anything to a non final variable, you can rarely do two things at the same while looping, so you end up iterating twice, or thrice.此外,由于您也不能为非最终变量分配任何内容,因此在循环时您很少可以同时做两件事,因此您最终会迭代两次或三次。

Most that like and prefer the Streams approach over a for loop are people that started learning Java post Java 8. Those before hate it.大多数喜欢和喜欢 Streams 方法而不是 for 循环的人是在 Java 8 之后开始学习 Java 的人。以前的人讨厌它。 The thing is that it is far more complex use, refactor and more difficult to use the right way.问题是它的使用、重构和使用正确的方式要复杂得多。

And when I say it performs worse, it is not in comparison to a for loop, which is also a very real thing but more due to the tendency such code have to over iterate a wide range of things.当我说它的性能更差时,它不是与 for 循环相比,这也是一个非常真实的事情,但更多的是由于此类代码必须过度迭代各种事物的趋势。

It is deemed easy to iterate a list to find an item that it tends being done over and over again.迭代一个列表以找到它往往会一遍又一遍地完成的项目被认为很容易。

I've not seen a single system that has benefitted from it.我还没有看到一个系统从中受益。 All of the systems I have seen are horribly implemented, mostly because of it.我见过的所有系统都实施得很糟糕,主要是因为它。

Code is definitely not more readable than a for loop and definitely more flexible and refactorable in a for loop.代码绝对不比 for 循环更具可读性,而且在 for 循环中绝对更灵活和可重构。 The reason we see so many such complex crap systems and bugs everywhere today is I promise you due to the heavy reliance on Streams to filter, not to mention overuse of Lombok and Jackson.我们今天在任何地方看到这么多如此复杂的垃圾系统和错误的原因是我向您保证,由于严重依赖 Streams 进行过滤,更不用说过度使用 Lombok 和 Jackson。 Those three are the hallmark of a badly implemented system.这三个是系统实施不当的标志。 Keyword overuse .关键字过度使用 A patch work approach.补丁工作方法。

Again, I consider it really bad to iterate a list to find anything.同样,我认为迭代列表以查找任何内容真的很糟糕。 Yet with Stream based systems, this is what people do all the time.然而,对于基于 Stream 的系统,这就是人们一直在做的事情。 It is also not rare and difficult to parse and detect that an iteration might be O(N2) while with a for loop you would immediately see it.解析和检测迭代可能是 O(N2) 而使用 for 循环时,您会立即看到它,这也并不罕见和困难。

What is often customary to ask the database to filter things for you it is not rare that a base query would instead return a big list of things and expanded with all kinds of iterative things and methods to cover use cases to filter out undesirables, and of course they use Streams to do that.通常要求数据库为您过滤事物的习惯并不罕见,基本查询会返回一大堆事物并使用各种迭代事物和方法进行扩展以涵盖用例以过滤掉不需要的事物,以及当然,他们使用 Streams 来做到这一点。 All kinds of methods arises around that big list with various things to filter.围绕这个大列表出现了各种各样的方法,其中包含各种要过滤的内容。

Over and over again.再三,一而再再而三。 Of course, I do not mean you.当然,我不是说你。 Your colleagues.你的同事。 Right?对?

I almost never iterate anything.我几乎从不迭代任何东西。 I use the right datasets and rely on the database to filter it for me.我使用正确的数据集并依靠数据库为我过滤它。 Once .一次 However in a Streams heavy system you will see this everywhere.但是,在 Streams 重系统中,您会在任何地方看到这一点。 In the deepest method, in the caller, caller of caller, caller of the caller of the caller.在最深的方法中,在调用者,调用者的调用者,调用者的调用者的调用者中。 Streams everywhere.处处流水。 It is ugly.它很丑。 And good luck refactoring that code that lives in tiny lambdas.祝你好运重构那些存在于微小 lambda 表达式中的代码。

And good luck reusing them.祝你好运重用它们。 Nobody will look to reuse your nice Predicates.没有人会希望重用你漂亮的谓词。 And if they want to use them, guess what.如果他们想使用它们,你猜怎么着。 They need to use more Streams.他们需要使用更多的流。 You just got yourself addicted and cornered yourself further.你只是让自己上瘾并进一步逼迫自己。 Now, are you proposing I start splitting all of my code in tiny Predicates, Consumers, Function and BiFcuntions?现在,您是否建议我开始将所有代码拆分为微小的 Predicates、Consumer、Function 和 BiFcuntions? Just so I can reuse that logic for Streams?只是为了我可以为 Streams 重用该逻辑?

Of course I hate it just as much in Javascript as well, where over iteration is everywhere.当然,我在 Javascript 中也同样讨厌它,因为到处都是过度迭代。

You might say the cost is nothing to iterate a list but the system complexity grows, redundancy increases and therefore maintenance costs and number of bugs increases.您可能会说迭代列表的成本不算什么,但系统复杂性会增加,冗余会增加,因此维护成本和错误数量也会增加。 It becomes a patch and glue based approach to various things.它变成了一种基于补丁和胶水的方法来处理各种事情。 Just add another filter and remove this, rather than code things the right way.只需添加另一个过滤器并删除它,而不是以正确的方式编码。

Furthermore, where you need three servers to host all of your users, I can manage with just one.此外,如果您需要三台服务器来托管所有用户,我只需一台即可。 So required scalability of such a system is going to be required way earlier than a non streams heavy system.因此,这种系统所需的可扩展性比非流重系统更早。 For small projects that is a very important metric.对于小型项目,这是一个非常重要的指标。 Where you can have say 5000 concurrent users, my system can handle twice or thrice that.你可以说 5000 个并发用户,我的系统可以处理两倍或三次。

I have no need for it in my code, and when I am in charge of new projects, the first rule is that Streams are totally forbidden to use.我的代码中不需要它,当我负责新项目时,第一条规则是完全禁止使用Streams。

That is not to say there are not use cases for it, or that it might be useful at times but the risks associated with allowing it far outweighs the benefits.这并不是说它没有用例,或者它有时可能有用,但与允许它相关的风险远远超过收益。

When you start using Streams you are essentially adopting a whole new programming paradigm .当您开始使用 Streams 时,您实际上是在采用一种全新的编程范式 The entire programming style of the system will change and that is what I am concerned about .系统的整个编程风格会发生变化,这就是我所关心的

You do not want that style.你不想要那种风格。 It is not superior to the old style.它并不优于旧式。 Especially on Java.尤其是在 Java 上。

Take the Futures API .期货 API为例。 Sure, you could start coding everything to return a Promise or a Future, but do you really want to?当然,您可以开始编写所有代码以返回 Promise 或 Future,但是您真的想要吗? Is that going to resolve anything?这能解决什么吗? Can your entire system really follow up on being that, everywhere?你的整个系统真的能在任何地方跟进吗? Will it be better for you, or are you just experimenting and hoping you will benefit at some point?这对你来说会更好,还是你只是在尝试并希望你能在某个时候受益?

There are people that overdo JavaRx and overdo promises in JavaScript as well.有些人在 JavaScript 中过度使用JavaRx和过度使用 promise。 There are really really few cases for when you really want to have things futures based, and very many many corner cases will be felt where you will find that those APIs have certain limitations and you just got made.当您真正想要基于未来的东西时,真的很少有案例,并且会感觉到非常多的极端案例,您会发现这些 API 具有某些限制并且您刚刚完成。

You can build really really complex and far far more maintainable systems without all that crap.您可以构建非常复杂且更易于维护的系统,而无需使用这些废话。 This is what it is about.这就是它的内容。 It is not about your hobby project expanding and becoming a horrible code base.这与您的爱好项目扩展并成为一个可怕的代码库无关。

It is about what is best approach to build large and complex enterprise systems and ensure they remain coherent, consistent refactorable and easily maintainable.它是关于构建大型复杂企业系统并确保它们保持一致、一致、可重构和易于维护的最佳方法。

Furthermore, rarely are you ever working on such systems on your own.此外,您很少独自在此类系统上工作。 You are very likely working with a minimum of > 10 people all experimenting and overdoing Streams.您很可能与至少 10 人以上的人一起工作,他们都在试验和过度使用 Streams。 So while you might know how to use them properly, you can rest assure the other 9 don't.因此,虽然您可能知道如何正确使用它们,但您可以放心,其他 9 种方法不会。

I will leave you with these wonderful examples of real code, with thousands more like them:我会给你留下这些精彩的真实代码示例,还有成千上万个这样的:

private List<ViewAccessProfileExternalDto> markHiddenSchedules(SystemId systemId, List<ViewAccessProfileExternalDto> accessProfiles) {
    final var accessGroupIds =
        accessProfiles.stream()
            .flatMap(
                ap ->
                    ap.getAccessPolicies().stream()
                        .map(po -> po.getAccessGroupId())
                        .filter(Objects::nonNull)
                        .map(id -> AccessPointGroupId.of(id, systemId)))
            .collect(Collectors.toSet());
    final var accessGroupsWithDevices = accessGroupHandler.getAccessGroupsWithDevices(accessGroupIds);
    final var groupsWithDevices =
        accessGroupIds.stream()
            .collect(
                Collectors.toMap(
                    id -> id.idString(),
                    id -> Optional.ofNullable(accessGroupsWithDevices.getOrDefault(id.idString(), null))));
    
    accessProfiles.forEach(
        ap -> {
            ap.getAccessPolicies()
                .forEach(
                    policy -> {
                        if (policy.getAccessGroupId() != null) {
                            final var onlineSchedules =
                                policy.getSchedules().stream()
                                    .filter(
                                        s ->
                                            ScheduleType.STANDARD
                                                .name()
                                                .equalsIgnoreCase(s.getScheduleType()));
                            final var offlineSchedules =
                                policy.getSchedules().stream()
                                    .filter(s -> ScheduleType.OFFLINE.name().equalsIgnoreCase(s.getScheduleType()));
                            final var opt = groupsWithDevices.get(policy.getAccessGroupId());
                            if (opt.isPresent()) {
                                if (!opt.get().isAnyOnlineDevices()) {
                                    onlineSchedules.forEach(sc -> sc.setHidden(true));
                                }
                                if (!opt.get().isAnyNonPulseOfflineDevices()) {
                                    offlineSchedules.forEach(sc -> sc.setHidden(true));
                                }
                            } else {
                                onlineSchedules.forEach(sc -> sc.setHidden(true));
                                offlineSchedules.forEach(sc -> sc.setHidden(true));
                            }
                        }
                    });
        });
    
    return accessProfiles;
}

Or this:或这个:

private List<DoorInfo> packageDoorInfos(final CredentialUpdateEvent credentialUpdateEvent, final CredentialScheduleHandler.SchedulesAndCounts schedules) {
    // For each accessArea in accessPolicy, find connected doorIds and create DoorInfo from each pair
    // of lockId, schemaId
    final Map<MsfIdDto, List<CredentialUpdateEvent.Door>> doorsPerAccessArea
            = credentialUpdateEvent.doors.stream().collect(Collectors.groupingBy(d -> d.accessAreaId));
    final Stream<DoorInfoEntry> distinctDoorInfoEntries = credentialUpdateEvent.accessPolicies.stream()
            .flatMap(ap -> {
                final List<CredentialUpdateEvent.Door> doors = doorsPerAccessArea.get(ap.accessAreaId);
                if (doors == null) {
                    LOGGER.warn("No doors configured for access area {}. Skipping it", ap.accessAreaId);
                    return Stream.<DoorInfoEntry>empty();
                }
                final int scheduleId = schedules.scheduleIdFor(ap.scheduleId);
                return doors.stream()
                        .map(d -> new DoorInfoEntry(d, Integer.parseInt(d.lockId), scheduleId));
            })
                    // no need to write more that one entry per lockId, scheduleId tuple, so make them distinct
            .distinct()
            // sort em by lockId and the scheduleId, which should enable binary search in lcoks
            .sorted(byLockIdAndScheduleId);

    // convert to DoorInfo's
    return distinctDoorInfoEntries.map(di -> DoorInfo.from(di.lockId,
                                                           di.scheduleId,
                                                           true,
                                                           di.door.toggleActive != null ? di.door.toggleActive : false,
                                                           di.door.extendedUnlockTimeActive != null ? di.door.extendedUnlockTimeActive : false))
            .collect(Collectors.toList());
}

Or this:或这个:

public Result result(List<CredentialHolderExternalDto> credentialHolders, List<Apartment> apartments) {
    final var fch = forCredentialHolders(credentialHolders);
    final var fap = forApartments(apartments);
    return new Result(
            fch.entrySet().stream()
                    .filter(e -> e.getValue().isPresent())
                    .collect(Collectors.toMap(e -> e.getKey().getId(), e -> e.getValue().get())),
            fch.entrySet().stream().filter(e -> e.getValue().isEmpty()).map(e -> e.getKey()).collect(Collectors.toList()),
            fap.entrySet().stream()
                    .filter(e -> e.getValue().isPresent())
                    .collect(Collectors.toMap(e -> e.getKey().getEntityId().idString(), e -> e.getValue().get())),
            fap.entrySet().stream().filter(e -> e.getValue().isEmpty()).map(e -> e.getKey()).collect(Collectors.toList()));
}

private Map<CredentialHolderExternalDto, Optional<ViewAccessProfile>> forCredentialHolders(
        List<CredentialHolderExternalDto> credentialHolders) {
    final var profileIdsPerCredentialHolder =
            credentialHolders.stream()
                    .collect(
                            toMap(
                                    ch -> ch,
                                    ch ->
                                            ch.getAccessRightsInformation().stream()
                                                    .map(ari -> AccessProfileId.of(ari.getAccessProfileId(), systemIdObject))
                                                    .collect(Collectors.toList())));
    final var profiles =
            viewAccessProfileRepository.get(
                    profileIdsPerCredentialHolder.values().stream().flatMap(List::stream).distinct().collect(toList()));
    final var relevantProfiles =
            profiles.stream()
                    .filter(vap -> vap.getType() == AccessProfile.Type.CREDENTIAL_HOLDER)
                    .collect(toMap(vap -> vap.getEntityId(), Function.identity()));

    final var preferredProfilePerCredentialHolder =
            profileIdsPerCredentialHolder.entrySet().stream()
                    .map(
                            e -> {
                                final var optProfile =
                                        e.getValue().stream().map(id -> relevantProfiles.get(id)).filter(Objects::nonNull).findFirst();
                                return new AbstractMap.SimpleEntry<>(e.getKey(), optProfile);
                            })
                    .collect(toMap(e -> e.getKey(), e -> e.getValue()));
    return preferredProfilePerCredentialHolder;
}

private Map<Apartment, Optional<ViewAccessProfile>> forApartments(List<Apartment> apartments) {
    final var profileIdsPerApartment =
            apartments.stream()
                    .collect(
                            toMap(
                                    ap -> ap,
                                    ap ->
                                            ap.getAccessProfiles().stream()
                                                    .map(p -> AccessProfileId.of(p.getId(), systemIdObject))
                                                    .collect(toList())));
    final var profiles =
            viewAccessProfileRepository.get(
                    profileIdsPerApartment.values().stream().flatMap(List::stream).distinct().collect(toList()));
    final var relevantProfiles =
            profiles.stream()
                    .filter(vap -> vap.getType() == AccessProfile.Type.APARTMENT)
                    .collect(toMap(vap -> vap.getEntityId(), Function.identity()));

    final var preferredProfilePerApartment =
            profileIdsPerApartment.entrySet().stream()
                    .map(
                            e -> {
                                final var optProfile =
                                        e.getValue().stream().map(id -> relevantProfiles.get(id)).filter(Objects::nonNull).findFirst();
                                return new AbstractMap.SimpleEntry<>(e.getKey(), optProfile);
                            })
                    .collect(toMap(e -> e.getKey(), e -> e.getValue()));

    return preferredProfilePerApartment;
}

Or this:或这个:

private BatchResponseExternalDto performOperationCredentialContainers(
        List<CredentialContainerV2ExternalDto> containers,
        BiFunction<CredentialContainerV2ExternalDto, CredentialContainerV2ExternalDto, CredentialContainerV2ExternalDto> transformation,
        Function2<CredentialContainerV2ExternalDto, String, DeliveredMessage> operation) {
    SystemId systemId = SystemId.of(containers.stream().findFirst().get().getSystemId());
    List<CredentialContainerType> allCredentialContainerTypes = credentialContainerTypeHandler.getAll(systemId);

    Map<String, List<BatchEntityResponseExternalDto>> resultMap =
            io.vavr.collection.List.ofAll(containers)
                    .map(container -> Tuple.of(container, getOldCredentialContainerV2Dto(container.getId(), systemId)))
                    .map(
                            t ->
                                    t.map(
                                            (newContainer, oldContainer) ->
                                                    Tuple.of(
                                                            newContainer,
                                                            applyTransformation(oldContainer, newContainer, transformation))))
                    .map(
                            t ->
                                    t.map2(
                                            tryOfTransformedContainer ->
                                                    tryOfTransformedContainer.map(
                                                            transformedContainer ->
                                                                    operation.apply(
                                                                            transformedContainer,
                                                                            toFormatId(
                                                                                    allCredentialContainerTypes,
                                                                                    transformedContainer)))))
                    .collect(getBatchEntityCollector());

    BatchResponseExternalDto result = new BatchResponseExternalDto(resultMap.get(SUCCESS), resultMap.get(FAILED));

    return result;
}

Try refactoring the above.尝试重构上述内容。 I challenge you.我挑战你。 Give it a try.试一试。 Everything is a Stream, everywhere.一切都是流,无处不在。 This is what Stream developers do, they overdo it, and there is no easy way to grasp what the code is actually doing.这就是 Stream 开发人员所做的,他们做得太过分了,并且没有简单的方法来掌握代码实际在做什么。 What is this method returning, what is this transformation doing, what do I end up with.这个方法返回什么,这个转换在做什么,我最终会得到什么。 Everything is inferred.一切都是推断出来的。 Much more difficult to read for sure.肯定更难阅读。

If you understand this, then you must be the einstein , but you should know not everyone is like you, and this could be your system in a very near future.如果你明白这一点,那么你一定是爱因斯坦,但你应该知道不是每个人都像你一样,这可能是你在不久的将来的系统。

Do note, this is not isolated to this one project but I've seen many of them very similar to these structures.请注意,这并非孤立于这个项目,但我已经看到其中许多与这些结构非常相似。

One thing is for sure, horrible coders love streams.有一件事是肯定的,可怕的程序员喜欢流。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM