Nous attendons depuis longtemps que lambda apporte le concept de fermeture à Java, mais si nous ne l'utilisons pas dans les collections, nous perdrons beaucoup de valeur. Le problème de la migration des interfaces existantes vers le style lambda a été résolu grâce aux méthodes par défaut. Dans cet article, nous analyserons en profondeur les opérations de données par lots dans les collections Java et percerons le mystère de l'effet le plus puissant de lambda.
Nous attendons depuis longtemps que lambda apporte le concept de fermeture à Java, mais si nous ne l'utilisons pas dans les collections, nous perdrons beaucoup de valeur. Le problème de la migration des interfaces existantes vers le style lambda a été résolu grâce à des méthodes par défaut. Dans cet article, nous analyserons en profondeur l'opération de données en masse (opération en masse) dans les collections Java et percerons le mystère du rôle le plus puissant de lambda.
1. À propos de JSR335
JSR est l'abréviation de Java Spécification Requests, qui signifie demande de spécification Java, la version principale de Java 8 Une amélioration est le projet Lambda (JSR 335), qui vise à rendre Java plus facile à coder pour les processeurs multicœurs.
2. Itération externe VS interne
Dans le passé, les collections Java n'étaient pas capables d'exprimer une itération interne, mais seulement fourni Une méthode d'itération externe est fournie, c'est-à-dire une boucle for ou while .
List persons = asList(new Person("Joe"), new Person("Jim"), new Person("John")); for (Person p : persons) { p.setLastName("Doe"); }
L'exemple ci-dessus est notre approche précédente, qui est ce qu'on appelle l'itération externe. La boucle est une boucle à séquence fixe. À l'ère multicœur d'aujourd'hui, si nous voulons paralléliser la boucle, nous devons modifier le code ci-dessus. La mesure dans laquelle l'efficacité peut être améliorée est encore incertaine, et cela entraînera certains risques (problèmes de sécurité des threads, etc.).
Pour décrire l'itération interne, nous devons utiliser une bibliothèque de classes comme Lambda. Utilisons lambda et Collection.forEach pour réécrire la boucle ci-dessus
persons.forEach(p->p.setLastName("Doe"));
Maintenant, la bibliothèque jdk contrôle la boucle. Nous n'avons plus besoin de nous soucier de la façon dont le nom de famille est défini pour chaque objet personne. La bibliothèque peut décider comment le faire en fonction. l'environnement d'exécution. Faites-le en parallèle, dans le désordre ou en chargement paresseux. Il s'agit d'une itération interne et le client transmet le comportement p.setLastName sous forme de données dans l'API. En fait, l'itération interne n'est pas étroitement liée au fonctionnement par lots des collections. Avec elle, on peut ressentir les changements dans l'expression grammaticale. La chose vraiment intéressante liée aux opérations par lots est la nouvelle API de flux. Le nouveau package java.util.stream a été ajouté au JDK 8.
3. API Stream
Le flux représente uniquement un flux de données et n'a pas de structure de données, il a donc été parcouru. ne peut plus être parcouru après une seule fois (cela doit être noté lors de la programmation, contrairement à Collection, il contient toujours des données quel que soit le nombre de fois où il est parcouru. Sa source peut être Collection, tableau, io, etc.).
3.1 Méthodes intermédiaires et finales
La fonction stream fournit une interface pour exploiter le Big Data, permettant aux données opérations Plus faciles et plus rapides. Il dispose de méthodes telles que le filtrage, le mappage et la réduction du nombre de parcours. Ces méthodes sont divisées en deux types : les méthodes intermédiaires et les méthodes terminales. L'abstraction "stream" doit être continue par nature. Les méthodes intermédiaires renvoient toujours un Stream, donc si. nous voulons obtenir le résultat final. Si tel est le cas, les opérations de point final doivent être utilisées pour collecter les résultats finaux produits par le flux. La différence entre ces deux méthodes est de regarder sa valeur de retour. S'il s'agit d'un Stream, c'est une méthode intermédiaire, sinon c'est une méthode de fin.
Une brève introduction à plusieurs méthodes intermédiaires (filtre, carte) et méthodes de point final (collecte, somme)
3.1.1Filtre
Implémenter la fonction de filtrage dans le flux de données est l'opération la plus naturelle à laquelle on puisse penser. L'interface Stream expose une méthode de filtre, qui peut accepter une implémentation de Predicate représentant une opération pour utiliser une lambdaexpression qui définit les conditions de filtre.
List persons = … Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//过滤18岁以上的人
3.1.2Carte
Supposons que nous filtrions certaines données maintenant, par exemple lors de la conversion d'objets. L'opération Map nous permet d'exécuter une implémentation de Function (les T et R génériques de Function
Stream adult= persons .stream() .filter(p -> p.getAge() > 18) .map(new Function() { @Override public Adult apply(Person person) { return new Adult(person);//将大于18岁的人转为成年人 } });
Maintenant, convertissez l’exemple ci-dessus en une expression lambda :
Stream map = persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person));
3.1.3Count
La méthode count est la méthode du point final d'un flux, qui peut être utilisée La finale les statistiques des résultats de flux reviennent à int. Par exemple, calculons le nombre total de personnes âgées de 18 ans ou plus
int countOfAdult=persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .count();
3.1.4Collecter
La méthode de collecte est également une méthode de point final du flux, qui peut collecter le résultat final
List adultList= persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .collect(Collectors.toList());
Ou, si nous voulons utiliser une classe d'implémentation spécifique Pour collecter les résultats :
List adultList = persons .stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .collect(Collectors.toCollection(ArrayList::new));
篇幅有限,其他的中间方法和终点方法就不一一介绍了,看了上面几个例子,大家明白这两种方法的区别即可,后面可根据需求来决定使用。
3.2顺序流与并行流
每个Stream都有两种模式:顺序执行和并行执行。
顺序流:
List <Person> people = list.getStream.collect(Collectors.toList());
并行流:
List <Person> people = list.getStream.parallel().collect(Collectors.toList());
顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
3.2.1并行流原理:
List originalList = someData; split1 = originalList(0, mid);//将数据分小部分 split2 = originalList(mid,end); new Runnable(split1.process());//小部分执行操作 new Runnable(split2.process()); List revisedList = split1 + split2;//将结果合并
大家对hadoop有稍微了解就知道,里面的 MapReduce 本身就是用于并行处理大数据集的软件框架,其 处理大数据的核心思想就是大而化小,分配到不同机器去运行map,最终通过reduce将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。
3.2.2顺序与并行性能测试对比
如果是多核机器,理论上并行流则会比顺序流快上一倍,下面是测试代码
long t0 = System.nanoTime(); //初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法 int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray(); long t1 = System.nanoTime(); //和上面功能一样,这里是用并行流来计算 int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray(); long t2 = System.nanoTime(); //我本机的结果是serial: 0.06s, parallel 0.02s,证明并行流确实比顺序流快 System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);
3.3关于Folk/Join框架
应用硬件的并行性在java 7就有了,那就是 java.util.concurrent 包的新增功能之一是一个 fork-join 风格的并行分解框架,同样也很强大高效,有兴趣的同学去研究,这里不详谈了,相比Stream.parallel()这种方式,我更倾向于后者。
4.总结
如果没有lambda,Stream用起来相当别扭,他会产生大量的匿名内部类,比如上面的3.1.2map例子,如果没有default method,集合框架更改势必会引起大量的改动,所以lambda+default method使得jdk库更加强大,以及灵活,Stream以及集合框架的改进便是最好的证明。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!