141 votes

Java 8 constitue-t-il un bon moyen de répéter une valeur ou une fonction?

Dans de nombreuses autres langues, par exemple. Haskell, il est facile de répéter une valeur ou une fonction à plusieurs reprises, par exemple. pour obtenir une liste de 8 copies de la valeur 1:

take 8 (repeat 1)

mais je n'ai pas trouvé encore dans Java 8. Existe-t-il une fonction en Java 8 JDK?

Ou sinon quelque chose d'équivalent à une plage comme

[1..8]

Il peut sembler évident pour le remplacement d'une verbose déclaration en Java comme

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

pour avoir quelque chose comme

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

bien que cet exemple n'a pas l'air beaucoup plus concis en fait... mais j'espère que c'est plus lisible.

182voto

assylias Points 102015

Pour cet exemple spécifique, vous pouvez faire:

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

Si vous avez besoin d’une étape différente de 1, vous pouvez utiliser une fonction de mappage, par exemple pour une étape de 2:

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

Ou créez une itération personnalisée et limitez la taille de l'itération:

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

77voto

Stuart Marks Points 8927

Voici une autre technique que j'ai couru à travers de l'autre jour:

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

L' Collections.nCopies appel crée un List contenant n des copies de la valeur que vous fournissez. Dans ce cas, c'est le coffret Integer de la valeur 1. Bien sûr, il ne fait pas de créer une liste avec n éléments; il crée un "virtualisé" liste qui ne contient que la valeur et la longueur, et de tout appel à l' get au sein de la gamme renvoie simplement la valeur. L' nCopies méthode a été autour depuis les Collections de Cadre a été présenté chemin du retour dans le JDK 1.2. Bien sûr, la possibilité de créer un flux à partir de son résultat a été ajoutée dans Java SE 8.

Une grosse affaire, une autre façon de faire la même chose à peu près le même nombre de lignes.

Cependant, cette technique est plus rapide que l' IntStream.generate et IntStream.iterate approches, et étonnamment, il est également plus rapide que l' IntStream.range approche.

Pour iterate et generate le résultat est peut-être pas surprenant. Le flux de cadre (vraiment, la Spliterators pour ces cours d'eau) est construit sur l'hypothèse que les lambdas seront susceptibles de générer des valeurs différentes à chaque fois, et qu'ils vont générer un nombre illimité de résultats. Cela rend parallèle fractionnement particulièrement difficile. L' iterate méthode est également problématique pour ce cas, car chaque appel nécessite le résultat de la précédente. De sorte que le flux à l'aide generate et iterate ne font pas très bien pour générer répété des constantes.

Le rendement relativement faible de range est surprenant. Cela aussi est virtualisé, de sorte que les éléments ne fait pas exister dans la mémoire, et la taille est connue à l'avant. Cela devrait rendre rapidement et facilement parallélisables spliterator. Mais, étonnamment, n'a pas très bien. Peut-être la raison en est qu' range a pour calculer une valeur pour chaque élément de la gamme, puis appeler une fonction sur elle. Mais cette fonction ignore son entrée et retourne une constante, donc je suis surpris de découvrir que ce n'est pas incorporé et tué.

L' Collections.nCopies technique a faire boxing/unboxing afin de gérer les valeurs, car il n'existe pas de primitive de la spécialisation de l' List. Puisque la valeur est la même à chaque fois, c'est essentiellement en boîte une fois et que la zone est partagée par tous, n des copies. Je soupçonne boxing/unboxing est hautement optimisé, même intrinsified, et il peut être incorporé.

Voici le code:

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

Et voici la JMH résultats: (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

Il y a un juste montant de la variance dans le ncopies version, mais dans l'ensemble il semble confortablement 20x plus vite que la gamme de la version. (Je serais assez disposé à croire que j'ai fait quelque chose de mal, cependant).

Je suis surpris de voir à quel point l' nCopies technique fonctionne. En interne, il ne font pas grand-chose de spécial, avec le flux de la virtualisé liste tout simplement être mis en œuvre à l'aide de IntStream.range! J'avais prévu qu'il serait nécessaire de créer une institution spécialisée spliterator pour obtenir cela, pour aller vite, mais il semble déjà être assez bon.

39voto

msandiford Points 6297

Pour être complet et aussi parce que je ne pouvais pas m'en empêcher :)

La génération d'une séquence limitée de constantes est assez proche de ce que vous verriez dans Haskell, uniquement avec la verbosité au niveau Java.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X