Новые возможности JDK8 [Lambda, функциональный интерфейс, Stream]

Набор свойств

1.1 Обзор

java.util.PropertiesУнаследовано от Hashtableдля представления постоянного набора свойств. Он хранит данные, используя структуру ключ-значение, где каждый ключ и соответствующее ему значение представляют собой строку. Этот класс также используется многими классами Java, например, при получении системных свойств System.getPropertiesметод возвращает Propertiesобъект.

1.2 Класс свойств

Метод строительства

  • public Properties(): Создает пустой список свойств.

Методы, связанные с потоками

  • public void load(InputStream inStream): Чтение пар ключ-значение из входного потока байтов.

  • public void load(Reader reader): Чтение пар ключ-значение из потока ввода символов.

В параметре используется байтовый входной поток, через объект потока его можно связать с файлом, чтобы можно было загрузить данные в тексте. Формат текстовых данных:

имя файла=a.txt
длина=209385038
местоположение=D:\a.txt

Демонстрация кода загрузки:

public static void main(String[] args) выбрасывает FileNotFoundException {
    // создать объект набора свойств
    Свойства pro = новые свойства();
    // Загружаем информацию в тексте в набор атрибутов
    pro.load(новый FileInputStream("read.txt"));
    // цикл по коллекции и печать
    Set<String> strings = pro.stringPropertyNames();
    for (Строковый ключ: строки) {
        System.out.println(key+" -- "+pro.getProperty(key));
    }
}

Лямбда-выражение

2.1 Обзор идей функционального программирования

В математике функция — это набор схем расчета с входом и выходом, то есть «что с чем делать». Условно говоря, объектно-ориентированное делает слишком большой акцент на том, что «вещи должны выполняться в форме объектов», в то время как функциональное мышление пытается игнорировать сложный синтаксис объектно-ориентированного подхода, акцентируя внимание на том, что делать, а не в какой форме .

у = 2*х + 5;

общественный класс А {

общедоступный метод int (int x) {

вернуть 2*х + 5;

}

}

А = новый А();

int y = a.method(5);

java.util. Класс сканера

Сканер sc = ...;

int число = sc.nextInt();

что делать, а не как

Действительно ли мы хотим создать анонимный объект внутреннего класса? Нет. Мы должны создать объект только для этого . Что мы действительно хотим сделать, так это: передать runкод в теле метода Threadклассу.

Передайте фрагмент кода — вот для чего мы здесь. И создание объекта — это только средство, которое необходимо использовать из-за ограничений объектно-ориентированного синтаксиса. Итак, есть ли способ проще? Если мы вернем фокус с «как делать» на суть «что делать», мы обнаружим, что до тех пор, пока цель может быть достигнута лучше, процесс и форма не важны.

2.2 Оптимизация лямбда

Когда для выполнения задачи необходимо запустить поток, java.lang.Runnableсодержание задачи обычно определяется через интерфейс, а java.lang.Threadпоток запускается с использованием класса.

Традиционный способ написания кода выглядит следующим образом:

открытый класс Demo03Thread {
    public static void main(String[] args) {
        новый поток (новый Runnable() {
            @Override
            публичный недействительный запуск () {
                System.out.println("Многопоточное выполнение задачи!");
            }
        }).начинать();
    }
}

В соответствии с идеей «все есть объект» такой подход понятен: сначала создайте Runnableанонимный объект внутреннего класса интерфейса, чтобы указать содержание задачи, а затем передайте его потоку для запуска.

Анализ кода:

Для Runnableиспользования анонимного внутреннего класса мы можем проанализировать несколько вещей:

  • ThreadКлассу требуется Runnableинтерфейс в качестве параметра, а абстрактный runметод является ядром, используемым для указания содержимого задачи потока;

  • Для указанного runтела метода должен требоваться класс реализации интерфейса Runnable;

  • Чтобы избежать проблем с определением RunnableImplкласса реализации, необходимо использовать анонимный внутренний класс ;

  • Абстрактный runметод должен быть переопределен, поэтому имя метода, параметры метода и возвращаемое значение метода должны быть записаны заново и не могут быть написаны неправильно;

  • На самом деле ключом кажется только тело метода .

Метод записи лямбда-выражения, код такой:

С помощью нового синтаксиса Java 8 Runnableметод написания анонимного внутреннего класса вышеуказанного интерфейса может быть эквивалентен более простому лямбда-выражению:

открытый класс Demo04LambdaRunnable {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Выполнение многопоточной задачи!")).start(); // Запускаем поток
    }
}

Этот код точно такой же, как эффект выполнения только что, и может быть передан на уровне компиляции 1.8 или выше. Это видно из семантики кода: мы запускаем поток, и содержание задачи потока указывается в более лаконичной форме.

Больше нет ограничения «необходимости создания объекта интерфейса», и больше нет бремени «перезаписи и перезаписи абстрактного метода», это так просто!

2.3 Формат лямбды

стандартный формат:

Lambda опускает объектно-ориентированные правила и положения, а формат состоит из 3 частей :

  • некоторые параметры

  • Стрелка

  • кусок кода

Стандартный формат лямбда-выражения :

(имя параметра типа параметра) -> {оператор кода}

Спецификация формата:

  • Синтаксис внутри круглых скобок такой же, как и для традиционных списков параметров метода: оставьте пустым, если параметр отсутствует; несколько параметров разделяются запятыми.

  • ->- это недавно введенный грамматический формат, представляющий действие указывания.

  • Синтаксис внутри фигурных скобок в основном соответствует требованиям к телу традиционного метода.

Анонимный внутренний класс по сравнению с лямбдой:

новый поток (новый Runnable() {
            @Override
            публичный недействительный запуск () {
                System.out.println("Многопоточное выполнение задачи!");
            }
}).начинать();

Внимательно проанализируйте код, Runnableв интерфейсе всего одно runопределение метода:

  • public abstract void run();

То есть сформулирован план действий (собственно метод):

  • Без параметров : для выполнения сценария не требуется никаких условий.

  • Нет возвращаемого значения : программа не дает никаких результатов.

  • Блок кода (тело метода): конкретные этапы выполнения решения.

Та же семантика отражена в Lambdaсинтаксисе, который проще:

() -> System.out.println("Многопоточное выполнение задачи!")
  • Предыдущая пара скобок — это runпараметры метода (нет), что означает, что никаких условий не требуется;

  • Стрелка посередине представляет собой передачу предыдущих параметров следующему коду;

  • Следующий выходной оператор представляет собой код бизнес-логики.

Параметры и возвращаемые значения:

В следующем примере демонстрируется java.util.Comparator<T>код сценария использования интерфейса, где абстрактный метод определен как:

  • public abstract int compare(T o1, T o2);

Когда необходимо отсортировать массив объектов, Arrays.sortметоду требуется Comparatorэкземпляр интерфейса для указания правил сортировки. Предположим, что есть Personкласс с String nameдвумя int ageпеременными-членами:

общественный класс Person {
    частное строковое имя;
    частный возраст;
    
    // Опускаем конструктор, метод toString и геттер-сеттер
}

традиционное письмо

Если вы используете традиционный код для Person[]сортировки массива, он записывается следующим образом:

открытый класс Demo05Comparator {
    public static void main(String[] args) {
        // Первоначально массив неупорядоченных по возрасту объектов
        Person[] array = { new Person("Гули Нажа", 19), new Person("Дильраба", 18), new Person("Марзаха", 20) };
        // анонимный внутренний класс
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            общественное сравнение (человек o1, человек o2) {
                вернуть o1.getAge() - o2.getAge();
            }
        };
        Arrays.sort(array, comp); // Второй параметр — это правило сортировки, которое является экземпляром интерфейса Comparator
        for (Человек-человек: массив) {
            System.out.println(человек);
        }
    }
}

Такой подход кажется «естественным» в объектно-ориентированном мышлении. Среди них Comparatorэкземпляр интерфейса (с использованием анонимного внутреннего класса) представляет правило сортировки «по возрасту от младшего к старшему».

анализ кода

Давайте разберемся, что на самом деле делает приведенный выше код.

  • Для сортировки Arrays.sortметодам нужны правила сортировки, то есть Comparatorэкземпляры интерфейсов, а compareключом являются абстрактные методы;

  • Для указанного compareтела метода должен требоваться класс реализации интерфейса Comparator;

  • Чтобы избежать проблем с определением ComparatorImplкласса реализации, необходимо использовать анонимный внутренний класс ;

  • Абстрактный compareметод должен быть переопределен, поэтому имя метода, параметры метода и возвращаемое значение метода должны быть записаны заново и не могут быть написаны неправильно;

  • На самом деле ключевыми являются только параметры и тело метода .

лямбда письмо

открытый класс Demo06ComparatorLambda {
    public static void main(String[] args) {
        массив человек[] = {
            новый Человек("Гули Нажа", 19),
            новый человек ("Ди Лиеба", 18),
            новый человек("Мальзахар", 20) };
        Arrays.sort (массив, (человек a, человек b) -> {
            вернуть a.getAge() - b.getAge();
        });
        for (Человек-человек: массив) {
            System.out.println(человек);
        }
    }
}

Пропустить формат:

пропуск правил

Основываясь на стандартном формате Lambda, правила использования многоточия следующие:

  1. Тип параметров в скобках можно не указывать;

  2. Если в скобках указан только один параметр , скобки можно опустить;

  3. Если внутри фигурных скобок находится один и только один оператор , фигурные скобки, ключевое слово return и точка с запятой оператора могут быть опущены независимо от того, есть ли возвращаемое значение.

Примечание. После освоения этих правил пропуска просмотрите случай многопоточности в начале этой главы соответствующим образом.

Можно вывести или опустить

Lambda подчеркивает «что делать», а не «как это делать», поэтому любая информация, которую можно получить, может быть опущена. Например, в приведенном выше примере также может использоваться пропуск Lambda:

Интерфейс Runnable упрощает:
1. () -> System.out.println("Многопоточное выполнение задачи!")
Упрощение интерфейса компаратора:
2. Arrays.sort(array, (a, b) -> a.getAge() - b.getAge());

2.4 Предпосылки для лямбда

Синтаксис Lambda очень лаконичен, без ограничений объектно-ориентированной сложности. Однако есть несколько моментов, которые требуют особого внимания при его использовании:

  1. Использование Lambda должно иметь интерфейс и требует наличия одного и только одного абстрактного метода в интерфейсе . Будь то встроенный интерфейс JDK Runnableили Comparatorпользовательский интерфейс, Lambda можно использовать только в том случае, если абстрактный метод в интерфейсе существует и уникален.

  2. Использование Lambda должно иметь интерфейс в качестве параметра метода. То есть тип параметра или локальной переменной метода должен быть типом интерфейса, соответствующим Lambda, чтобы Lambda можно было использовать как экземпляр интерфейса.

Примечание. Интерфейс с одним и только одним абстрактным методом называется « функциональным интерфейсом ».

функциональный интерфейс

3.1 Обзор

Функциональный интерфейс в Java относится к интерфейсу с одним и только одним абстрактным методом .

Функциональный интерфейс, то есть интерфейс, подходящий для сценариев функционального программирования. Воплощением функционального программирования на Java является Lambda, поэтому функциональный интерфейс — это интерфейс, который можно применить к Lambda. Только убедившись, что в интерфейсе есть один и только один абстрактный метод, можно плавно вывести Lambda в Java.

Примечания. С точки зрения приложения Lambda в Java можно рассматривать как упрощенный формат анонимного внутреннего класса, но в принципе они разные.

Формат

Просто убедитесь, что интерфейс имеет один и только один абстрактный метод:

Интерфейс модификатора имя интерфейса {
    имя метода публичного абстрактного типа возвращаемого значения (необязательная информация о параметре);
    // Прочее содержимое неабстрактного метода
}

Поскольку абстрактный метод в интерфейсе public abstractможно опустить, определить функциональный интерфейс очень просто:

открытый интерфейс MyFunctionalInterface {    
    аннулировать мой метод();
}

Аннотация функционального интерфейса

Подобно @Overrideроли аннотаций, Java 8 вводит новую аннотацию специально для функциональных интерфейсов: @FunctionalInterface. Эту аннотацию можно использовать для определения интерфейса:

@ФункциональныйИнтерфейс
открытый интерфейс MyFunctionalInterface {
    аннулировать мой метод();
}

Как только эта аннотация используется для определения интерфейса, компилятор принудительно проверит, имеет ли интерфейс один и только один абстрактный метод, иначе будет сообщено об ошибке. Однако, даже если эта аннотация не используется, пока соблюдается определение функционального интерфейса, это все еще функциональный интерфейс, и его можно использовать так же.

3.2 Часто используемые функциональные интерфейсы

JDK предоставляет большое количество часто используемых функциональных интерфейсов для расширения типичных сценариев использования Lambda, и они в основном java.util.functionпредоставляются в виде пакетов. Предыдущий MySupplierинтерфейс имитирует функциональный интерфейс: java.util.function.Supplier<T>. На самом деле их гораздо больше, ниже приведены самые простые интерфейсы и примеры использования.

Интерфейс поставщика

java.util.function.Supplier<T>Интерфейс, что означает «предоставление», соответствующее лямбда-выражение должно « предоставлять » данные объекта, соответствующие универсальному типу.

абстрактный метод: получить

Содержит только один метод без параметров: T get(). Используется для получения данных объекта типа, указанного универсальным параметром.

открытый класс Demo08Supplier {
    частная статическая строка getString (функция Supplier<String>) {
        функция возврата.получить();
    }
    public static void main(String[] args) {
        Строка msgA = "Привет";
        Строка msgB = "Мир";
        System.out.println(getString(() -> msgA + msgB));
    }
}

Найдите максимальное значение элемента массива

Используйте Supplierинтерфейс в качестве типа параметра метода и найдите максимальное значение в массиве int с помощью лямбда-выражения. Совет: для дженериков интерфейса используйте java.lang.Integerклассы.

Пример кода:

открытый класс DemoIntArray {
    public static void main(String[] args) {
        массив int[] = {10, 20, 100, 30, 40, 50};
        printMax(() -> {
            интервал макс = массив [0];
            for (int i = 1; i < array.length; i++) {
                если (массив [i] > макс) {              
                    макс = массив [я];
                }
            }
            возврат макс.;
        });
    }
    private static void printMax(Supplier<Integer> поставщик) {
        интервал макс = поставщик.получить();
        System.out.println (макс.);
    }
}

Потребительский интерфейс

java.util.function.Consumer<T>С интерфейсом все наоборот: вместо создания данных он потребляет данные, а его тип данных определяется общим параметром.

Абстрактный метод: принять

ConsumerИнтерфейс содержит абстрактные методы void accept(T t), предназначенные для использования данных указанного универсального типа. Основное использование, такое как:

//Дайте вам строку, используйте ее в верхнем регистре
импортировать java.util.function.Consumer;
открытый класс Demo09Consumer {
    public static void main(String[] args) {
        Строка str = "Привет, мир";
        //1.стандартный формат лямбда-выражения
        весело (стр, (строка с) -> {
            System.out.println(s.toUpperCase());
        });
        //2.лямбда-выражение упрощенный формат
        fun(str,s-> System.out.println(s.toUpperCase()));
    }
    /*
        Определите метод, который использует интерфейс Consumer в качестве параметра
        забавный метод: использовать переменную типа String
     */
    public static void fun(String s,Consumer<String> con) {
        con.accept(s);
    }
}

Функциональный интерфейс

java.util.function.Function<T,R>Интерфейс используется для получения данных другого типа на основе данных одного типа, первый из которых называется предусловием, а второй — постусловием. Есть вход и выход, поэтому он называется «Функция».

Абстрактный метод: применить

FunctionОсновной абстрактный метод в интерфейсе: R apply(T t), получить результат типа R по параметру типа T. Используемые сценарии, например: преобразование Stringтипа в Integerтип.

//Дайте вам число String, вы конвертируете его в число int
открытый класс Demo11FunctionApply {
    метод private static void (функция Function<String, Integer>, Str str) {
        int num = function.apply(str);
        System.out.println (число + 20);
    }
    public static void main(String[] args) {
        метод(ы -> Integer.parseInt(s), "10");
    }
}

Предикатный интерфейс

Иногда нам нужно оценить определенный тип данных, чтобы получить результат логического значения. Здесь можно использовать java.util.function.Predicate<T>интерфейсы .

Абстрактный метод: тест

PredicateИнтерфейс содержит один абстрактный метод: boolean test(T t). Он используется в сценариях условного суждения. Критерием условного суждения является логика входящего лямбда-выражения. Если длина строки больше 5, она считается очень длинной.

// 1. Упражнение: Определить, больше ли длина строки 5
// 2. Упражнение: Определить, содержит ли строка «H»
открытый класс Demo15PredicateTest {
    метод private static void (предикат Predicate<String>, String str) {
        логическое значение veryLong = predicate.test(str);
        System.out.println("Строка очень длинная: " + veryLong);
    }
    public static void main(String[] args) {
        метод(s -> s.length() > 5, "HelloWorld");
    }
}

Глава 4 Поток

В Java 8, благодаря функциональному программированию, представленному Lambda, представлена ​​новая концепция Stream для решения существующих недостатков существующих библиотек коллекций.

4.1 Введение

Код многошагового обхода для традиционных коллекций

Почти все коллекции (например, Collectionинтерфейсы или Mapинтерфейсы и т. д.) поддерживают прямые или косвенные операции обхода. Когда нам нужно работать с элементами в коллекции, помимо необходимого добавления, удаления и получения, наиболее типичным является обход коллекции. Например:

открытый класс Demo10ForEach {
    public static void main(String[] args) {
        List<String> список = новый ArrayList<>();
        list.add("Чжан Уцзи");
        list.add("Чжоу Чжируо");
        list.add("Чжао Минь");
        list.add("Чжан Цян");
        list.add("Чжан Санфэн");
        для (имя строки: список) {
            System.out.println(имя);
        }
    }  
}

Это очень простая операция обхода коллекции: распечатать каждую строку в коллекции.

Недостатки зацикливания

Lambda в Java 8 позволяет нам больше сосредоточиться на том, что делать («Что»), а не на том, как это делать («Как») Ранее это сравнивали с внутренними классами. Теперь, если мы внимательно посмотрим на приведенный выше пример кода, мы обнаружим, что:

  • Синтаксис цикла for: " как это сделать "

  • Тело цикла for - это " что делать "

Зачем использовать цикл? Из-за обхода. Но является ли зацикливание единственным способом прохождения? Обход означает, что каждый элемент обрабатывается один за другим, а не цикл, который обрабатывается последовательно от первого до последнего . Первое — цель, второе — путь.

Только представьте, если вы хотите отфильтровать элементы в коллекции:

  1. Отфильтровать набор A в подмножество B в соответствии с первым условием ;

  2. Затем отфильтруйте его в подмножество C в соответствии со вторым условием .

Так что делать? Путь до Java 8 может быть:

Этот код содержит три цикла, каждый со своим эффектом:

  1. Сначала отфильтруйте всех людей по фамилии Чжан;

  2. Затем отфильтруйте людей с тремя символами в имени;

  3. Наконец, распечатайте результаты.

открытый класс Demo11NormalFilter {
    public static void main(String[] args) {
        List<String> список = новый ArrayList<>();
        list.add("Чжан Уцзи");
        list.add("Чжоу Чжируо");
        list.add("Чжао Минь");
        list.add("Чжан Цян");
        list.add("Чжан Санфэн");
        List<String> zhangList = new ArrayList<>();
        для (имя строки: список) {
            если (имя.startsWith("张")) {
                zhangList.add(имя);
            }
        }
        List<String> shortList = new ArrayList<>();
        для (имя строки: zhangList) {
            если (имя.длина() == 3) {
                шортлист.добавить(имя);
            }
        }
        для (имя строки: короткий список) {
            System.out.println(имя);
        }
    }
}

