Expresiones lambda y programación funcional.

Programación funcional - Stream

Descripción general

¿Por qué estudiar?

  • Capaz de comprender el código de la empresa.
  • Alta eficiencia en el procesamiento de colecciones en grandes cantidades.
  • Código altamente legible
  • Destruye el infierno de anidación
//查询未成年作家的评分在70以上的书籍 由于洋流影响所以作家和书籍可能出现重复,需要进行去重
List<Book> bookList = new ArrayList<>();
Set<Book> uniqueBookValues = new HashSet<>();
Set<Author> uniqueAuthorValues = new HashSet<>();
for (Author author : authors) {
    
    
    if (uniqueAuthorValues.add(author)) {
    
    
        if (author.getAge() < 18) {
    
    
            List<Book> books = author.getBooks();
            for (Book book : books) {
    
    
                if (book.getScore() > 70) {
    
    
                    if (uniqueBookValues.add(book)) {
    
    
                        bookList.add(book);
                    }
                }
            }
        }
    }
}
System.out.println(bookList);
List<Book> collect = authors.stream()
    .distinct()
    .filter(author -> author.getAge() < 18)
    .map(author -> author.getBooks())
    .flatMap(Collection::stream)
    .filter(book -> book.getScore() > 70)
    .distinct()
    .collect(Collectors.toList());
System.out.println(collect);

ideas de programación funcional

概念

El pensamiento orientado a objetos debe centrarse en qué objetos se utilizan para lograr qué cosas. La idea de programación funcional es similar a las funciones de nuestras matemáticas. Su principal preocupación es qué operaciones se realizan con los datos.

优点

  • Código simple y desarrollo rápido.
  • Cercano al lenguaje natural y fácil de entender.
  • Fácil de "programación concurrente"

expresión lambda

Descripción general

Lambda es un azúcar sintáctico en JDK8. Puede simplificar la escritura de algunas clases internas anónimas . Es una encarnación importante del pensamiento de programación funcional . No nos centremos en cuál es el objeto. En cambio, nos centramos más en lo que hacemos con los datos.

  • La definición de JAVA orientada a objetos es demasiado engorrosa: cuando se trata de diversas operaciones matemáticas, con frecuencia es necesario definir clases, objetos, métodos, etc., por lo que se introduce el método de programación Lambda.

3 características especiales introducidas por JDK8

  • Expresión Lambda, la premisa para usar Lambda es una interfaz funcional
  • El método común predeterminado se introdujo en la interfaz -> solo después de JDK8. Antes, la interfaz solo tenía variables globales y métodos abstractos.
  • 函数式接口:一个接口只有一个抽象方法

El método común predeterminado de la interfaz.

public interface defaultMethodTest {
    
    
    void test();
    default void test1(){
    
    
        System.out.println("接口中的普通方法");
    }
}
class defaultMethodTestImpl implements defaultMethodTest{
    
    

    @Override
    public void test() {
    
    
        
    }
}
  • Vemos que implementamos la interfaz defaultMethodTest y no estamos obligados a anular el método modificado predeterminado.
  • La palabra clave predeterminada representa un método común en la interfaz y no se puede omitir. No escribir predeterminado significa que se trata de un método abstracto.
    • En realidad, esta clase se introdujo para corregir los métodos extendidos en la interfaz en la versión antigua.

interfaz funcional

@FunctionalInterface
public interface defaultMethodTest {
    
    
    void test();
    default void test1(){
    
    
        System.out.println("接口中的普通方法");
    }
}
  • Utilice @FunctionalInterface para detectar si la interfaz actual es una interfaz funcional

核心原则

Se puede deducir y omitir.

基本格式

(参数列表)->{
    
    代码}

Predecesor de las expresiones Lambda

Su predecesor es una clase interna anónima . Comparemos las diferencias entre los dos (pero el requisito previo de la interfaz para la implementación de Lambda debe ser una interfaz funcional).

@FunctionalInterface
interface FuncInterface{
    
    
    void test();
}
public class LambdaTest {
    
    
    public static void main(String[] args) {
    
    
        fun(new FuncInterface() {
    
    
            @Override
            public void test() {
    
    
                System.out.println("测试匿名内部类的写法");
            }
        });
    }
    public static void fun(FuncInterface f){
    
    
        f.test();
    }
}

implementación lambda

  • Aquí tenemos una función particularmente poderosa de IDEA: alt+enter puede convertir la escritura de clases internas anónimas en nuestras expresiones Lambda.
  • Necesitamos hacer un buen uso de las herramientas.

imagen-20230605173949574

public class LambdaTest {
    
    
    public static void main(String[] args) {
    
    
        fun(() -> System.out.println("测试匿名内部类的写法"));
    }
    public static void fun(FuncInterface f){
    
    
        f.test();
    }
}

