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 principale amélioration de la version Java 8 est le projet Lambda (JSR 335), dont le but est. pour rendre Java plus facile à écrire du code pour les processeurs multicœurs. JSR 335=améliorations de l'interface d'expression lambda (méthode par défaut) opérations de données par lots. Avec les deux articles précédents, nous avons complètement appris le contenu pertinent du JSR335.
2. Itération externe VS interne
Dans le passé, les collections Java n'étaient pas capables d'exprimer une itération interne, mais fournissaient uniquement une méthode d'itération externe, 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 du 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 pas 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 de l'environnement d'exécution, du chargement parallèle, dans le désordre ou 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.
L'itération interne n'est en fait pas étroitement liée au fonctionnement par lots de la collection. 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 ne représente qu'un flux de données et n'a pas de structure de données, donc après avoir été parcouru une fois, il ne peut plus être utilisé Traversal (vous devez y faire attention lors de la programmation, contrairement à Collection, il contient toujours des données quel que soit le nombre de fois parcouru), sa source peut être Collection, tableau, io, etc.
3.1 Méthodes intermédiaires et finales
La fonction de flux consiste à fournir une interface pour exploiter le Big Data, rendant les opérations de données 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, une opération de point final doit être utilisée pour collecter le résultat final produit 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. Veuillez vous référer à l'API de Stream pour plus de détails.
Une brève introduction à plusieurs méthodes intermédiaires (filtre, carte) et méthodes de point final (collecte, somme)
3.1.1Filtre
La première étape consiste à implémenter le fonction de filtrage dans le flux de données L'opération la plus naturelle à laquelle on puisse penser. L'interface Stream expose une méthode de filtre, qui accepte une implémentation de Predicate représentant une opération pour utiliser une expression lambda 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 fonction (les T et R génériques de Function43ca9160a1fbc6e1e17f36fac17e2094 représentent respectivement l'entrée d'exécution et les résultats d'exécution), qui accepte les paramètres d'entrée et les renvoie. Voyons d’abord comment la décrire comme une classe interne anonyme :
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 établir les statistiques finales des résultats du flux et renvoyer un entier. 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 .4CollectLa méthode collect est également une méthode de point final du flux, qui peut collecter les résultats finaux
List adultList= persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .collect(Collectors.toList());Ou, si nous voulons utiliser une implémentation spécifique classe pour collecter les résultats :
List adultList = persons .stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .collect(Collectors.toCollection(ArrayList::new));L'espace est limité, donc les autres méthodes intermédiaires et méthodes de point final ne seront pas présentées une par une. Après avoir lu les exemples ci-dessus, il vous suffit de comprendre la différence. entre ces deux méthodes, et vous pourrez décider de les utiliser en fonction de vos besoins ultérieurement. 3.2 Flux séquentiel et flux parallèle Chaque flux a deux modes : l'exécution séquentielle et l'exécution parallèle.
Flux séquentiel :
List <Person> people = list.getStream.collect(Collectors.toList());Flux parallèle :
List <Person> people = list.getStream.parallel().collect(Collectors.toList());Comme son nom l'indique, lorsque vous utilisez la méthode séquentielle pour parcourir, lisez chaque élément avant de lire l'élément suivant. Lors de l'utilisation du parcours parallèle, le tableau sera divisé en plusieurs segments, chacun étant traité dans un thread différent, puis les résultats sont affichés ensemble. 3.2.1 Principe du flux parallèle :
List originalList = someData; split1 = originalList(0, mid);//将数据分小部分 split2 = originalList(mid,end); new Runnable(split1.process());//小部分执行操作 new Runnable(split2.process()); List revisedList = split1 + split2;//将结果合并3.2.2 Comparaison des tests de performances séquentiels et parallèles S'il s'agit d'une machine multicœurs, flux théoriquement parallèle sera meilleur que séquentiel Le flux est deux fois plus rapide Ce qui suit est le code de test
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 À propos du framework Folk/Join
Le parallélisme du matériel d'application est disponible dans Java 7, c'est-à-dire que l'une des nouvelles fonctionnalités du package java.util.concurrent est un cadre de décomposition parallèle de style fork-join, qui est également très puissant et efficace. Allez faire des recherches, je n'entrerai pas dans les détails ici. Par rapport à Stream.parallel(), je préfère ce dernier.
4.Résumé
S'il n'y a pas de lambda, Stream est assez difficile à utiliser. Il générera un grand nombre de classes internes anonymes, comme l'exemple 3.1.2map ci-dessus. Il n'y a pas de méthode par défaut, le cadre de collection va changer. Cela entraînera forcément de nombreux changements, donc la méthode par défaut lambda rend la bibliothèque jdk plus puissante et plus flexible. Les améliorations apportées aux cadres Stream et de collection en sont la meilleure preuve.
Pour plus de nouvelles fonctionnalités Java8, quelles sont les utilisations des expressions lambda (exemples d'utilisation) et les articles associés, veuillez faire attention au site Web PHP chinois !