548 votes

Java 8 Iterable.forEach() vs boucle foreach

Lequel des éléments suivants constitue une meilleure pratique en Java 8 ?

Java 8 :

joins.forEach(join -> mIrc.join(mSession, join));

Java 7 :

for (String join : joins) {
    mIrc.join(mSession, join);
}

J'ai beaucoup de boucles for qui pourraient être "simplifiées" avec des lambdas, mais y a-t-il vraiment un avantage à les utiliser ? Cela améliorerait-il leurs performances et leur lisibilité ?

EDIT

Je vais également étendre cette question aux méthodes plus longues. Je sais que vous ne pouvez pas retourner ou interrompre la fonction parentale à partir d'une méthode lambda et que cela doit également être pris en compte lors de la comparaison, mais y a-t-il autre chose à prendre en considération ?

0 votes

Je crois que les lamdas sont destinés à améliorer l'utilisation des processeurs multi-cœurs. Si votre environnement cible est multi-core, je pense que les lamdas auront des performances améliorées par rapport à la boucle for de java 7.

15 votes

Il n'y a pas de réel avantage en termes de performances d'un système par rapport à un autre. La première option est quelque chose qui s'inspire de la FP (dont on parle généralement comme d'une manière plus "agréable" et "claire" d'exprimer votre code). En réalité, il s'agit plutôt d'une question de "style".

5 votes

@Dwb : dans ce cas, ce n'est pas pertinent. forEach n'est pas défini comme étant parallèle ou quelque chose comme ça, donc ces deux choses sont sémantiquement équivalentes. Bien sûr, il est possible d'implémenter une version parallèle de forEach (et il se peut qu'elle soit déjà présente dans la bibliothèque standard), et dans ce cas, la syntaxe des expressions lambda serait très utile.

633voto

Aleksandr Dubinsky Points 2488

La meilleure pratique consiste à utiliser for-each . En plus de violer la Gardez-le simple, stupide principe, le nouveau forEach() présente au moins les déficiences suivantes :

  • Impossible d'utiliser des variables non finales . Ainsi, un code comme celui qui suit ne peut pas être transformé en un lambda forEach :
Object prev = null;
for(Object curr : list)
{
    if( prev != null )
        foo(prev, curr);
    prev = curr;
}
  • Ne peut pas gérer les exceptions vérifiées . Il n'est pas vraiment interdit aux lambdas de lancer des exceptions vérifiées, mais les interfaces fonctionnelles communes telles que Consumer n'en déclarent pas. Par conséquent, tout code qui lève des exceptions vérifiées doit les envelopper dans des fichiers try-catch o Throwables.propagate() . Mais même si vous faites cela, il n'est pas toujours évident de savoir ce qu'il advient de l'exception levée. Elle pourrait être avalée quelque part dans les entrailles de forEach()

  • Contrôle de flux limité . A return dans un lambda équivaut à un continue dans un for-each, mais il n'y a pas d'équivalent à un break . Il est également difficile de faire des choses comme des valeurs de retour, un court-circuit ou des drapeaux définis (ce qui aurait allégé un peu les choses, si ce n'était pas une violation de l'accord de l pas de variables non finales règle). "Il ne s'agit pas seulement d'une optimisation, mais d'un élément critique si l'on considère que certaines séquences (comme la lecture des lignes d'un fichier) peuvent avoir des effets secondaires, ou que l'on peut avoir une séquence infinie."

  • Peut s'exécuter en parallèle Ce qui est une chose horrible, horrible pour tout sauf pour le 0,1% de votre code qui a besoin d'être optimisé. Tout code parallèle doit être réfléchi (même s'il n'utilise pas de verrous, de volatiles et d'autres aspects particulièrement désagréables de l'exécution multithread traditionnelle). Tout bogue sera difficile à trouver.

  • Peut nuire aux performances En effet, le JIT ne peut pas optimiser forEach()+lambda dans la même mesure que les boucles simples, surtout maintenant que les lambdas sont nouveaux. Par "optimisation", je n'entends pas le surcoût lié à l'appel des lambdas (qui est faible), mais l'analyse et la transformation sophistiquées que le compilateur JIT moderne effectue sur le code en cours d'exécution.

  • Si vous avez besoin de parallélisme, il est probablement beaucoup plus rapide et pas beaucoup plus difficile d'utiliser un ExecutorService . Les flux sont à la fois automatiques (lire : ne savent pas grand chose de votre problème). et utiliser une stratégie de parallélisation spécialisée (lire : inefficace pour le cas général) ( décomposition récursive à la fourche et à la jonction ).

  • Rend le débogage plus confus à cause de la hiérarchie d'appels imbriqués et, Dieu nous en préserve, de l'exécution parallèle. Le débogueur peut avoir des problèmes pour afficher les variables du code environnant, et des choses comme le step-through peuvent ne pas fonctionner comme prévu.

  • Les flux en général sont plus difficiles à coder, à lire et à déboguer. . En fait, c'est vrai pour les " complexes ". couramment " Les API en général. La combinaison d'instructions simples complexes, l'utilisation intensive de génériques et le manque de variables intermédiaires conspirent pour produire des messages d'erreur confus et frustrer le débogage. Au lieu de "cette méthode n'a pas de surcharge pour le type X", vous obtenez un message d'erreur plus proche de "quelque part, vous vous êtes trompé dans les types, mais nous ne savons pas où ni comment". De même, vous ne pouvez pas examiner les choses dans un débogueur aussi facilement que lorsque le code est divisé en plusieurs instructions et que les valeurs intermédiaires sont enregistrées dans des variables. Enfin, lire le code et comprendre les types et le comportement à chaque étape de l'exécution peut s'avérer non trivial.

  • Il se démarque comme un pouce endolori . Le langage Java dispose déjà de l'instruction for-each. Pourquoi la remplacer par un appel de fonction ? Pourquoi encourager la dissimulation des effets secondaires dans les expressions ? Pourquoi encourager l'utilisation de lignes uniques peu maniables ? Mélanger des for-each réguliers et des for-each nouveaux bon gré mal gré est un mauvais style. Le code devrait parler en idiomes (des modèles qui sont rapides à comprendre en raison de leur répétition), et moins d'idiomes sont utilisés, plus le code est clair et moins de temps est passé à décider quel idiome utiliser (une grande perte de temps pour les perfectionnistes comme moi !)