Omitir reglas

  • El tipo de parámetro se puede omitir
  • Cuando el cuerpo del método tiene solo una línea de código, las llaves regresan y se puede omitir el punto y coma de la única línea de código.
  • Los paréntesis se pueden omitir cuando el método tiene un solo parámetro.
  • Si no recuerdas las reglas anteriores, puedes omitirlas.

Sin valor de retorno, sin parámetros

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
public class LambdaTest2 {
    
    
    public static void main(String[] args) {
    
    
        //匿名类写法
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("演示无参数无返回值的Lambda表达式");
            }
        }).start();
        //无参数无返回值写法
        new Thread(()->{
    
    
            System.out.println("演示无参数无返回值的Lambda表达式");
        }).start();
        //若只有一句语句,可省略大括号
        new Thread(()-> System.out.println("演示无参数无返回值的Lambda表达式")).start();
    }
}
  • Si solo hay un cuerpo de método, puede omitir las llaves

Sin valor de retorno con parámetros

@FunctionalInterface
public interface IntConsumer {
    void accept(int value);

    default IntConsumer andThen(IntConsumer after) {
        Objects.requireNonNull(after);
        return (int t) -> { accept(t); after.accept(t); };
    }
}
public class LambdaTest3 {
    
    
    public static void foreachArr(IntConsumer consumer){
    
    
        int[] arr = {
    
    1,2,3};
        for (int i : arr) {
    
    
            consumer.accept(i);
        }
    }

    public static void main(String[] args) {
    
    
        //匿名内部类写法
        foreachArr(new IntConsumer() {
    
    
            @Override
            public void accept(int value) {
    
    
                System.out.println("测试有参无返回值的Lambda表达式"+value);
            }
        });
        //Lambda表达式 
        foreachArr((int value)->{
    
    
            System.out.println("测试有参无返回值的Lambda表达式"+value);
        });
        //只有一个参数省略括号
        foreachArr(value -> {
    
    
            System.out.println("测试有参无返回值的Lambda表达式"+value);
        });
    }
}
  • Si el método tiene solo un parámetro, puedes omitir los paréntesis.
  • Puede omitir el tipo de parámetro en Lambda, si omite el tipo, debe omitirlo.

Tiene un valor de retorno y no tiene parámetros.

public class LambdaTest4 {
    
    
    public static void main(String[] args) {
    
    
        //匿名内部类实现方式
        test(new Supplier<String>() {
    
    
            @Override
            public String get() {
    
    
                return "测试无参数有返回值的Lambda表达式";
            }
        });
        //lambda实现方式
        test(()->{
    
    
            return "测试无参数有返回值的Lambda表达式";
        });
        //省略版本 因为只有一条return语句
        test(()->"测试无参数有返回值的Lambda表达式");
    }
    public static void test(Supplier<String> supplier){
    
    
        System.out.println(supplier.get());
    }
}
  • Si el método abstracto tiene un valor de retorno y el código del cuerpo del método anulado tiene solo una línea, entonces el retorno y las llaves se pueden omitir en este momento.

Hay parámetros y valores de retorno.

public class LambdaTest5 {
    
    
    public static int calculateNum(IntBinaryOperator operator){
    
    
        int a = 10;
        int b = 20;
        return operator.applyAsInt(a, b);
    }

    public static void main(String[] args) {
    
    
        //匿名内部类写法
        int i = calculateNum(new IntBinaryOperator() {
    
    
            @Override
            public int applyAsInt(int left, int right) {
    
    
                return left + right;
            }
        });
        System.out.println(i);
        //lambda写法
        int i1 = calculateNum((int left, int right) -> {
    
    
            return left + right;
        });
        System.out.println(i1);
        //lambda省略版写法
        int i2 = calculateNum(((left, right) -> left + right));
        System.out.println(i2);
    }
}
  • Puede omitir el tipo de parámetro en Lambda, si omite el tipo, debe omitirlo.

Arroyo

Descripción general

Stream de Java8 utiliza un modelo de programación funcional y, como su nombre indica, se puede utilizar para realizar operaciones de transmisión en cadena en colecciones o matrices. Nos facilita operar en conjuntos o matrices.

Preparación de datos de casos

clase de autor

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author {
    
    
      //id
      private Long id;
      //姓名
      private String name;
      //年龄
      private Integer age;
      //简介
      private String intro;
      //作品
      private List<Book> books;
}

Libros

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Book {
    
    
        //id
        private Long id;
        //书名
        private String name;

        //分类
        private String category;

        //评分
        private Integer score;

        //简介
        private String intro;

}

Inicialización de datos

 private static List<Author> getAuthors() {
        //数据初始化
        Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
        Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
        Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
        Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);

        //书籍列表
        List<Book> books1 = new ArrayList<>();
        List<Book> books2 = new ArrayList<>();
        List<Book> books3 = new ArrayList<>();

        books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
        books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));

        books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
        books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
        books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));

        books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
        books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
        books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));

        author.setBooks(books1);
        author2.setBooks(books2);
        author3.setBooks(books3);
        author4.setBooks(books3);

        List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
        return authorList;
    }

