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

다른 많은 언어에서. 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))

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



답변

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

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


답변

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

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! 이 작업을 빠르게 수행하려면 특수 분할기를 만들어야한다고 예상했지만 이미 꽤 괜찮은 것 같습니다.


답변

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

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

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


답변

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

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"));


답변

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

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!");


답변