Java 8이 값이나 기능을 반복하는 좋은 방법을 제공합니까?


118

다른 많은 언어에서. Haskell, 값이나 함수를 여러 번 반복하는 것은 쉽습니다. 값 1의 8 개 사본 목록을 얻으려면 :

take 8 (repeat 1)

하지만 Java 8에서는 아직 찾지 못했습니다. Java 8의 JDK에 이러한 기능이 있습니까?

또는 다음과 같은 범위에 해당하는 것

[1..8]

Java의 장황한 진술에 대한 명백한 대체물처럼 보일 것입니다.

for (int i = 1; i <= 8; i++) {
    System.out.println(i);
}

같은 것을 가지고

Range.from(1, 8).forEach(i -> System.out.println(i))

이 특정 예제는 실제로 훨씬 더 간결 해 보이지는 않지만 ...하지만 더 읽기 좋기를 바랍니다.


2
Streams API 를 공부 했습니까 ? JDK에 관한 한 이것이 최선의 방법입니다. 그것은 있어요 범위 그것이 내가 지금까지 발견 한 무엇을, 기능.
Marko Topolnik 2013 년

1
@MarkoTopolnik Streams 클래스 가 제거되었습니다 (더 정확하게는 여러 다른 클래스로 분할되었으며 일부 메서드가 완전히 제거되었습니다).
assylias 2013-08-30

3
for 루프를 자세히 호출합니다! Cobol 시대에 당신이 없었던 것은 다행입니다. 오름차순 숫자를 표시하기 위해 Cobol에서 10 개 이상의 선언문이 필요했습니다. 요즘 젊은이들은 그들이 얼마나 좋은지 감사하지 않습니다.
Gilbert Le Blanc

1
@GilbertLeBlanc 자세한 내용은 그것과 아무 관련이 없습니다. 루프는 구성 할 수 없으며 스트림은 구성 할 수 있습니다. 루프는 피할 수없는 반복으로 이어지고 스트림은 재사용을 허용합니다. 이러한 스트림은 루프보다 양적으로 더 나은 추상화이므로 선호되어야합니다.
Alain O'Dea

2
@GilbertLeBlanc와 우리는 눈 속에서 맨발로 코드를 작성해야했습니다.
Dawood ibn Kareem

답변:


155

이 특정 예의 경우 다음을 수행 할 수 있습니다.

IntStream.rangeClosed(1, 8)
         .forEach(System.out::println);

1과 다른 단계가 필요한 경우, 예를 들어 2 단계에 매핑 기능을 사용할 수 있습니다.

IntStream.rangeClosed(1, 8)
         .map(i -> 2 * i - 1)
         .forEach(System.out::println);

또는 사용자 지정 반복을 만들고 반복 크기를 제한합니다.

IntStream.iterate(1, i -> i + 2)
         .limit(8)
         .forEach(System.out::println);

4
클로저는 더 나은 것을 위해 Java 코드를 완전히 변환합니다. 그날을 고대하고 있습니다 ...
Marko Topolnik 2013-08-30

1
@jwenting 정말 의존적입니다-일반적으로 GUI 항목 (Swing 또는 JavaFX)을 사용하면 익명 클래스로 인해 많은 보일러 플레이트 를 제거 합니다 .
assylias 2013-08-30

8
@jwenting FP 경험이있는 사람에게 고차 함수를 중심으로하는 코드는 순전히 승리입니다. 그 경험이없는 사람에게는 기술을 업그레이드하거나 먼지 속에 남겨질 위험이 있습니다.
Marko Topolnik 2013-08-30

2
@MarkoTopolnik 약간 새로운 버전의 javadoc을 사용하고 싶을 수 있습니다 (빌드 78을 가리키고 있으며 최신 버전은 빌드 105 : download.java.net/lambda/b105/docs/api/java/util/stream/… )
Mark Rotteveel 2013-08-30