crear flujo

Colección de una sola columna:集合对象.stream()

List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();

Matriz: Arrays.stream(数组)O Stream.ofcrear usando

Integer[] arr = {
    
    1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);

Colección de dos columnas: conviértala en una colección de una sola columna y luego créela

Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

operaciones intermedias

filter

  • Los elementos de la secuencia se pueden filtrar condicionalmente y solo aquellos que cumplan las condiciones de filtrado pueden permanecer en la secuencia.

Por ejemplo:

Imprima los nombres de todos los escritores cuyos nombres tengan más de 1

public class StreamTest1 {
    public static void main(String[] args) {
        //打印所有姓名长度大于1的作家的姓名 
        //匿名内部类写法
        List<Author> authors = getAuthors();
        authors.stream().
            filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getName().length()>1;
             }
        	  }).forEach(new Consumer<Author>() {
            @Override
            public void accept(Author author) {
                System.out.println(author.getName());
             }
        });
        //打印所有姓名长度大于1的作家的姓名
        List<Author> authors = getAuthors();
        authors.stream()
                .filter(author -> author.getName().length()>1)
                .forEach(author -> System.out.println(author.getName()));
    }
}

map

  • Los elementos de la corriente de convección se pueden calcular o convertir (la conversión aquí es uno a uno).

Por ejemplo:

Imprima los nombres de todos los escritores.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //打印所有作家的姓名 
        //匿名内部类写法
        List<Author> authors = getAuthors();
        authors.stream()
                .map(new Function<Author, String>() {
    
    
                    @Override
                    public String apply(Author author) {
    
    
                        return author.getName();
                    }
                }).forEach(new Consumer<String>() {
    
    
                    @Override
                    public void accept(String s) {
    
    
                        System.out.println(s);
                    }
        });
        //打印所有作家的姓名
        List<Author> authors = getAuthors();
        authors.stream()
                .map(author -> author.getName())
                .forEach(name-> System.out.println(name));
    }
}

distinct

  • Puede eliminar elementos duplicados de la secuencia.

Por ejemplo:

Imprime los nombres de todos los escritores sin repetir elementos.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //打印所有作家的姓名,并且要求其中不能有重复元素。
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .forEach(author-> System.out.println(author.getName()));
    }
}
  • Nota: El método distinto se basa en el método igual de Objeto para determinar si son el mismo objeto. Por lo tanto, debe prestar atención a anular el método igual.

sorted

  • Los elementos de una secuencia se pueden ordenar.

Por ejemplo:

Ordene los elementos de la secuencia en orden descendente por edad y no debe haber elementos duplicados.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
        //匿名内部类写法
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted(new Comparator<Author>() {
    
    
                    @Override
                    public int compare(Author o1, Author o2) {
    
    
                        return o2.getAge()-o1.getAge();
                    }
                }).forEach(author -> System.out.println(author));
        authors.stream()
             .distinct()
             .sorted((o1, o2) -> o2.getAge()-o1.getAge())
             .forEach(author -> System.out.println(author));
    }
}

Nota: Si llama al método sorted() con parámetros vacíos, los elementos de la secuencia deben implementar Comparable.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted()
                .forEach(author -> System.out.println(author));
    }
}
public class Author implements Comparable<Author>{
    
    
     //id
     private Long id;
     //姓名
     private String name;
     //年龄
     private Integer age;
     //简介
     private String intro;
    //作品
     private List<Book> books;


    @Override
    public int compareTo(Author o) {
    
    
        return this.getAge()-o.getAge();
    }
}

limit

  • Se puede establecer la longitud máxima de la secuencia y se descartará el exceso de longitud.

Por ejemplo:

Ordene los elementos de la secuencia en orden descendente por edad, no debe haber elementos duplicados, y luego imprima los nombres de los dos escritores más antiguos entre ellos.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted((o1, o2) -> o2.getAge()-o1.getAge())
                .map(author -> author.getName())
                .limit(2)
                .forEach(name-> System.out.println(name));
    }
}

skip

  • Omita los primeros n elementos de la secuencia y devuelva los elementos restantes

Por ejemplo:

Imprime todos los escritores excepto los más antiguos, sin elementos duplicados y ordenados por edad en orden descendente.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted((o1, o2) -> o2.getAge()-o1.getAge())
                .skip(1)
                .forEach(author -> System.out.println(author));
    }
}

flatMap

  • Map solo puede convertir un objeto en otro objeto como un elemento en la secuencia. FlatMap puede convertir un objeto en varios objetos como elementos de la secuencia.

Ejemplo 1:

Imprima los nombres de todos los libros. Es necesario eliminar elementos duplicados.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        List<Author> authors = getAuthors();
        //打印所有书籍的名字。要求对重复的元素进行去重。
        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .forEach(book -> System.out.println(book.getName()));

    }
}

Ejemplo 2:

Imprima todas las categorías de datos existentes. Se requiere deduplicar la clasificación. No puede aparecer este formato: filosofía, amor

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
        List<Author> authors = getAuthors();
        authors.stream()
                .flatMap(author->author.getBooks().stream())
                .distinct()
                .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
                .distinct()
                .forEach(category-> System.out.println(category));
    }
 }

Terminar la operación

forEach

  • Para recorrer los elementos de la secuencia, utilizamos los parámetros entrantes para especificar las operaciones específicas que se realizarán en los elementos atravesados.

ejemplo:

Imprima los nombres de todos los escritores.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //输出所有作家的名字
        List<Author> authors = getAuthors();
        authors.stream()
                .forEach(author -> System.out.println(author.getName()));
    }
}

count

  • Se puede utilizar para obtener la cantidad de elementos en la secuencia actual.

ejemplo:

Imprima el número de libros publicados por estos autores, teniendo cuidado de eliminar elementos duplicados.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //打印这些作家的所出书籍的数目,注意删除重复元素。
        List<Author> authors = getAuthors();
        long count = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .count();
        System.out.println(count);
    }
}

max&min

  • Se puede utilizar para o el valor máximo en la secuencia.

ejemplo:

Obtenga las puntuaciones más altas y más bajas de los libros publicados por estos autores e imprímalos.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //分别获取这些作家的所出书籍的最高分和最低分并打印。
        List<Author> authors = getAuthors();
        Optional<Integer> max = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .max((o1, o2) -> o1 - o2);
        Optional<Integer> min = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getScore())
                .min(((o1, o2) -> o1 - o2));
        System.out.println(max.get());
        System.out.println(min.get());
    }
}

collect

  • Convierte la transmisión actual en una colección.

ejemplo:

Obtenga una colección de Lista que almacene los nombres de todos los autores.

//获取一个存放所有作者名字的List集合。
        List<Author> authors = getAuthors();
        List<String> nameList = authors.stream()
                .map(author -> author.getName())
                .distinct()
                .collect(Collectors.toList());
        System.out.println(nameList);

Obtenga un conjunto de todos los títulos de libros.

List<Author> authors = getAuthors();
//获取一个所有书名的Set集合。
Set<String> books = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getName())
                .collect(Collectors.toSet());
System.out.println(books);

Obtenga una colección de mapas, la clave del mapa es el nombre del autor,value为List<Book>

List<Author> authors = getAuthors();
//获取一个Map集合,map的key为作者名,value为List<Book>
Map<String, List<Book>> map = authors.stream()
                .distinct()
                .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
System.out.println(map);

查找与匹配

cualquier partido

  • Se puede utilizar para determinar si hay elementos que cumplan las condiciones coincidentes y el resultado es de tipo booleano.

ejemplo:

Determinar si hay escritores mayores de 29 años

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //判断是否有年龄在29以上的作家
        List<Author> authors = getAuthors();
        boolean b = authors.stream()
                .anyMatch(author -> author.getAge() > 29);
        System.out.println(b);
    }
}

todo coincide

  • Se puede utilizar para determinar si todos cumplen las condiciones coincidentes y el resultado es de tipo booleano. Si todos coinciden, el resultado es verdadero; de lo contrario, el resultado es falso.

ejemplo:

Determinar si todos los escritores son adultos.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //判断是否所有的作家都是成年人
        List<Author> authors = getAuthors();
        boolean b = authors.stream()
                .allMatch(author -> author.getAge() >= 18);
        System.out.println(b);
    }
}

ningunoCoincidencia

  • Se puede juzgar si ninguno de los elementos de la secuencia cumple las condiciones coincidentes. Si ninguno de ellos coincide, el resultado es verdadero; en caso contrario, el resultado es falso.

ejemplo:

Determinar si el autor tiene más de 100 años.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
       // 判断作家是否都没有超过100岁的。
        List<Author> authors = getAuthors();
        boolean b = authors.stream()
                .noneMatch(author -> author.getAge() > 100);
        System.out.println(b);
    }
}

encontrar cualquier

  • Consigue cualquier elemento de la secuencia. Este método no tiene forma de garantizar que se obtendrá el primer elemento de la secuencia.

ejemplo:

Consiga cualquier escritor que sea mayor de 18 años y escriba su nombre si existe.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //获取任意一个年龄大于18的作家,如果存在就输出他的名字
        List<Author> authors = getAuthors();
        Optional<Author> any = authors.stream()
                .filter(author -> author.getAge() > 18)
                .findAny();
        System.out.println(any.get());
    }
}

encontrar primero

  • Obtenga el primer elemento de la secuencia.

ejemplo:

Consiga el escritor más joven y escriba su nombre.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
       //获取一个年龄最小的作家,并输出他的姓名
        List<Author> authors = getAuthors();
        Optional<Author> first = authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .findFirst();
        System.out.println(first.get().getName());
    }
}

reducir la fusión

  • Los datos del flujo de convección calculan un resultado según el método de cálculo que especifique. (operación de reducción)