Всякий раз, когда нам нужно работать с элементами в коллекции, нам всегда нужно зацикливаться, зацикливаться и перерабатывать. Это воспринимается как должное? нет. Циклы — это способ делать что-то, а не цель. С другой стороны, использование линейного цикла означает, что его можно пройти только один раз. Если вы хотите пройти снова, вы можете использовать только другой цикл, чтобы начать с самого начала.

Итак, какой более элегантный способ может предложить производный от Lambda Stream?

Лучший способ написать Stream

Давайте посмотрим, что может быть элегантнее с помощью Stream API Java 8:

открытый класс Demo12StreamFilter {
    public static void main(String[] args) {
        List<String> список = новый ArrayList<>();
        list.add("Чжан Уцзи");
        list.add("Чжоу Чжируо");
        list.add("Чжао Минь");
        list.add("Чжан Цян");
        list.add("Чжан Санфэн");
        список.поток()
            .filter(s -> s.startsWith("张"))
            .filter(s -> s.length() == 3)
            .forEach(s -> System.out.println(s));
    }
}

Непосредственное чтение буквального значения кода может прекрасно отображать семантику метода нерелевантной логики: получить поток, отфильтровать фамилию, отфильтровать длину до 3 и вывести по одному . Код не отражает использование линейных циклов или любого другого алгоритма обхода, и содержание того, что мы действительно хотим сделать, лучше отражено в коде.

4.2 Обзор потокового мышления

Примечание. Пожалуйста, временно забудьте о неотъемлемом впечатлении от традиционных потоков ввода-вывода!

В целом идея потоковой передачи похожа на « производственную линию » в заводских цехах.

 

Когда необходимо оперировать несколькими элементами (особенно многоэтапными операциями), учитывая производительность и удобство, мы должны сначала составить «образцовый» пошаговый план, а затем выполнять его в соответствии с планом.

 

На этом рисунке показаны многоэтапные операции, такие как фильтрация, сопоставление, пропуск и подсчет.Это схема обработки элементов коллекции, а схема представляет собой «функциональную модель». Каждое поле на диаграмме представляет собой «поток», который можно преобразовать из одной модели потока в другую, вызвав указанный метод. И крайняя справа цифра 3 — это окончательный результат.

, , и здесь все работают с функциональной моделью, а элементы коллекции на самом деле не обрабатываются filter. Только при выполнении последнего метода вся модель будет выполнять операции в соответствии с заданной стратегией. И это выигрывает от функции отложенного выполнения Lambda.mapskipcount

Примечания: «Поток» — это фактически функциональная модель элемента коллекции, это не коллекция и не структура данных, и он не хранит никаких элементов (или их адресных значений).

4.3 Получить потоковый режим

java.util.stream.Stream<T>Это наиболее часто используемый потоковый интерфейс, недавно добавленный в Java 8. (Это не функциональный интерфейс.)

Получить поток очень просто, и есть несколько распространенных способов:

  • Все Collectionколлекции могут streamполучать потоки методом по умолчанию;

  • StreamСтатический метод интерфейса ofможет получить поток, соответствующий массиву.

Способ 1: получить поток в соответствии с коллекцией

public default Stream<E> stream(): получить объект потока Stream, соответствующий объекту коллекции Collection.

Прежде всего, java.util.Collectionв интерфейс добавляется метод по умолчанию streamдля получения потока, поэтому все его классы реализации могут получать поток.

импортировать java.util.*;
импортировать java.util.stream.Stream;
/*
    Как получить стрим
    1. Методы в коллекции
        Потоковой поток ()
    2. Статические методы в интерфейсе Stream
        of(T...t) Добавить несколько данных в поток
 */
открытый класс Demo13GetStream {
    public static void main(String[] args) {
        List<String> список = новый ArrayList<>();
        // ...
        Stream<String> stream1 = list.stream();
        Set<String> set = new HashSet<>();
        // ...
        Stream<String> stream2 = set.stream();
    }
}

Способ 2: Получить поток по массиву

Если вы используете не коллекцию или карту, а массив, так как к объекту массива невозможно добавить метод по умолчанию, Streamв интерфейсе предусмотрен статический метод of, который очень прост в использовании:

импортировать java.util.stream.Stream;
открытый класс Demo14GetStream {
    public static void main(String[] args) {
        String[] array = { "Чжан Уцзи", "Чжан Цуйшань", "Чжан Саньфэн", "Чжан Июань" };
        Stream<String> поток = Stream.of(массив);
    }
}

Примечания: ofПараметр метода на самом деле является переменным параметром, поэтому поддерживаются массивы.

4.4 Общие методы

