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.

 89
Author: Graeme Moss, 2013-08-30

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);
 114
Author: assylias,
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.

[[25]} Wielka mi rzecz, inny sposób, aby zrobić to samo w tej samej liczbie linii. Jednak ta technika jest szybsza niż podejście 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.
 41
Author: Stuart Marks,
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);
 26
Author: msandiford,
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"));
 5
Author: Hartmut,
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