1
@GraemeMoss 여전히 동일한 패턴 ( IntStream.rangeClosed(1, 8).forEach(i -> methodNoArgs());)을 사용할 수 있지만 IMO를 혼동 하고이 경우 루프가 표시된 것처럼 보입니다.
assylias

65

다른 날에 내가 사용한 또 다른 기술이 있습니다.

Collections.nCopies(8, 1)
           .stream()
           .forEach(i -> System.out.println(i));

Collections.nCopies호출은 사용자가 제공하는 모든 값 의 List포함 n복사본을 만듭니다 . 이 경우 박스형 Integer값 1입니다. 물론 실제로 n요소가 있는 목록을 생성하지는 않습니다 . 값과 길이 만 포함 된 "가상화 된"목록을 만들고 get범위 내 호출 은 값을 반환합니다. 이 nCopies메서드는 Collections Framework가 JDK 1.2에서 도입 된 이후로 사용되었습니다. 물론 그 결과로부터 스트림을 생성하는 기능은 Java SE 8에 추가되었습니다.

큰 거래, 거의 동일한 줄 수에서 동일한 작업을 수행하는 또 다른 방법입니다.

그러나이 기술은 IntStream.generateIntStream.iterate접근 방식 보다 빠르며 놀랍게도 IntStream.range접근 방식 보다 빠릅니다 .

의 경우 iterategenerate결과는 아마도 너무 놀라운 일이 아니다. 스트림 프레임 워크 (실제로 이러한 스트림에 대한 분할 자)는 람다가 매번 다른 값을 잠재적으로 생성하고 무제한의 결과를 생성한다는 가정을 기반으로 구축됩니다. 이것은 병렬 분할을 특히 어렵게 만듭니다. 이 iterate메서드는 또한 각 호출에 이전 호출의 결과가 필요하기 때문에 문제가 있습니다. 스트림 사용 그래서 generateiterate반복 상수를 생성하기위한 아주 잘하지 않는다.

의 상대적으로 낮은 성능 range은 놀랍습니다. 이것도 가상화되므로 요소가 실제로 모두 메모리에 존재하는 것은 아니며 크기가 미리 알려집니다. 이것은 빠르고 쉽게 병렬화 할 수있는 분할자를 만들 것입니다. 그러나 놀랍게도 잘되지 않았습니다. 아마도 그 이유는 range범위의 각 요소에 대한 값을 계산 한 다음 함수를 호출해야하기 때문일 것입니다. 하지만이 함수는 입력을 무시하고 상수를 반환하므로 인라인 처리되지 않고 종료되지 않은 것이 놀랍습니다.

Collections.nCopies기술은 값을 처리하기 위해 boxing / unboxing을 수행해야 List합니다.. 값이 매번 같기 때문에 기본적으로 한 번 상자에 넣어 모든 n복사본에서 해당 상자를 공유합니다 . 나는 boxing / unboxing이 고도로 최적화되고 내재화되어 있고 잘 인라인 될 수 있다고 생각합니다.

코드는 다음과 같습니다.

    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();
}