La función de reducir es combinar los elementos en la secuencia. Podemos pasar un valor inicial y calculará los elementos en la secuencia y el valor de inicialización en secuencia de acuerdo con nuestro método de cálculo, y el resultado del cálculo se calculará con los elementos posteriores.

El método de cálculo interno de la forma sobrecargada de reducción con dos parámetros es el siguiente:

T result = identity;
for (T element : this stream)
    result = accumulator.apply(result, element)
return result;
  • La identidad es el valor inicial que podemos pasar a través de los parámetros del método. El cálculo específico de la aplicación del acumulador también está determinado por los parámetros del método.

ejemplo:

Utilice reducir para encontrar la suma de las edades de todos los autores.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        //使用reduce求所有作者年龄的和
        List<Author> authors = getAuthors();
        Integer reduce = authors.stream()
                .distinct()
                .map(author -> author.getAge())
                .reduce(0, ((result, element) -> result + element));
        System.out.println(reduce);
    }
}

Utilice reducir para encontrar la edad máxima entre todos los autores.

List<Author> authors = getAuthors(); 
//使用reduce求所有作者中年龄的最大值
Integer reduce1 = authors.stream()
                .distinct()
                .map(author -> author.getAge())
                .reduce(Integer.MIN_VALUE, ((result, element) -> result > element ? result : element));
System.out.println(reduce1);

Utilice reducir para encontrar la edad mínima entre todos los autores.

List<Author> authors = getAuthors(); 
//使用reduce求所有作者中年龄的最小值
Integer reduce2 = authors.stream()
                .distinct()
                .map(author -> author.getAge())
                .reduce(Integer.MAX_VALUE, ((result, element) -> result > element ? element : result));
System.out.println(reduce2);

Cálculo dentro de la forma sobrecargada de reducción con un parámetro.

boolean foundAny = false;
     T result = null;
     for (T element : this stream) {
    
    
         if (!foundAny) {
    
    
             foundAny = true;
             result = element;
         }
         else
             result = accumulator.apply(result, element);
     }
     return foundAny ? Optional.of(result) : Optional.empty();

Si utiliza un método sobrecargado de parámetros para encontrar el valor mínimo, el código es el siguiente:

  //        使用reduce求所有作者中年龄的最小值
        List<Author> authors = getAuthors();
        Optional<Integer> minOptional = authors.stream()
                .map(author -> author.getAge())
                .reduce((result, element) -> result > element ? element : result);
        minOptional.ifPresent(age-> System.out.println(age));

Precauciones

  • Evaluación diferida (si no hay operación final, no se ejecutarán operaciones intermedias)
  • Las transmisiones son desechables (una vez que un objeto de transmisión se somete a una operación de finalización, la transmisión ya no se puede usar)
  • No afectará los datos originales (podemos procesar mucho varios datos en la secuencia. Pero en circunstancias normales, no afectará los elementos de la colección original. Esto es a menudo lo que esperamos).
    • Si modificamos los datos de la colección durante la operación de transmisión, aún hará que los datos de la colección cambien.

Opcional

Descripción general

Lo más común que ocurre cuando escribimos código es la excepción del puntero nulo. Entonces, en muchos casos necesitamos hacer varios juicios no vacíos.

Por ejemplo:

Author author = getAuthor();
if(author!=null){
   System.out.println(author.getName());
}

Especialmente cuando las propiedades del objeto siguen siendo un objeto. Habrá más de este juicio.

  • Y demasiadas declaraciones de juicio harán que nuestro código parezca inflado.

  • Por lo tanto, se introdujo Opcional en JDK8. Después de desarrollar el hábito de usar Opcional, puede escribir código más elegante para evitar excepciones de puntero nulo.

  • Y Opcional también se usa en muchas API relacionadas con la programación funcional. Si no sabe cómo usar Opcional, también tendrá un impacto en el aprendizaje de la programación funcional.

Crear objeto

Opcional es como una clase de empaquetado que puede encapsular nuestros datos específicos dentro del objeto Opcional. Luego podemos usar el método encapsulado en Opcional para operar los datos encapsulados y evitar excepciones de puntero nulo de manera muy elegante.

Generalmente usamos el método estático de Nullable de Opcional para encapsular datos en un objeto Opcional.无论传入的参数是否为null都不会出现问题。

public class OptionalTest {
    
    
    public static void main(String[] args) {
    
    
        Author author = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(author);
    }
}
  • Puede resultarle problemático agregar una línea de código para encapsular los datos. Pero si modificamos el método getAuthor para que su valor de retorno sea el Opcional encapsulado, nos resultará mucho más conveniente usarlo.
  • Y en el desarrollo real, muchos de nuestros datos se obtienen de la base de datos. Mybatis puede y ya soporta Opcional desde la versión 3.5. Podemos definir directamente el tipo de valor de retorno del método dao como tipo Opcional, y MyBastis encapsulará los datos en un objeto Opcional y los devolverá. El proceso de encapsulación no requiere que lo operemos nosotros mismos.