Comme vous pouvez le constater, je ne suis pas un grand fan de la méthode forEach(), sauf dans les cas où elle a un sens.

Je trouve particulièrement choquant le fait que Stream ne met pas en œuvre Iterable (en dépit du fait qu'il existe une méthode iterator ) et ne peut pas être utilisé dans un for-each, seulement avec un forEach(). Je recommande de convertir les Streams en Iterables avec la fonction (Iterable<T>)stream::iterator . Une meilleure alternative est d'utiliser StreamEx qui corrige un certain nombre de problèmes liés à l'API Stream, notamment l'implémentation de Iterable .

Cela dit, forEach() est utile dans les cas suivants :

  • Itération atomique sur une liste synchronisée . Auparavant, une liste générée avec Collections.synchronizedList() était atomique par rapport à des choses comme get ou set, mais n'était pas thread-safe lors de l'itération.

  • Exécution parallèle (en utilisant un flux parallèle approprié) . Cela vous permet d'économiser quelques lignes de code par rapport à l'utilisation d'un ExecutorService, si votre problème correspond aux hypothèses de performance intégrées aux Streams et Spliterators.

  • Conteneurs spécifiques qui comme la liste synchronisée, bénéficient d'un contrôle de l'itération (bien que cela soit largement théorique, à moins que les gens puissent donner d'autres exemples).

  • Appeler une seule fonction plus proprement en utilisant forEach() et un argument de référence de la méthode (c'est-à-dire list.forEach (obj::someMethod) ). Cependant, gardez à l'esprit les points concernant les exceptions vérifiées, le débogage plus difficile et la réduction du nombre d'idiomes que vous utilisez lorsque vous écrivez du code.

Articles que j'ai utilisés comme référence :

EDITAR: Il semble que certaines des propositions originales pour les lambdas (telles que http://www.javac.info/closures-v06a.html Cache Google ) ont résolu certains des problèmes que j'ai mentionnés (tout en ajoutant leurs propres complications, bien sûr).

2 votes

La raison supposée pour laquelle Stream n'implémente pas Iterable est qu'en Java les itérables sont censés être toujours réutilisables (c'est-à-dire qu'ils peuvent appeler iterator() plusieurs fois), alors qu'en C# ils sont explicitement autorisés à être à usage unique. Mais cela ne semble pas être une bonne raison. La plupart des développeurs C# n'apprécient pas ce point délicat, et ce n'est pas une énorme source de bogues. En fait, il existe déjà des Iterables en Java qui sont à usage unique.

110 votes

"Pourquoi encourager à cacher les effets secondaires quelque part dans les expressions ?" est la mauvaise question. L'approche fonctionnelle forEach est là pour encourager le style fonctionnel, c'est-à-dire l'utilisation d'expressions sans des effets secondaires. Si vous rencontrez la situation où le forEach ne fonctionne pas bien avec vos effets secondaires, vous devriez avoir le sentiment que vous n'utilisez pas le bon outil pour le travail. La réponse est alors simple : c'est parce que votre sentiment est juste, alors restez dans la boucle for-each pour cela. La méthode classique for La boucle n'est pas devenue dépréciée

22 votes

@Holger Comment forEach être utilisé sans effets secondaires ?

174voto

mschenk74 Points 1484

L'avantage intervient lorsque les opérations peuvent être exécutées en parallèle. (Voir http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and - la section sur l'itération interne et externe)

  • Le principal avantage, de mon point de vue, est que l'implémentation de ce qui doit être fait dans la boucle peut être définie sans avoir à décider si elle sera exécutée en parallèle ou en séquence.

  • Si vous voulez que votre boucle soit exécutée en parallèle, vous pouvez simplement écrire

     joins.parallelStream().forEach(join -> mIrc.join(mSession, join));

    Vous devrez écrire du code supplémentaire pour la gestion des fils, etc.

Note : Pour ma réponse, j'ai supposé que les jointures mettant en œuvre le java.util.Stream l'interface. Si les joints n'implémentent que l'interface java.util.Iterable interface, ce n'est plus vrai.

4 votes

Les diapositives d'un ingénieur oracle auquel il se réfère ( blogs.oracle.com/darcy/resource/Devoxx/ ) ne mentionnent pas le parallélisme dans ces expressions lambda. Le parallélisme peut se produire dans les méthodes de collecte en masse telles que map & fold qui ne sont pas vraiment liés aux lambdas.

1 votes

Il ne semble pas vraiment que le code de l'OP bénéficiera d'un parallélisme automatique ici (d'autant plus qu'il n'y a aucune garantie qu'il y en ait un). Nous ne savons pas vraiment ce qu'est "mIrc", mais "join" ne semble pas vraiment être quelque chose qui peut être exécuté hors de l'ordre.

0 votes

@ThomasJungblut Dans l'API ( lambdadoc.net/api/java/util/stream/ ), il existe une documentation sur le parallèle et le forEach.

130voto

Balder Points 3315

En lisant cette question, on peut avoir l'impression que Iterable#forEach en combinaison avec des expressions lambda est un raccourci/remplacement pour l'écriture d'une boucle for-each traditionnelle. Ce n'est tout simplement pas vrai. Ce code de l'OP :

joins.forEach(join -> mIrc.join(mSession, join));

est no conçu comme un raccourci pour écrire

for (String join : joins) {
    mIrc.join(mSession, join);
}

et ne devrait certainement pas être utilisé de cette manière. Il s'agit plutôt d'un raccourci (bien qu'il soit no exactement la même) pour écrire

joins.forEach(new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
});

Et c'est en remplacement du code Java 7 suivant :

final Consumer<T> c = new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
};
for (T t : joins) {
    c.accept(t);
}

Remplacer le corps d'une boucle par une interface fonctionnelle, comme dans les exemples ci-dessus, rend votre code plus explicite : Vous dites que (1) le corps de la boucle n'affecte pas le code environnant et le flux de contrôle, et (2) le corps de la boucle peut être remplacé par une implémentation différente de la fonction, sans affecter le code environnant. Le fait de ne pas pouvoir accéder aux variables non finales de la portée externe n'est pas un déficit des fonctions/lambdas, c'est un déficit de la fonction. fonctionnalité qui distingue la sémantique de Iterable#forEach de la sémantique d'une boucle for-each traditionnelle. Une fois que l'on s'est habitué à la syntaxe de Iterable#forEach cela rend le code plus lisible, car vous obtenez immédiatement ces informations supplémentaires sur le code.

Les boucles for-each traditionnelles resteront certainement bonne pratique (pour éviter l'expression galvaudée " meilleure pratique ") en Java. Mais cela ne signifie pas que Iterable#forEach devrait être considérée comme une mauvaise pratique ou un mauvais style. Il est toujours bon d'utiliser le bon outil pour faire le travail, et cela inclut le mélange de boucles for-each traditionnelles avec des boucles Iterable#forEach où cela a un sens.

Puisque les inconvénients de Iterable#forEach ont déjà été abordées dans ce fil de discussion, voici quelques raisons pour lesquelles vous pourriez probablement utiliser Iterable#forEach :

  • Pour rendre votre code plus explicite : Comme décrit ci-dessus, Iterable#forEach puede rendre votre code plus explicite et plus lisible dans certaines situations.

  • Pour rendre votre code plus extensible et plus facile à maintenir : L'utilisation d'une fonction comme corps d'une boucle vous permet de remplacer cette fonction par différentes implémentations (cf. Modèle de stratégie ). Vous pourriez par exemple facilement remplacer l'expression lambda par un appel de méthode, qui peut être écrasé par les sous-classes :

    joins.forEach(getJoinStrategy());

    Vous pourriez alors fournir des stratégies par défaut en utilisant un enum, qui implémente l'interface fonctionnelle. Cela rend non seulement votre code plus extensible, mais augmente également la maintenabilité car cela permet de dissocier l'implémentation de la boucle de la déclaration de la boucle.

  • Pour rendre votre code plus facile à déboguer : Séparer l'implémentation de la boucle de la déclaration peut également faciliter le débogage, car vous pouvez disposer d'une implémentation de débogage spécialisée, qui imprime des messages de débogage, sans avoir besoin d'encombrer votre code principal de if(DEBUG)System.out.println() . La mise en œuvre du débogage pourrait par exemple être une délégué que décore la mise en œuvre effective de la fonction.

  • Pour optimiser le code critique pour les performances : Contrairement à certaines des affirmations de ce fil de discussion, Iterable#forEach fait offrent déjà de meilleures performances qu'une boucle for-each traditionnelle, du moins lorsqu'on utilise ArrayList et qu'on exécute Hotspot en mode "-client". Bien que ce gain de performance soit faible et négligeable pour la plupart des cas d'utilisation, il existe des situations où cette performance supplémentaire peut faire la différence. Par exemple, les responsables de bibliothèques voudront certainement évaluer si certaines de leurs implémentations de boucles existantes devraient être remplacées par Iterable#forEach .

    Pour étayer cette affirmation par des faits, j'ai réalisé quelques micro-benchmarks avec Étrier . Voici le code de test (le dernier Caliper de git est nécessaire) :

    @VmOptions("-server")
    public class Java8IterationBenchmarks {
    
        public static class TestObject {
            public int result;
        }
    
        public @Param({"100", "10000"}) int elementCount;
    
        ArrayList<TestObject> list;
        TestObject[] array;
    
        @BeforeExperiment
        public void setup(){
            list = new ArrayList<>(elementCount);
            for (int i = 0; i < elementCount; i++) {
                list.add(new TestObject());
            }
            array = list.toArray(new TestObject[list.size()]);
        }
    
        @Benchmark
        public void timeTraditionalForEach(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : list) {
                    t.result++;
                }
            }
            return;
        }
    
        @Benchmark
        public void timeForEachAnonymousClass(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(new Consumer<TestObject>() {
                    @Override
                    public void accept(TestObject t) {
                        t.result++;
                    }
                });
            }
            return;
        }
    
        @Benchmark
        public void timeForEachLambda(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(t -> t.result++);
            }
            return;
        }
    
        @Benchmark
        public void timeForEachOverArray(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : array) {
                    t.result++;
                }
            }
        }
    }

    Et voici les résultats :

    Lorsqu'il est exécuté avec "-client", Iterable#forEach est plus performant que la traditionnelle boucle for sur une ArrayList, mais reste plus lent que l'itération directe sur un tableau. Lorsqu'elle est exécutée avec "-server", les performances de toutes les approches sont à peu près les mêmes.

  • Fournir un support optionnel pour l'exécution parallèle : Il a déjà été dit ici, que la possibilité d'exécuter l'interface fonctionnelle de Iterable#forEach en parallèle en utilisant ruisseaux est certainement un aspect important. Depuis Collection#parallelStream() ne garantit pas que la boucle soit effectivement exécutée en parallèle, il faut considérer cela comme une en option fonction. En itérant sur votre liste avec list.parallelStream().forEach(...); vous le dites explicitement : Cette boucle soutient exécution parallèle, mais elle n'en dépend pas. Encore une fois, il s'agit d'une fonctionnalité et non d'un déficit !

    En éloignant la décision d'exécution parallèle de l'implémentation réelle de la boucle, vous permettez une optimisation optionnelle de votre code, sans affecter le code lui-même, ce qui est une bonne chose. En outre, si l'implémentation par défaut du flux parallèle ne répond pas à vos besoins, personne ne vous empêche de fournir votre propre implémentation. Vous pourriez, par exemple, fournir une collection optimisée en fonction du système d'exploitation sous-jacent, de la taille de la collection, du nombre de cœurs et de certains paramètres de préférence :

    public abstract class MyOptimizedCollection<E> implements Collection<E>{
        private enum OperatingSystem{
            LINUX, WINDOWS, ANDROID
        }
        private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
        private int numberOfCores = Runtime.getRuntime().availableProcessors();
        private Collection<E> delegate;
    
        @Override
        public Stream<E> parallelStream() {
            if (!System.getProperty("parallelSupport").equals("true")) {
                return this.delegate.stream();
            }
            switch (operatingSystem) {
                case WINDOWS:
                    if (numberOfCores > 3 && delegate.size() > 10000) {
                        return this.delegate.parallelStream();
                    }else{
                        return this.delegate.stream();
                    }
                case LINUX:
                    return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
                case ANDROID:
                default:
                    return this.delegate.stream();
            }
        }
    }

    Ce qui est bien ici, c'est que l'implémentation de votre boucle n'a pas besoin de connaître ou de se soucier de ces détails.