Операции потоковой модели очень богаты, и здесь представлены некоторые часто используемые API. Эти методы можно разделить на два типа:

  • Окончательный метод : тип возвращаемого значения больше не является Streamметодом собственного типа интерфейса, поэтому StringBuilderподобные цепные вызовы больше не поддерживаются. В этом разделе окончательные методы включают countи forEachметоды.

  • Нетерминальный метод : тип возвращаемого значения по-прежнему является Streamметодом собственного типа интерфейса, поэтому поддерживаются цепные вызовы. (За исключением конечных методов, остальные методы являются нетерминальными.)

Примечания. Дополнительные методы, выходящие за рамки этого раздела, см. в документации по API самостоятельно.

forEach : обрабатывать один за другим

Хотя имя метода вызывается forEach, но в отличие от псевдонима «для каждого» в цикле for, этот метод не гарантирует, что действия поэлементного потребления выполняются по порядку в потоке .

void forEach (действие Consumer<? super T>);

Этот метод получает Consumerфункцию интерфейса и передает каждый элемент потока функции для обработки. Например:

импортировать java.util.stream.Stream;
открытый класс Demo15StreamForEach {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("большой ребенок", "второй ребенок", "третий ребенок", "четвертый ребенок", "пятый ребенок", "шестой ребенок", "седьмой ребенок", "дедушка", " Эссенция змеи", "Эссенция скорпиона");
        //Stream<String> stream = Stream.of("Чжан Уцзи", "Чжан Саньфэн", "Чжоу Чжируо");
        stream.forEach((String str)->{System.out.println(str);});
    }
}

Здесь лямбда-выражение (String str)->{System.out.println(str);}является примером функционального интерфейса Consumer.

фильтр: фильтр

Методы могут использоваться filterдля преобразования потока в поток другого подмножества. Объявление метода:

Фильтр Stream<T>(предикат Predicate<? super T>);

Этот интерфейс получает Predicateпараметр функционального интерфейса (который может быть лямбда) в качестве условия фильтра.

основное использование

filterОсновной код, используемый методом в потоке Stream, выглядит следующим образом:

открытый класс Demo16StreamFilter {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Чжан Уцзи", "Чжан Саньфэн", "Чжоу Чжируо");
        Stream<String> result = original.filter((String s) -> {return s.startsWith("张");});
    }
}

Здесь условие фильтрации задается через лямбда-выражение: фамилия должна быть Чжан.

count: количество статистики

CollectionКак и методы в старых коллекциях size, потоки предоставляют countметоды для подсчета количества элементов в них:

длинный счет();

Этот метод возвращает длинное значение, представляющее количество элементов (больше не значение типа int, как в старой коллекции). Основное использование:

открытый класс Demo17StreamCount {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Чжан Уцзи", "Чжан Саньфэн", "Чжоу Чжируо");
        Stream<String> result = original.filter(s -> s.startsWith("张"));
        System.out.println(result.count()); // 2
    }
}

ограничение: взять первые несколько

limitМетод может перехватывать поток и использовать только первые n потоков. Подпись метода:

Stream<T> limit(long n): получить первые n элементов в объекте потока Stream и вернуть новый объект потока Stream.

Параметр имеет тип long, если текущая длина коллекции больше параметра, то он будет перехвачен, в противном случае операция выполняться не будет. Основное использование:

импортировать java.util.stream.Stream;
открытый класс Demo18StreamLimit {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Чжан Уцзи", "Чжан Саньфэн", "Чжоу Чжируо");
        Результат Stream<String> = original.limit(2);
        System.out.println(result.count()); // 2
    }
}

пропустить: пропустить первые несколько

Если вы хотите пропустить первые несколько элементов, вы можете использовать skipметод для получения нового потока после перехвата:

Stream<T> skip(long n): пропустить первые n элементов в объекте Stream и вернуть новый объект Stream.

Если текущая длина потока больше n, первые n потоков пропускаются, иначе будет получен пустой поток длины 0. Основное использование:

импортировать java.util.stream.Stream;
открытый класс Demo19StreamSkip {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Чжан Уцзи", "Чжан Саньфэн", "Чжоу Чжируо");
        Результат Stream<String> = original.skip(2);
        System.out.println(result.count()); // 1
    }
}

конкат: комбинация

Если у вас есть два потока и вы хотите объединить их в один поток, вы можете использовать Streamстатический метод интерфейса concat:

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): объединить два объекта Stream a и b в списке параметров в новый объект Stream

Примечания: Это статический метод, который отличается от методов java.lang.Stringв нем .concat

Основной код использования этого метода выглядит следующим образом:

импортировать java.util.stream.Stream;
открытый класс Demo20StreamConcat {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("Чжан Уцзи");
        Stream<String> streamB = Stream.of("Чжан Цуйшань");
        Результат Stream<String> = Stream.concat(streamA, streamB);
    }
}

4.5 Комплексный кейс Stream