Si está seguro de que un objeto no está vacío , puede utilizar el método estático de Opcional para encapsular los datos en un objeto Opcional.

Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);

Pero asegúrese de tener en cuenta que los parámetros pasados ​​al usar of no deben ser nulos.

  • Si el tipo de valor de retorno de un método es Opcional. Y si encontramos que el valor de retorno obtenido por un determinado cálculo es nulo, necesitamos encapsular nulo en un objeto Opcional y devolverlo. En este momento, puede utilizar el método estático de Opcional vacío para encapsular.

Valor de consumo seguro

Después de obtener un objeto Opcional, definitivamente necesitamos usar los datos que contiene. En este momento podemos usar su par de métodos ifPresent para consumir el valor.

  • Este método determinará si los datos encapsulados en él están vacíos, si no están vacíos se ejecutará el código de consumo específico. Esto hace que su uso sea más seguro.

Por ejemplo, el siguiente método de escritura evita elegantemente excepciones de puntero nulo.

public class OptionalTest {
    
    
    public static void main(String[] args) {
    
    
        Author author = getAuthor();
        Optional<Author> authorOptional = Optional.ofNullable(author);
        authorOptional.ifPresent(author1 -> System.out.println(author1.getName()));
    }
}

Obtener valor

  • Si queremos obtener el valor y procesarlo nosotros mismos, podemos utilizar el método get, pero no es recomendable. Porque se producirá una excepción cuando los datos dentro de Opcional estén vacíos.
public class OptionalTest {
    
    
    public static void main(String[] args) {
    
    
        Optional<Author> author1 = Optional.ofNullable(null);
        Author author2 = author1.get();
    }
}
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at com.lsc.optional.OptionalTest.main(OptionalTest.java:23)

Obtenga valor de forma segura

Si queremos obtener el valor de forma segura. No recomendamos utilizar el método get, pero utilice los siguientes métodos proporcionados por Opcional.

orElseGet

  • Obtenga los datos y establezca el valor predeterminado cuando los datos estén vacíos. Si los datos no están vacíos, se pueden obtener los datos. Si está vacío, se crea un objeto en función de los parámetros que ingresa y se devuelve como valor predeterminado.
public class OptionalTest {
    
    
    public static void main(String[] args) {
    
    
        Optional<Author> author1 = Optional.ofNullable(null);
        Author author = author1.orElseGet(new Supplier<Author>() {
    
    
            @Override
            public Author get() {
    
    
                return new Author(10L, "lsc", 23, "帅比", null);
            }
        });
        System.out.println(author);
    }
}
   // Author(id=10, name=lsc, age=23, intro=帅比, books=null)

orElseThrow

  • Obtenga los datos. Si los datos no están vacíos, puede obtenerlos. Si está vacío, se generará una excepción según los parámetros que ingrese.
public class OptionalTest {
    
    
    public static void main(String[] args) {
    
    
        Optional<Author> author1 = Optional.ofNullable(null);
        Author author = author1.orElseThrow(new Supplier<RuntimeException>() {
    
    
            @Override
            public RuntimeException get() {
    
    
                return new RuntimeException("对象为null");
            }
        });
        System.out.println(author);
    }
}
Exception in thread "main" java.lang.RuntimeException: 对象为null
	at com.lsc.optional.OptionalTest.lambda$main$0(OptionalTest.java:24)
	at java.util.Optional.orElseThrow(Optional.java:290)
	at com.lsc.optional.OptionalTest.main(OptionalTest.java:24)

filtrar

  • Podemos utilizar el método de filtro para filtrar los datos. Si originalmente hay datos, pero no cumplen con el juicio, también se convertirá en un objeto opcional sin datos.
public class OptionalTest {
    
    
    public static void main(String[] args) {
    
    
        Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
        authorOptional.filter(author->author.getAge()>100).ifPresent(author -> System.out.println(author.getName()));
    }
}

conversión de datos

  • Opcional también proporciona un mapa que nos permite convertir datos, y Opcional aún empaqueta los datos convertidos, lo que garantiza nuestra seguridad.

Por ejemplo, queremos obtener una colección de libros de un autor.

public class OptionalTest {
    
    
    public static void main(String[] args) {
    
    
        Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
        Optional<List<Book>> optionalBooks = authorOptional.map(author -> author.getBooks());
        optionalBooks.ifPresent(books -> System.out.println(books) );
    }
}

interfaz funcional

Una interfaz con un solo método abstracto se denomina interfaz funcional.

Las interfaces funcionales de JDK están marcadas con la anotación @FunctionalInterface . Pero independientemente de si se agrega o no la anotación, siempre que solo haya un método abstracto en la interfaz, es una interfaz funcional.

常见的函数式接口

Ejecutable

@FunctionalInterface
public interface Runnable {
    
    
    public abstract void run();
}