5 votes

Vous avez un point de vue intéressant dans cette discussion et vous soulevez un certain nombre de points. Je vais essayer d'y répondre. Vous proposez de basculer entre forEach y for-each sur la base de certains critères concernant la nature du corps de la boucle. La sagesse et la discipline pour suivre de telles règles sont la marque d'un bon programmeur. De telles règles sont également son fléau, car les personnes qui l'entourent ne les suivent pas ou ne sont pas d'accord. Par exemple, l'utilisation d'exceptions vérifiées et non vérifiées. Cette situation semble encore plus nuancée. Mais, si le corps "n'affecte pas le code d'entourage ou le contrôle de flux", n'est-il pas préférable de le prendre en compte en tant que fonction ?

0 votes

Pour rendre le code plus extensible, vous argumentez en faveur de la factorisation des fonctions membres (qui peuvent être appelées à partir d'une fonction for-each ) et l'utilisation d'objets fonctionnels (qui peuvent également être appelés à partir d'un objet for-each ). Je ne suis pas opposé à l'une ou l'autre de ces techniques, et j'utilise également des objets fonctionnels. Je ne sais simplement pas pourquoi ils encouragent l'utilisation des objets fonctionnels. forEach() (qui, encore une fois, ne leur permet pas d'utiliser des exceptions vérifiées). Oui, la syntaxe est un peu plus courte. Quelles autres raisons ?

0 votes

Débattre de la performance est une boîte de Pandore. Les optimisations que la JVM peut faire à un programme for-each vont au-delà du coût de l'accès à une liste. La JVM peut déplacer les évaluations d'expressions redondantes, allouer des objets sur la pile au lieu du tas, etc, etc. C'est là que les lambdas sont en retard.

13voto

bayou.io Points 3680

forEach() peut être implémentée pour être plus rapide que la boucle for-each, parce que l'itérable connaît la meilleure façon d'itérer ses éléments, par opposition à l'itérateur standard. La différence est donc de boucler en interne ou en externe.

Par exemple ArrayList.forEach(action) peut être simplement mis en œuvre comme

for(int i=0; i<size; i++)
    action.accept(elements[i])

par opposition à la boucle for-each qui nécessite beaucoup d'échafaudages.

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`

Cependant, nous devons également tenir compte de deux frais généraux en utilisant les éléments suivants forEach() L'un crée l'objet lambda, l'autre invoque la méthode lambda. Ils ne sont probablement pas significatifs.

voir aussi http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ pour comparer les itérations internes/externes pour différents cas d'utilisation.

9 votes

Pourquoi l'itérable connaît-il le meilleur chemin mais pas l'itérateur ?

2 votes

Pas de différence essentielle, mais du code supplémentaire est nécessaire pour se conformer à l'interface des itérateurs, ce qui peut être plus coûteux.

1 votes

@zhong.j.yu si vous implémentez Collection vous implémentez aussi Iterable de toute façon. Il n'y a donc pas de surcharge de code en termes d'"ajout de code pour implémenter les méthodes d'interface manquantes", si c'est ce que vous voulez dire. Comme l'a dit mschenk74, il semble n'y avoir aucune raison pour laquelle vous ne pouvez pas modifier votre itérateur pour qu'il sache comment itérer sur votre collection de la meilleure façon possible. Je suis d'accord qu'il peut y avoir un surcoût pour la création d'un itérateur, mais sérieusement, ces choses sont généralement si bon marché, que vous pouvez dire qu'elles ont un coût nul...

10voto

Assaf Points 348

TL;DR : List.stream().forEach() était le plus rapide.

J'ai pensé que je devais ajouter mes résultats de l'itération de benchmarking. J'ai adopté une approche très simple (pas de cadres d'évaluation comparative) et j'ai évalué 5 méthodes différentes :

  1. classique for
  2. foreach classique
  3. List.forEach()
  4. List.stream().forEach()
  5. List.parallelStream().forEach

la procédure et les paramètres d'essai

private List<Integer> list;
private final int size = 1_000_000;

public MyClass(){
    list = new ArrayList<>();
    Random rand = new Random();
    for (int i = 0; i < size; ++i) {
        list.add(rand.nextInt(size * 50));
    }    
}
private void doIt(Integer i) {
    i *= 2; //so it won't get JITed out
}

La liste de cette classe doit être itérée et avoir une certaine doIt(Integer i) appliqué à tous ses membres, chaque fois par une méthode différente. Dans la classe Main, j'exécute la méthode testée trois fois pour chauffer la JVM. J'exécute ensuite la méthode testée 1000 fois en additionnant le temps nécessaire pour chaque méthode d'itération (en utilisant la fonction System.nanoTime() ). Après cela, je divise cette somme par 1000 et c'est le résultat, le temps moyen. exemple :

myClass.fored();
myClass.fored();
myClass.fored();
for (int i = 0; i < reps; ++i) {
    begin = System.nanoTime();
    myClass.fored();
    end = System.nanoTime();
    nanoSum += end - begin;
}
System.out.println(nanoSum / reps);

Je l'ai exécuté sur un processeur i5 à 4 cœurs, avec la version 1.8.0_05 de java.

classique for

for(int i = 0, l = list.size(); i < l; ++i) {
    doIt(list.get(i));
}

temps d'exécution : 4,21 ms

foreach classique

for(Integer i : list) {
    doIt(i);
}

temps d'exécution : 5,95 ms

List.forEach()

list.forEach((i) -> doIt(i));

temps d'exécution : 3,11 ms

List.stream().forEach()

list.stream().forEach((i) -> doIt(i));

temps d'exécution : 2,79 ms

List.parallelStream().forEach

list.parallelStream().forEach((i) -> doIt(i));

temps d'exécution : 3,6 ms

26 votes

Comment obtenez-vous ces chiffres ? Quel cadre de référence utilisez-vous ? Si vous n'en utilisez aucun et que vous vous contentez System.out.println d'afficher naïvement ces données, alors tous les résultats sont inutiles.

2 votes

Pas de cadre. J'utilise System.nanoTime() . Si vous lisez la réponse, vous verrez comment cela a été fait. Je ne pense pas que cela le rende inutile, étant donné qu'il s'agit d'une relatif question. Je ne me soucie pas de savoir si une certaine méthode a donné de bons résultats, je me soucie de savoir si elle a donné de bons résultats par rapport aux autres méthodes.

35 votes

Et c'est le but d'un bon micro benchmark. Puisque vous n'avez pas satisfait à ces exigences, les résultats sont inutiles.

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