Теперь есть две ArrayListколлекции для хранения имен нескольких членов команды, и необходимо использовать традиционный цикл for (или усовершенствованный цикл for), чтобы последовательно выполнять следующие шаги:

  1. Первой команде нужны только имена участников, имена которых состоят из 3 символов;

  2. После проверки первой команды требуются только 3 лучших человека;

  3. Второй команде нужны только имена членов по фамилии Чжан;

  4. После отбора второй команды 2 лучших человека не требуются;

  5. объединить две команды в одну команду;

  6. Распечатайте информацию об именах всей команды.

Код для двух команд (наборов) выглядит следующим образом:

открытый класс Demo21ArrayListNames {
    public static void main(String[] args) {
        Список<String> один = новый ArrayList<>();
        one.add("Ди Лиеба");
        one.add("Сун Юаньцяо");
        one.add("Су Синхэ");
        one.add("Лао-цзы");
        one.add("Чжуанцзы");
        one.add("внук");
        one.add("Хунцигун");
        Список<String> два = новый ArrayList<>();
        two.add("Гули Нажа");
        two.add("Чжан Уцзи");
        two.add("Чжан Саньфэн");
        two.add("Чжао Лиин");
        two.add("Чжан Эргоу");
        two.add("Чжан Тяньай");
        two.add("Чжан Сан");
        // ....
    }
}

традиционный способ

Использование цикла for, пример кода:

открытый класс Demo22ArrayListNames {
    public static void main(String[] args) {
        Список<String> один = новый ArrayList<>();
        // ...
        Список<String> два = новый ArrayList<>();
        // ...
        // Первой команде нужно только имя участника, чье имя состоит из 3 символов;
        Список<String> oneA = новый ArrayList<>();
        для (имя строки: один) {
            если (имя.длина() == 3) {
                oneA.add(имя);
            }
        }
        // После проверки первой команды требуются только первые 3 человека;
        Список<String> oneB = новый ArrayList<>();
        для (целое я = 0; я < 3; я ++) {
            oneB.add(oneA.get(i));
        }
        // Второй команде нужны только имена участников по фамилии Чжан;
        Список<String> twoA = новый ArrayList<>();
        для (имя строки: два) {
            если (имя.startsWith("张")) {
                дваA.добавить(имя);
            }
        }
        // После проверки второй команды первые 2 человека не нужны;
        Список<String> twoB = новый ArrayList<>();
        for (int i = 2; i < twoA.size(); i++) {
            twoB.add(twoA.get(i));
        }
        // Объединяем две команды в одну команду;
        List<String> totalNames = new ArrayList<>();
        totalNames.addAll (oneB);
        totalNames.addAll(twoB);        
        // Распечатать информацию об именах всей команды.
        for (Имя строки: totalNames) {
            System.out.println(имя);
        }
    }
}

Результат операции:

Сун Юаньцяо
Су Синхэ
Хун Цигун
Чжан Эргоу
Чжан Тианай
Чжан Сан

Поточный метод

Эквивалентный код потоковой передачи:

открытый класс Demo23StreamNames {
    public static void main(String[] args) {
        Список<String> один = новый ArrayList<>();
        // ...
        Список<String> два = новый ArrayList<>();
        // ...
        // Первой команде нужно только имя участника, чье имя состоит из 3 символов;
        // После проверки первой команды требуются только первые 3 человека;
        Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
        // Второй команде нужны только имена участников по фамилии Чжан;
        // После проверки второй команды первые 2 человека не нужны;
        Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);
        // Объединяем две команды в одну команду;
        // Создать объект Person на основе имени;
        // Распечатать информацию об объекте Person всей команды.
        Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s));
    }
}

Он работает точно так же:

Сун Юаньцяо
Су Синхэ
Хун Цигун
Чжан Эргоу
Чжан Тианай
Чжан Сан

4.6 Объединение функций и метод финализации

Среди различных методов, представленных выше, те, чье возвращаемое значение по-прежнему является Streamинтерфейсом, являются методами объединения функций , и они поддерживают цепные вызовы; в то время как те, чье возвращаемое значение больше не является Streamинтерфейсом, являются терминальными методами , которые больше не поддерживают цепные вызовы. Как показано в таблице ниже:

имя метода роль метода тип метода Поддерживать ли цепные вызовы
считать Статистика конец нет
для каждого Обрабатывать один за другим конец нет
фильтр фильтр объединение функций да
ограничение Возьмите первые несколько объединение функций да
пропускать пропустить первые несколько объединение функций да
конкат комбинация объединение функций да

Глава 5 Справочник по методам

5.1 Обзор и ссылки на методы

Давайте посмотрим на простой функциональный интерфейс для применения лямбда-выражений, получения строк в методе accept, целью которого является печать и отображение строк, затем код для его использования через лямбда очень прост:

открытый класс DemoPrintSimple {
    private static void printString (данные Consumer<String>, String str) {
        данные.принять(ул);
    }
    public static void main(String[] args) {
        printString(s -> System.out.println(s), "Hello World");
    }
}

Поскольку в лямбда-выражении вызывается уже реализованный метод println, вместо лямбда-выражения можно использовать ссылку на метод.

