我有一個CountBarrier帶有方法的簡單類,count()
我想替換類似的東西:
for (int i = 0; i < 5; i ) {
countBarrier.count()
}
和:
IntStream.range(0, 5).forEach(countBarrier::count);
但要正常作業,我只能寫這個:
IntStream.range(0, 5).forEach((i) -> countBarrier.count());
如何管理這個問題(不想看到 var i)?
uj5u.com熱心網友回復:
“醫生,我按這里就疼!” “好吧,那就別按那里了”。
聽起來您希望盡可能簡潔地撰寫“呼叫此方法 5 次”的概念。
Java 可以做到這一點——并且已經做到了 30 年:
for (int i = 0; i < 5; i ) countBarrier.count();
正是這樣。沒有大括號。沒有換行符。
想象一下您使用 foreach 的嘗試實際上是有效的(它沒有,也無法做到,但是讓我們前往假設的土地,發現這段代碼位于獨角獸和彩虹之間):
IntStream.range(0, 5).forEach(countBarrier::count);
它更長。如果我們按照高爾夫標準,顯然第一個更好。
但是,最后一個片段(已經很差)甚至不起作用 - 正如您發現的那樣。問題是,forEach'''''''''''''''''''''''''''''''''''''''''''''''''''函式的目的是對流中的每個事物執行一次給定的消費者(它是一個接受1個引數且不回傳任何內容的代碼塊),并將流元素傳遞給每個“呼叫”。這里有一個整數流(從 0 到 4,含),所以.forEach的目的是為 0、1、2、3 和最后為 4 運行一些代碼,并將索引交給代碼. 因此,您的count方法必須將 a 作為引數int。它沒有,因此java拒絕編譯它。沒有語言特性可以告訴java:是的,無論如何,我不在乎i只需運行它 - 因為 java 就是這樣的堅持者。沒有(簡單的)解決方法。
這讓我們回到:
for (int i = 0; i < 5; i ) countBarrier.count();
是完成這項作業的最佳方式。所以,寫那個。
但是牙套是必須的!
不,他們不是。在風格指南中要求您使用它們有點常見,但是對于這個問題有一個非常非常簡單的解決方案:不要使用這種不必要的限制性風格指南,并且總是問為什么。風格指南并非來自山上;他們傳達的規則具有潛在的意圖。風格指南規則的意圖是“所有事物的大括號”,主要是您可以輕松添加額外的陳述句(也許除了呼叫每個回圈之外,您還想發出一條日志訊息count()),部分是為了避免真正排長龍。
Chaining lambdas / stream API has the exact same problem if you just pile it all on. Given your preferred (non-working) syntax, if you want to also emit a log message in addition to calling count, you need to turn this:
IntStream.range(0, 5).forEach(countBarrier::count);
into this:
IntStream.range(0, 5).forEach(() -> {
log.info("Counting!");
countBarrier.count();
});
That is way more rewriting compared to turning this:
for (int i = 0; i < 5; i ) countBarrier.count();
into this:
for (int i = 0; i < 5; i ) {
log.info("Counting!");
countBarrier.count();
}
There are only three options:
- You are a hopeless slave to a style guide. That's weird.
- You believe that some slight extra rewriting effort required to expand on a single-statement loop body is acceptable (in which case one-linering that traditional for loop is fine).
- You believe that some slight extra rewriting will lead to bugs or is just too much effort, so instead one should always write loops to be capable of receiving extra statements merely by injecting them in the middle. In which case your intended new-style
.forEachis a style violation.
Long lines? Well, it's obvious that piling on with stream API leads to incredulously long lines so that's clearly a non-starter: You can't use 'but lambdas let me put stuff on one line!' whilst also supporting the notion that all traditional for loops must use braces because otherwise lines get too long.
Lambdas are just better.. just.. because!
They definitely aren't. Lambdas in java have no idea if they run 'in context' or 'out of context'. For example, .forEach() runs 'in context': The call foo.forEach(x -> codeGoesHere) will finish all loops before that entire statement, lambda and all, 'resolves' and completes. However, that doesn't have to be the case with lambdas. For example, this:
new TreeSet<>(Comparator.comparing(Student::getName))
Never invokes any student object's .getName() method. That 'code' instead is saved in a field in TreeSet and will be invoked anytime you interact with that treeset. Which could be 5 days from now, in a different thread.
And java doesn't know.
Because of that, java intentionally makes these things non-transparent in lambdas of any stripe:
- You cannot throw checked exceptions from a lambda, even if the containing code catches them. i.e. this:
try {
listOfPaths.forEach(path -> books.append(Files.readString(path)));
} catch (IOException e) { .. }
Is a compiler error, because Files.readString is declared to throws IOException - and even though we catch it, javac has no idea if that lambda is run 'in context' or not, and therefore doesn't compile the above. Replace it with a plain jane for loop and that works fine.
Hence, lambdas aren't a replacement for loops / control flow statements like do, while, or try. They are different tools and are good at different things. Use the right tool for the job. Which, for this job, is not lambdas.
- Non-final local variables
You cannot interact with them or even read them from within a lambda. After all, if that code ends up running 5 days from now in a different thread, well, that context is long gone. Yes, we know that it'll run in context, but javac doesn't, and therefore won't let you. No such problem with traditional for loops.
- Control flow
You can break, continue, or return from inside a normal for loop to outside of it. But lambdas cannot do that. Again, same reason. It'd make absolutely no sense at all if the lambda runs out of its context. The thing you are attempting to break/continue/return to is looong gone.
Hence, whilst any and all benefits to using .forEach style looping are purely based on 'style' (and beauty is in the eye of the beholder. You cannot make rational arguments in favour of finding one thing more beautiful than another) – the benefits of using traditional for loops are objective. They can do more.
But forEach allows the list construct to control the looping behaviour
It's specced specifically to go in list order, sequentially. There is no difference in how it runs now and never will be, as that would be backwards incompatible.
But .forEach lets me omit the variable type, so, it is shorter
Not so, as var exists. This is legal java:
for (var map : listOfMapsOfStringsToLists) { .... }
But, hey, if you really want it...
IntStream is 'abuse' - the purpose of IntStream is to generate a consecutive sequence of int values. You have no interest in them (you are merely 'abusing' it to make something be called 5 times, you have no interest specifically in the sequence '0, 1, 2, 3, 4' - you merely have an interest in any sequence of length precisely 5), but because you have them, you can only use :: syntax to invoke a method that consumes an int. Your count() method doesn't (and you don't want it to), so you shouldn't be using IntStream. What you'd want instead is something like:
Loops.forEach(5, countBarrier::count);
Unfortunately, java doesn't have this method. But, you can write it yourself, of course! It's easy:
class Loops {
public static void forEach(int count, Runnable r) {
if (i < 0) throw new IllegalArgumentException("i is negative");
for (int i = 0; i < count; i ) r.run();
}
}
A trivial 2 liner.
uj5u.com熱心網友回復:
您傳入的方法參考forEach()被歸類為對特定物件的實體方法的參考。
containingObject::instanceMethodName
默認情況下,編譯器將假定流的整數元素需要用作方法引數。
因為您的方法count()沒有引數,所以您會收到編譯錯誤。如果你有一個多載版本,count(int)那么forEach(countBarrier::count)將編譯。
需要注意的重要一點是,您在這里通過更改流外部物件的狀態來濫用 Stream API。
方法forEach()只能通過副作用進行操作。根據檔案,它只能在沒有其他方法實作它的情況下使用。
例如,當您使用回傳流的方法進行撥號時,您需要以某種方式處理其元素,然后需要在每個流元素上呼叫方法。在這種情況下,使用forEach()將是合理的,因為沒有其他辦法。回圈不是一個選項,如果你創建一個中間集合,它會增加記憶體消耗并可能損害性能。
但是對于您的簡單示例,forEahc()并不是必不可少的。此操作不需要流。
uj5u.com熱心網友回復:
沒有更好的選擇。您可以撰寫自己的方法轉換countBarrier::count為 a Consumer<Integer>,但通常沒有一種方法可以比您現有的方法做得更好。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/456885.html
上一篇:如何在for回圈中使用random.sample()生成多個*不相同*樣本串列?
下一篇:加載csv并將列添加為for回圈