Interfaz de conversión de cálculo de funciones

  • Según la lista de parámetros y el tipo de valor de retorno del método abstracto, podemos calcular o convertir los parámetros entrantes en el método y devolver el resultado.
@FunctionalInterface
public interface Function<T, R> {
    
    
    R apply(T t);
}

Interfaz de consumo del consumidor

  • Según la lista de parámetros y el tipo de valor de retorno del método abstracto, podemos consumir los parámetros entrantes en el método.
@FunctionalInterface
public interface Consumer<T> {
    
    
    void accept(T t);
}

Interfaz de producción del proveedor

  • De acuerdo con la lista de parámetros y el tipo de valor de retorno del método abstracto, podemos crear un objeto en el método y devolver el objeto creado.
@FunctionalInterface
public interface Supplier<T> {
    
    
/**
 * Gets a result.
   *
 * @return a result
   */
   T get();
}

Interfaz de juicio de predicados

  • De acuerdo con la lista de parámetros y el tipo de valor de retorno del método abstracto, podemos juzgar condicionalmente los parámetros pasados ​​en el método y devolver el resultado del juicio.
@FunctionalInterface
public interface Predicate<T> {
    
    
    boolean test(T t);
}

Biconsumidor (Bi significa dos, necesitamos pasar dos parámetros, en el caso anterior son v y e)

@FunctionalInterface
public interface BiConsumer<T, U> {
    
    
    void accept(T t, U u);
}
Nombre de la interfaz funcional nombre del método parámetro valor de retorno
Ejecutable correr sin parámetros Sin valor de retorno
Función aplicar 1 parámetro tiene un valor de retorno
Consumir aceptar 1 parámetro Sin valor de retorno
Proveedor conseguir sin parámetros tiene un valor de retorno
biconsumidor aceptar 2 parámetros Sin valor de retorno

Métodos predeterminados comúnmente utilizados

  • y

Cuando utilizamos la interfaz Predicate, es posible que necesitemos unir condiciones de juicio. El método and es equivalente a usar && para unir dos condiciones de juicio

Por ejemplo:

Escritores impresos mayores de 17 años y cuyo nombre tenga una longitud superior a 1.

public class StreamTest1 {
    public static void main(String[] args) {
        List<Author> authors = getAuthors();
        Stream<Author> stream = authors.stream();
        stream.filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getAge()>17;
            }
        }.and(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getName().length()>1;
            }
        })).forEach(author -> System.out.println(author));
    }
}

referencia del método

Cuando usamos lambda, si solo hay una llamada a un método en el cuerpo del método (incluido el método constructor), podemos usar referencias de métodos para simplificar aún más el código.

Uso recomendado

Cuando usamos lambda, no necesitamos considerar cuándo usar referencias de métodos, qué método usar y cuál es el formato de las referencias de métodos. Solo necesitamos usar las teclas de método abreviado para intentar convertirlo en una referencia de método después de escribir el método lambda y descubrir que el cuerpo del método tiene solo una línea de código y es una llamada al método .

A medida que utilizamos más referencias de métodos, gradualmente podemos escribir referencias de métodos directamente.

formato básico

Nombre de clase o nombre de objeto::nombre del método

Explicación detallada de la gramática (comprender)

引用类的静态方法

  • De hecho, es un método estático que hace referencia a la clase.

  • Formato

    • Nombre de clase de código::nombre del método
  • Requisitos previos para su uso

    • Si estamos anulando un método, solo hay una línea de código en el cuerpo del método , y esta línea de código llama a un método estático de una determinada clase , y pasamos todos los parámetros del método abstracto para que se anulen en este orden. En métodos estáticos , podemos referirnos a los métodos estáticos de la clase en este momento.
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
         .map(age->String.valueOf(age));

Tenga en cuenta que si el método que anulamos no tiene parámetros, el método llamado tampoco tiene parámetros, lo que equivale a cumplir con las reglas anteriores.

Después de la optimización, queda de la siguiente manera:

List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
         .map(String::valueOf);

引用对象的实例方法

  • Formato

    • Nombre del objeto::Nombre del método
  • Requisitos previos para su uso

    • Si estamos anulando un método, solo hay una línea de código en el cuerpo del método , y esta línea de código llama a un método miembro de un objeto , y pasamos todos los parámetros del método abstracto para que se anulen en este orden. el método miembro , en este momento podemos referirnos al método de instancia del objeto
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
        .forEach(name->sb.append(name));

Optimizado:

List<Author> authors = getAuthors();
        Stream<Author> stream = authors.stream();
        StringBuilder stringBuilder = new StringBuilder();
        stream.map(author -> author.getName())
                .forEach(stringBuilder::append);
        System.out.println(stringBuilder);

引用类的实例方法

  • Formato

    • Nombre de clase de código::nombre del método
  • Requisitos previos para su uso

    • Si anulamos un método, solo hay una línea de código en el cuerpo del método , y esta línea de código llama al método miembro del primer parámetro , y pasamos todos los parámetros restantes en el método abstracto para anularlos en orden. Al ingresar a este método miembro , podemos hacer referencia al método de instancia de la clase en este momento.