Символ означает: ::

Описание символа: Двойное двоеточие — это оператор ссылки на метод, а выражение, в котором он находится, называется ссылкой на метод .

Сценарий приложения: если схема функции, которая должна быть выражена Lambda, уже существует в реализации определенного метода, то можно использовать ссылку на метод.

Как и в приведенном выше примере, в объекте System.out есть метод println(String), который нам и нужен, тогда для интерфейса Consumer в качестве параметра следующие два метода написания полностью эквивалентны:

  • Метод записи лямбда-выражения: s -> System.out.println(s) После получения параметров оно будет передано в метод System.out.println для обработки Lambda.

  • Написание ссылки на метод: System.out::println напрямую заменяет Lambda методом println в System.out.

Выведение и исключение: Если вы используете Lambda, то по принципу « выводимое — это опускаемое », нет необходимости указывать тип параметра, и нет необходимости указывать перегруженную форму — они будут выведены автоматически. И если вы используете ссылку на метод, вы также можете вывести ее в соответствии с контекстом. Функциональный интерфейс — это основа Lambda, а ссылка на метод — это упрощенная форма Lambda.

5.2 Упрощение ссылки на метод

Просто "процитируйте" прошлое:

открытый класс DemoPrintRef {
    private static void printString (данные Consumer<String>, String str) {
        данные.принять(ул);
    }
    public static void main(String[] args) {
        printString(System.out::println, "HelloWorld");
    }
}

Обратите внимание на двойное двоеточие ::, которое называется « ссылкой на метод », а двойное двоеточие — это новый синтаксис.

5.3 Расширенные эталонные методы

Имя объекта — ссылочный метод члена

Это наиболее распространенное использование, аналогично предыдущему примеру. Если метод-член уже существует в классе, вы можете обратиться к методу-члену по имени объекта, код:

открытый класс DemoMethodRef {
     public static void main(String[] args) {
        Строка ул = "привет";
        printUP(str::toUpperCase);
    }
    public static void printUP(Supplier< String> sup ){
        Применить строку =sup.get();
        System.out.println(применить);
    }
}

Имя класса -- обратитесь к статическому методу

Поскольку java.lang.Mathстатический метод уже существует в классе random, когда нам нужно вызвать метод через Lambda, мы можем использовать ссылку на метод, записанную как:

открытый класс DemoMethodRef {
  public static void main(String[] args) {
        printRanNum(Math::random);
    }
    public static void printRanNum(Supplier<Double> sup ){
        Двойное применение =sup.get();
        System.out.println(применить);
    }
}

В этом примере следующие два способа записи эквивалентны:

  • Лямбда-выражения:n -> Math.abs(n)

  • Ссылка на метод:Math::abs

class -- ссылка на конструкцию

Поскольку имя конструктора точно такое же, как имя класса, оно не является фиксированным. Таким образом, ссылка на конструктор использует 类名称::newпредставление формата. Сначала простой Personкласс:

общественный класс Person {
    частное строковое имя;
    публичное лицо (имя строки) {
        это.имя = имя;
    }
    публичная строка getName () {
        вернуть имя;
    }
}

Чтобы использовать этот функциональный интерфейс, передайте его по ссылке на метод:

// Даем вам строку String (имя), преобразуем ее в объект Person
открытый класс Demo09Lambda {
    public static void main(String[] args) {
        Имя строки = "том";
        Person person = createPerson(Person::new, name);
        System.out.println(человек);
        
    }
    public static Person createPerson(Function<String, Person> fun , String name){
        Человек p = fun.apply(имя);
        вернуть р;
    }
}

В этом примере следующие два способа записи эквивалентны:

  • Лямбда-выражения:name -> new Person(name)

  • Ссылка на метод:Person::new

Массив -- ссылка на конструкцию

Массивы также являются Objectобъектами подкласса , поэтому у них также есть конструкторы, но синтаксис немного отличается. Если он соответствует сценарию использования Lambda, требуется функциональный интерфейс:

При применении этого интерфейса его можно передать по ссылке на метод:

//Дать вам целое число, получить массив, длина массива равна заданному целому числу
открытый класс Demo11ArrayInitRef {   
   public static void main(String[] args) {
        массив int[] = createArray(int[]::new, 3);
        System.out.println(массив.длина);
    }
    public static int[] createArray(Function<Integer, int[]> fun, int n){
        int[] p = fun.apply(n);
        вернуть р;
    }
}

В этом примере следующие два способа записи эквивалентны:

  • Лямбда-выражения:length -> new int[length]

  • Ссылка на метод:int[]::new

Примечание. Ссылка на метод — это аббревиатура для лямбда-выражений, которые соответствуют определенным обстоятельствам. Это делает наши лямбда-выражения более упорядоченными, а также может пониматься как аббревиатура лямбда-выражений. но следует отметить, что ссылка на метод может только «ссылаться» на существующий метод!

Je suppose que tu aimes

Origine blog.csdn.net/qq_46020806/article/details/130831178
conseillé
Classement