다음은 JMH 결과입니다. (2.8GHz 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

ncopies 버전에는 상당한 차이가 있지만 전반적으로 범위 버전보다 20 배 더 빠릅니다. (하지만 내가 뭔가 잘못했다고 믿고 싶어요.)

nCopies기술이 얼마나 잘 작동 하는지 놀랐습니다 . 내부적으로는 가상화 된 목록의 스트림이 단순히 IntStream.range! 이 작업을 빠르게 수행하려면 특수 분할기를 만들어야한다고 예상했지만 이미 꽤 괜찮은 것 같습니다.


6
경험이 부족한 개발자는 nCopies실제로 아무것도 복사 하지 않고 "복사본"이 모두 해당 단일 객체를 가리키는 것을 알게되면 혼란 스럽거나 문제에 빠질 수 있습니다 . 이 예제의 boxed primitive와 같이 객체가 불변 이면 항상 안전합니다 . "boxed once"문에서 이것을 언급하지만,이 동작은 자동 복싱에만 국한되지 않기 때문에 여기서주의 사항을 명시 적으로 부르는 것이 좋습니다.
William Price

1
그래서 그것은 그것이 LongStream.range훨씬 더 느리다는 것을 의미 IntStream.range합니까? 따라서 제공하지 않는다는 IntStream(그러나 LongStream모든 정수 유형에 사용) 아이디어가 삭제 된 것은 좋은 일입니다 . 순차 사용 사례의 경우 스트림을 사용할 이유가 전혀 없습니다. Collections.nCopies(8, 1).forEach(i -> System.out.println(i));동일한 작업을 수행 Collections.nCopies(8, 1).stream().forEach(i -> System.out.println(i));하지만 훨씬 더 효율적일 수 있습니다.Collections.<Runnable>nCopies(8, () -> System.out.println(1)).forEach(Runnable::run);
Holger

1
@Holger,이 테스트는 깨끗한 유형 프로필에서 수행되었으므로 실제 세계와 관련이 없습니다. 아마 LongStream.range이 두지도가 있기 때문에 실적이 나쁜 LongFunction내부를하면서, ncopies세지도를 가지고 IntFunction, ToLongFunction그리고 LongFunction따라서 모든 람다는 단형이다. 사전 오염 된 유형 프로필 (실제 사례에 더 가깝 음)에서이 테스트를 실행하면 ncopies1.5 배 더 느립니다.
Tagir Valeev 2015

1
조기 최적화 FTW
Rafael Bugajewski 2011

1
완전성을 위해이 두 기술을 일반 오래된 for루프 와 비교하는 벤치 마크를 보는 것이 좋습니다 . 귀하의 솔루션은 Stream코드 보다 빠르지 만 for루프가 이들 중 하나를 상당한 차이로 이길 것입니다.
typeracer

35

완전성을 위해 그리고 또한 내가 스스로를 도울 수 없었기 때문에 :)

제한된 시퀀스의 상수를 생성하는 것은 Java 수준의 장황함 만 있으면 하스켈에서 볼 수있는 것과 상당히 유사합니다.

IntStream.generate(() -> 1)
         .limit(8)
         .forEach(System.out::println);

() -> 11 만 생성 할 것입니다. 따라서 출력은 1 1 1 1 1 1 1 1.
Christian Ullenboom 2014 년

4
예, OP의 첫 번째 Haskell 예제에 따라 take 8 (repeat 1). assylias는 다른 모든 경우를 거의 다룹니다.
clstrfsck 2014 년

3
Stream<T>또한 generate동일한 방식으로 제한 될 수있는 다른 유형의 무한 스트림을 가져 오는 일반적인 방법이 있습니다.
zstewart

11

반복 기능이 다음과 같이 정의되면

public static BiConsumer<Integer, Runnable> repeat = (n, f) -> {
    for (int i = 1; i <= n; i++)
        f.run();
};

이제 다음과 같은 방법으로 사용할 수 있습니다.

repeat.accept(8, () -> System.out.println("Yes"));

Haskell과 동등한 것을 얻으려면

take 8 (repeat 1)

당신은 쓸 수 있습니다

StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));

2
이것은 굉장합니다. 그러나 나는를 변경하여, 반복 다시의 수를 제공하기 위해 그것을 수정 RunnableFunction<Integer, ?>한 후 사용 f.apply(i).
Fons

0

이것은 시간 함수를 구현하는 내 솔루션입니다. 저는 주니어이기 때문에 이상적이지 않을 수 있음을 인정합니다. 어떤 이유로 든 이것이 좋은 생각이 아니라면 기쁩니다.

public static <T extends Object, R extends Void> R times(int count, Function<T, R> f, T t) {
    while (count > 0) {
        f.apply(t);
        count--;
    }
    return null;
}

다음은 몇 가지 사용 예입니다.

Function<String, Void> greet = greeting -> {
    System.out.println(greeting);
    return null;
};

times(3, greet, "Hello World!");
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.