interface UseString{
    
    
   String use(String str,int start,int length);
}

public static String subAuthorName(String str, UseString useString){
    
    
     int start = 0;
     int length = 1;
     return useString.use(str,start,length);
 }
public static void main(String[] args) {
    
    
	subAuthorName("三更草堂", new UseString() {
    
    
         @Override
         public String use(String str, int start, int length) {
    
    
            return str.substring(start,length);
         }
    });

}

Después de la optimización, queda de la siguiente manera:

public static void main(String[] args) {
    
    
   subAuthorName("三更草堂", String::substring);
 }

构造器引用

  • Si una línea de código en el cuerpo del método es un constructor, puede utilizar una referencia de constructor.

  • Formato

    • Nombre de clase::nuevo
  • Requisitos previos para su uso

    • Si estamos anulando un método, solo hay una línea de código en el cuerpo del método , y esta línea de código llama al constructor de una determinada clase , y pasamos todos los parámetros del método abstracto para que se anulen en orden. este método de construcción , podemos hacer referencia al constructor en este momento.

Por ejemplo:

List<Author> authors = getAuthors();
        authors.stream()
                .map(author -> author.getName())
                .map(name->new StringBuilder(name))
                .map(sb->sb.append("-三更").toString())
                .forEach(str-> System.out.println(str));

Optimizado:

List<Author> authors = getAuthors();
        authors.stream()
                .map(author -> author.getName())
                .map(StringBuilder::new)
                .map(sb->sb.append("-三更").toString())
                .forEach(str-> System.out.println(str));

Uso avanzado

Optimización del tipo de datos básicos

Muchos de los métodos Stream que usamos antes usan genéricos. Por lo tanto, los parámetros y valores de retorno involucrados son todos tipos de datos de referencia.

Aunque estamos operando con números enteros y decimales, en realidad usamos sus clases de empaquetado. El empaquetado automático y el desempaquetado automático introducidos en JDK5 nos hacen tan conveniente usar la clase de empaquetado correspondiente como usar tipos de datos básicos. Pero debes saber que empacar y desempaquetar definitivamente llevará tiempo. Aunque esta vez el consumo es muy bajo. Pero cuando una gran cantidad de datos se encajona y se desempaqueta repetidamente, no se puede ignorar esta pérdida de tiempo.

Entonces, para que podamos optimizar esta parte del consumo de tiempo. Stream también proporciona muchos métodos específicos para tipos de datos básicos.

Ejemplo: mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        List<Author> authors = getAuthors();
        authors.stream()
                .map(author -> author.getAge())
                .map(age -> age+10)
                .filter(age-> age>18)
                .map(age-> age+2)
                .forEach(System.out::println);
        authors.stream()
                .mapToInt(author -> author.getAge())
                .map(age -> age+10)
                .filter(age-> age>18)
                .map(age-> age+2)
                .forEach(System.out::println);
    }
}

Corriente paralela

Cuando hay una gran cantidad de elementos en la secuencia, podemos utilizar secuencias paralelas para mejorar la eficiencia de las operaciones. De hecho, el flujo paralelo consiste en asignar tareas a varios subprocesos para que las completen. Si lo implementamos nosotros mismos en código, en realidad será muy complicado y requerirá que usted tenga suficiente comprensión y conocimiento de la programación concurrente. Y si usamos Stream, solo necesitamos modificar la llamada de un método para usar flujos paralelos que nos ayuden a implementarlo, mejorando así la eficiencia.

El método paralelo puede convertir un flujo en serie en un flujo paralelo.

public class StreamTest1 {
    
    
    public static void main(String[] args) {
    
    
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer sum = stream.parallel()
                .peek(new Consumer<Integer>() {
    
    
                    @Override
                    public void accept(Integer num) {
    
    
                        System.out.println(num + Thread.currentThread().getName());
                    }
                })
                .filter(num -> num > 5)
                .reduce(((result, element) -> result + element))
                .get();
        System.out.println(sum);
    }
3ForkJoinPool.commonPool-worker-9
5ForkJoinPool.commonPool-worker-11
6ForkJoinPool.commonPool-worker-9
4ForkJoinPool.commonPool-worker-6
1ForkJoinPool.commonPool-worker-4
2ForkJoinPool.commonPool-worker-2
7main
8ForkJoinPool.commonPool-worker-9
10ForkJoinPool.commonPool-worker-11
9ForkJoinPool.commonPool-worker-13
40

Los objetos de flujo paralelo también se pueden obtener directamente a través de paraleloStream.

   List<Author> authors = getAuthors();
        authors.parallelStream()
                .map(author -> author.getAge())
                .map(age -> age + 10)
                .filter(age->age>18)
                .map(age->age+2)
                .forEach(System.out::println);

Supongo que te gusta

Origin blog.csdn.net/qq_50985215/article/details/131096334
Recomendado
Clasificación