эта статьяgithub/JavaMapОн был включен, есть карта расширенных технических знаний Java-программистов и моя серия статей, добро пожаловать в Star.
Выпущенная Oracle в 2014 году, Java8 является самой революционной версией со времен Java5.
Java8 вбирает в себя суть других языков и предоставляет ряд новых функций, таких как функциональное программирование, лямбда-выражения, потоки Stream и т. д. Изучение этих новых функций позволит вам достичь эффективного и элегантного кодирования.
1. Что такое поток?
Stream — это интерфейс, добавленный в Java 8, который позволяет декларативную обработку коллекций данных. Stream не является типом коллекции и не хранит данные, его можно рассматривать как расширенный итератор (Iterator), который проходит коллекцию данных.
Потоковые операции можно постепенно накладывать друг на друга, как в Builder, для формирования конвейера. Конвейер обычно состоит из источника данных + нуля или более промежуточных операций + терминальной операции. Промежуточные операции могут преобразовывать поток в другой поток, например фильтрация элементов с помощью фильтра и извлечение значений с помощью карты.
Потоки неотделимы от лямбда-выражений, по умолчанию вы освоили основы лямбда-выражений.
2. Особенности потока
- Его можно пройти (использовать) только один раз. Экземпляр Stream можно пройти только один раз, и один обход заканчивается после терминальной операции, а другой обход должен регенерировать экземпляр, что похоже на итератор Iterator.
- Защитите источники данных. Изменение любого элемента в потоке не приведет к изменению источника данных, например, при фильтрации и удалении элемента в потоке, и повторный обход источника данных все еще может получить элемент.
- ленивый. Операции фильтра и карты объединены в цепочку, образуя серию промежуточных операций, которые никогда не будут выполняться без терминальной операции (например, сбора).
3. Как создать экземпляр Stream
(1) Создайте экземпляр Stream с указанным значением
// of为Stream的静态方法
Stream<String> strStream = Stream.of("hello", "java8", "stream");
// 或者使用基本类型流
IntStream intStream = IntStream.of(1, 2, 3);
(2) Используйте коллекцию для создания экземпляра потока (общий способ)
// 使用guava库,初始化一个不可变的list对象
ImmutableList<Integer> integers = ImmutableList.of(1, 2, 3);
// List接口继承Collection接口,java8在Collection接口中添加了stream方法
Stream<Integer> stream = integers.stream();
(3) Создание экземпляра потока с использованием массива
// 初始化一个数组
Integer[] array = {1, 2, 3};
// 使用Arrays的静态方法stream
Stream<Integer> stream = Arrays.stream(array);
(4) Используйте генератор для создания экземпляра Stream
// 随机生成100个整数
Random random = new Random();
// 加上limit否则就是无限流了
Stream<Integer> stream = Stream.generate(random::nextInt).limit(100);
(5) Используйте итератор для создания экземпляра Stream
// 生成100个奇数,加上limit否则就是无限流了
Stream<Integer> stream = Stream.iterate(1, n -> n + 2).limit(100);
stream.forEach(System.out::println);
(6) Используйте интерфейс ввода-вывода для создания экземпляра Stream.
// 获取指定路径下文件信息,list方法返回Stream类型
Stream<Path> pathStream = Files.list(Paths.get("/"));
4. Потоковые общие операции
В интерфейсе Stream определено множество операций, которые можно условно разделить на две категории: одна — промежуточная операция, а другая — терминальная операция;
(1) Промежуточная операция
Промежуточные операции возвращают другой поток, и несколько промежуточных операций могут быть объединены для формирования запроса.
Промежуточные операции ленивы, если в потоке нет терминальной операции, то промежуточная операция не будет выполнять никакой обработки.
Общие промежуточные операции описаны ниже:
операция карты
map заключается в сопоставлении каждого элемента входного потока с другим элементом для формирования выходного потока.
// 初始化一个不可变字符串
List<String> words = ImmutableList.of("hello", "java8", "stream");
// 计算列表中每个单词的长度
List<Integer> list = words.stream()
.map(String::length)
.collect(Collectors.toList());
// output: 5 5 6
list.forEach(System.out::println);
операция flatMap
List<String[]> list1 = words.stream()
.map(word -> word.split("-"))
.collect(Collectors.toList());
// output: [Ljava.lang.String;@59f95c5d,
// [Ljava.lang.String;@5ccd43c2
list1.forEach(System.out::println);
Нари? Вы ожидали список, но вернули List
В это время можно подумать о преобразовании массива в поток, поэтому есть второй вариант
Stream<Stream<String>> arrStream = words.stream()
.map(word -> word.split("-"))
.map(Arrays::stream);
// output: java.util.stream.ReferencePipeline$Head@2c13da15,
// java.util.stream.ReferencePipeline$Head@77556fd
arrStream.forEach(System.out::println);
Все еще неправильно, эту проблему можно решить с помощью плоского потока flatMap, flatMap берет каждый элемент в потоке и преобразует его в другой выходной поток.
Stream<String> strStream = words.stream()
.map(word -> word.split("-"))
.flatMap(Arrays::stream)
.distinct();
// output: hello java8 stream
strStream.forEach(System.out::println);
операция фильтра
filter получает объект Predicate, фильтрует в соответствии с условиями, и элементы, удовлетворяющие условиям, генерируют другой поток.
// 过滤出单词长度大于5的单词,并打印出来
List<String> words = ImmutableList.of("hello", "java8", "hello", "stream");
words.stream()
.filter(word -> word.length() > 5)
.collect(Collectors.toList())
.forEach(System.out::println);
// output: stream
(2) Работа терминала
Терминальная операция преобразует поток в определенное возвращаемое значение, такое как список, целое число и т. д. Общие терминальные операции: foreach, min, max, count и т. д.
Foreach очень распространен, давайте возьмем пример макс.
// 找出最大的值
List<Integer> integers = Arrays.asList(6, 20, 19);
integers.stream()
.max(Integer::compareTo)
.ifPresent(System.out::println);
// output: 20
5. Настоящий бой: используйте Stream для рефакторинга старого кода
Предположим, есть потребность: отфильтровать учащихся, чей возраст больше 20 и чей балл больше 95.
Используйте цикл for для записи:
private List<Student> getStudents() {
Student s1 = new Student("xiaoli", 18, 95);
Student s2 = new Student("xiaoming", 21, 100);
Student s3 = new Student("xiaohua", 19, 98);
List<Student> studentList = Lists.newArrayList();
studentList.add(s1);
studentList.add(s2);
studentList.add(s3);
return studentList;
}
public void refactorBefore() {
List<Student> studentList = getStudents();
// 使用临时list
List<Student> resultList = Lists.newArrayList();
for (Student s : studentList) {
if (s.getAge() > 20 && s.getScore() > 95) {
resultList.add(s);
}
}
// output: Student{name=xiaoming, age=21, score=100}
resultList.forEach(System.out::println);
}
Использование цикла for инициализирует временный список для хранения конечного результата, что в целом не выглядит элегантно и лаконично.
После рефакторинга с лямбдой и потоком:
public void refactorAfter() {
List<Student> studentLists = getStudents();
// output: Student{name=xiaoming, age=21, score=100}
studentLists.stream().filter(this::filterStudents).forEach(System.out::println);
}
private boolean filterStudents(Student student) {
// 过滤出年龄大于20岁并且分数大于95的学生
return student.getAge() > 20 && student.getScore() > 95;
}
Использование фильтров и ссылок на методы делает код более понятным и не требует объявления временного списка, что очень удобно.
6. Использование распространенных заблуждений о потоке
(1) Непонимание 1: повторное потребление объектов потока
После того, как объект потока будет использован, он не может быть использован снова.
List<String> strings = Arrays.asList("hello", "java8", "stream");
Stream<String> stream = strings.stream();
stream.forEach(System.out::println); // ok
stream.forEach(System.out::println); // IllegalStateException
После выполнения вышеуказанного кода сообщается об ошибке:
java.lang.IllegalStateException: stream has already been operated upon or closed
(2) Непонимание 2: изменить источник данных
Попытка добавить новый строковый объект во время потоковой операции приводит к ошибке:
List<String> strings = Arrays.asList("hello", "java8", "stream");
// expect: HELLO JAVA8 STREAM WORLD, but throw UnsupportedOperationException
strings.stream()
.map(s -> {
strings.add("world");
return s.toUpperCase();
}).forEach(System.out::println);
ПРИМЕЧАНИЕ. Обязательно не изменять источник данных во время манипуляции потока.
Суммировать
Потоковое программирование Java8 может сделать код в определенной степени красивым, но также избежать общих ошибок, таких как: не использовать объекты повторно, не изменять источник данных.
-- END --
Ежедневные лайки: Здравствуйте, технари, сначала поставьте лайк, а затем посмотрите на это, чтобы сформировать привычку, ваши лайки — это движущая сила на моем пути вперед, и следующий выпуск будет более захватывающим.
Введение: Блогер окончил Хуачжунский университет науки и технологий со степенью магистра, он программист со страстью к технологиям и страстью к жизни. За последние несколько лет он побродил по многим интернет-компаниям первой линии и имеет многолетний опыт разработки.
Публичный номер поиска в Wechat [Архитектор, который любит смеяться], у меня есть технологии и истории, которые ждут вас.
Статья постоянно обновляется вgithub/JavaMapВы можете увидеть мою заархивированную серию статей в , с опытом интервью и техническими галантерейными товарами, добро пожаловать в Star.