Czy Java 8 to dobry sposób na powtórzenie wartości lub funkcji?
W wielu innych językach, np. Haskell, łatwo jest powtórzyć wartość lub funkcję wiele razy, np. aby uzyskać listę 8 kopii wartości 1:
take 8 (repeat 1)
Ale nie znalazłem jeszcze tego w Javie 8. Czy w JDK Javy 8 jest taka funkcja?
Lub alternatywnie coś równoważnego zakresowi jak
[1..8]
Wydaje się oczywistym zamiennikiem dla wyrażenia w Javie, takiego jak
for (int i = 1; i <= 8; i++) {
System.out.println(i);
}
Mieć coś takiego
Range.from(1, 8).forEach(i -> System.out.println(i))
Choć ten szczególny przykład nie wygląda bardziej zwięźle... ale mam nadzieję, że będzie bardziej czytelny.
4 answers
Dla tego konkretnego przykładu można zrobić:
IntStream.rangeClosed(1, 8)
.forEach(System.out::println);
Jeśli potrzebujesz kroku innego niż 1, możesz użyć funkcji mapowania, na przykład dla kroku 2:
IntStream.rangeClosed(1, 8)
.map(i -> 2 * i - 1)
.forEach(System.out::println);
Lub zbudować niestandardową iterację i ograniczyć rozmiar iteracji:
IntStream.iterate(1, i -> i + 2)
.limit(8)
.forEach(System.out::println);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-30 12:43:35
Oto kolejna technika, na którą ostatnio natknąłem się:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
Wywołanie Collections.nCopies
tworzy List
zawierające n
kopie dowolnej podanej wartości. W tym przypadku jest to boxed Integer
wartość 1. Oczywiście nie tworzy listy z elementami n
; tworzy "zwirtualizowaną" listę, która zawiera tylko wartość i długość, a każde wywołanie get
w zakresie zwraca tylko wartość. Metoda nCopies
istnieje od czasu wprowadzenia frameworka zbiorów w sposób w JDK 1.2. Oczywiście możliwość tworzenia strumienia z jego wyniku została dodana w Javie SE 8.
IntStream.generate
i IntStream.iterate
, i co zaskakujące, jest również szybsza niż podejście IntStream.range
.
Dla iterate
i generate
wynik nie jest chyba zbyt zaskakujący. Streams framework (tak naprawdę Spliteratory dla tych strumieni) zbudowany jest na założenie, że lambda będą potencjalnie generować różne wartości za każdym razem i że będą generować nieograniczoną liczbę wyników. To sprawia, że podział równoległy jest szczególnie trudny. Metoda iterate
jest również problematyczna w tym przypadku, ponieważ każde wywołanie wymaga wyniku poprzedniego. Tak więc strumienie generate
i iterate
nie sprawdzają się zbyt dobrze do generowania powtarzających się stałych.
Stosunkowo słaba wydajność {[18] } jest zaskakująca. To też jest zwirtualizowane, więc elementy właściwie nie wszystkie istnieją w pamięci, a rozmiar jest znany z góry. Powinno to zapewnić szybki i łatwy do równoległego rozdzielacz. Ale o dziwo nie poszło zbyt dobrze. Być może powodem jest to, że range
musi obliczyć wartość dla każdego elementu zakresu, a następnie wywołać na nim funkcję. Ale ta funkcja po prostu ignoruje jej wejście i zwraca stałą, więc jestem zaskoczony, że to nie jest inlined i zabity.
Technika Collections.nCopies
musi zrobić Boks/unboxing w celu obsługi wartości, ponieważ nie ma prymitywnych specjalizacji List
. Ponieważ wartość jest ta sama za każdym razem, jest ona w zasadzie pudełkowa raz i to pudełko jest współdzielone przez wszystkie n
kopie. Podejrzewam, że boks/unboxing jest wysoce zoptymalizowany, nawet intrinsyfikowany i może być dobrze inlined.
Oto kod:
public static final int LIMIT = 500_000_000;
public static final long VALUE = 3L;
public long range() {
return
LongStream.range(0, LIMIT)
.parallel()
.map(i -> VALUE)
.map(i -> i % 73 % 13)
.sum();
}
public long ncopies() {
return
Collections.nCopies(LIMIT, VALUE)
.parallelStream()
.mapToLong(i -> i)
.map(i -> i % 73 % 13)
.sum();
}
A oto wyniki JMH: (2.8 GHz Core2Duo)
Benchmark Mode Samples Mean Mean error Units
c.s.q.SO18532488.ncopies thrpt 5 7.547 2.904 ops/s
c.s.q.SO18532488.range thrpt 5 0.317 0.064 ops/s
Istnieje spora zmienność w wersji ncopies, ale ogólnie wydaje się wygodnie 20x szybszy niż wersja range. (Byłbym jednak skłonny uwierzyć, że zrobiłem coś złego.)
Jestem zaskoczony, jak dobrze działa ta technika. Wewnętrznie nie robi to zbyt wiele specjalnego, a strumień zwirtualizowanej listy jest po prostu zaimplementowany za pomocąIntStream.range
! Spodziewałem się, że konieczne będzie stworzenie wyspecjalizowanego spliteratora, aby to poszło szybko, ale już wydaje się być całkiem dobre.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-18 23:49:55
Dla kompletności, a także dlatego, że nie mogłem się powstrzymać:)
Generowanie ograniczonej sekwencji stałych jest dość zbliżone do tego, co można by zobaczyć w Haskell, tylko z poziomem szczegółowości Java.
IntStream.generate(() -> 1)
.limit(8)
.forEach(System.out::println);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-30 22:33:48
Gdy funkcja repeat jest gdzieś zdefiniowana jako
public static BiConsumer<Integer, Runnable> repeat = (n, f) -> {
for (int i = 1; i <= n; i++)
f.run();
};
Możesz użyć go od czasu do czasu w ten sposób, np.:
repeat.accept(8, () -> System.out.println("Yes"));
Aby uzyskać I odpowiednik Haskella
take 8 (repeat 1)
Możesz napisać
StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-16 10:31:58