精华内容
下载资源
问答
  • java8 lambda 表达式累加

    千次阅读 2020-09-10 13:53:12
    public static void main(String[] args) { List<CardProductOrderRelation> list=new ArrayList<>(); for (int i=0;i<10;... CardProductOrderRelation r=new CardProductOrderRelation();...
    public static void main(String[] args) {
            List<CardProductOrderRelation> list=new ArrayList<>();
            for (int i=0;i<10;i++){
                CardProductOrderRelation r=new CardProductOrderRelation();
                r.setSuccessAmount(BigDecimal.valueOf(i+2));
                r.setActualAmount(BigDecimal.valueOf(i+1));
                r.setActualBalance(BigDecimal.valueOf(i));
                list.add(r);
            }
            BigDecimal successAmount = list.stream().map(CardProductOrderRelation::getSuccessAmount).reduce(BigDecimal::add).get();
            BigDecimal actualAmount = list.stream().map(CardProductOrderRelation::getActualAmount).reduce(BigDecimal::add).get();
            BigDecimal actualBalance = list.stream().map(CardProductOrderRelation::getActualBalance).reduce(BigDecimal::add).get();
            System.out.println(String.format("%s %s %s",successAmount,actualAmount,actualBalance));
        }
    
    展开全文
  • 之前写了一篇博客简单介绍了一下java 8发布新增的一些特性功能,java 8在2014年发布,距今也不少年了,但是lambda表达式使用并不熟悉,现在一边学习,一边记录一下。 一、Lambda表达式 是java8新增的最重要的新...

          之前写了一篇博客简单介绍了一下java 8发布新增的一些特性功能,java 8在2014年发布,距今也不少年了,但是lambda表达式使用并不熟练,现在一边学习,一边记录一下。

    目录

     一、Lambda表达式

    二、方法引用

    三、stream API使用


     一、Lambda表达式

    是java8新增的最重要的新功能之一。使用Lambda表达式是 Java8 中最重要的新功能之一。使用 Lambda 表达式可以替代只有一个抽象方法的接口(函数式接口)实现,告别匿名内部类,代码看起来更简洁易懂。Lambda表达式同时还提升了对集合、框架的迭代、遍历、过滤数据的操作。

    Lambda表达式特点

    1:函数式编程

    2:参数类型自动推断

    3:代码量少,简洁

    学习Lambda表达式需要熟悉java泛型,平时有意识地使用lambda表达式,学习java中固有的一些函数式接口,多用stream API。

    函数式接口(函数式接口都可以使用Lambda表达式):只有一个抽象方法的接口

    注意:如果自定义的接口使用Object类中的方法作为抽象方法,则该接口不是函数式接口。例如在接口中定义一个抽象方法:public int hashCode();

    在java8中可以用@FuctionalInterface注解来验证函数式接口。

    常用的函数式接口:

    Runnable接口:

    @FunctionalInterface
    public interface Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    }
    

    Runnable接口是函数式接口因此可以使用lambda表达式。

    Callable接口:

    @FunctionalInterface
    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

    Comparator接口:

    还有jdk中其他常用的函数式接口:

    在java.util.function包下的类几乎都是函数式接口:

    其中里面常用的接口有

    Supplier接口:可以表示为一个输出(获取一个结果),返回T类型

    @FunctionalInterface
    public interface Supplier<T> {
    
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get();
    }
    

    Consumer:可以表示为一个输入(操作一个输入的参数)

    @FunctionalInterface
    public interface Consumer<T> {
    
        /**
         * Performs this operation on the given argument.
         *
         * @param t the input argument
         */
        void accept(T t);
    
        /**
         * Returns a composed {@code Consumer} that performs, in sequence, this
         * operation followed by the {@code after} operation. If performing either
         * operation throws an exception, it is relayed to the caller of the
         * composed operation.  If performing this operation throws an exception,
         * the {@code after} operation will not be performed.
         *
         * @param after the operation to perform after this operation
         * @return a composed {@code Consumer} that performs in sequence this
         * operation followed by the {@code after} operation
         * @throws NullPointerException if {@code after} is null
         */
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }

    BiConsumer:可以表示为两个输入(有两个输入参数T和U)

    @FunctionalInterface
    public interface BiConsumer<T, U> {
    
        /**
         * Performs this operation on the given arguments.
         *
         * @param t the first input argument
         * @param u the second input argument
         */
        void accept(T t, U u);
    
        /**
         * Returns a composed {@code BiConsumer} that performs, in sequence, this
         * operation followed by the {@code after} operation. If performing either
         * operation throws an exception, it is relayed to the caller of the
         * composed operation.  If performing this operation throws an exception,
         * the {@code after} operation will not be performed.
         *
         * @param after the operation to perform after this operation
         * @return a composed {@code BiConsumer} that performs in sequence this
         * operation followed by the {@code after} operation
         * @throws NullPointerException if {@code after} is null
         */
        default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
            Objects.requireNonNull(after);
    
            return (l, r) -> {
                accept(l, r);
                after.accept(l, r);
            };
        }
    }

    Function接口:可以表示为通过一个输入参数,获取一个输出(即一个输入,一个输出),传入一个T类型,获取一个R类型数据

    @FunctionalInterface
    public interface Function<T, R> {
    
        /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    
        /**
         * Returns a composed function that first applies the {@code before}
         * function to its input, and then applies this function to the result.
         * If evaluation of either function throws an exception, it is relayed to
         * the caller of the composed function.
         *
         * @param <V> the type of input to the {@code before} function, and to the
         *           composed function
         * @param before the function to apply before this function is applied
         * @return a composed function that first applies the {@code before}
         * function and then applies this function
         * @throws NullPointerException if before is null
         *
         * @see #andThen(Function)
         */
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
    
        /**
         * Returns a composed function that first applies this function to
         * its input, and then applies the {@code after} function to the result.
         * If evaluation of either function throws an exception, it is relayed to
         * the caller of the composed function.
         *
         * @param <V> the type of output of the {@code after} function, and of the
         *           composed function
         * @param after the function to apply after this function is applied
         * @return a composed function that first applies this function and then
         * applies the {@code after} function
         * @throws NullPointerException if after is null
         *
         * @see #compose(Function)
         */
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
    
        /**
         * Returns a function that always returns its input argument.
         *
         * @param <T> the type of the input and output objects to the function
         * @return a function that always returns its input argument
         */
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }

    UnaryOperator接口:该接口继承了Funcition接口,可以表示为输入一个参数,获取一个输出数据(不同的是,输入和输出的类型相同了,都是T类型) 

    @FunctionalInterface
    public interface UnaryOperator<T> extends Function<T, T> {
    
        /**
         * Returns a unary operator that always returns its input argument.
         *
         * @param <T> the type of the input and output of the operator
         * @return a unary operator that always returns its input argument
         */
        static <T> UnaryOperator<T> identity() {
            return t -> t;
        }
    }

    BiFunction接口:表示两个输入(输入类型不同),一个输出(输入和输出类型都不同)

    @FunctionalInterface
    public interface BiFunction<T, U, R> {
    
        /**
         * Applies this function to the given arguments.
         *
         * @param t the first function argument
         * @param u the second function argument
         * @return the function result
         */
        R apply(T t, U u);
    
        /**
         * Returns a composed function that first applies this function to
         * its input, and then applies the {@code after} function to the result.
         * If evaluation of either function throws an exception, it is relayed to
         * the caller of the composed function.
         *
         * @param <V> the type of output of the {@code after} function, and of the
         *           composed function
         * @param after the function to apply after this function is applied
         * @return a composed function that first applies this function and then
         * applies the {@code after} function
         * @throws NullPointerException if after is null
         */
        default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t, U u) -> after.apply(apply(t, u));
        }
    }
    

    BinaryOperator接口:传入两个参数类型(输入),获取返回一个结果(输出),输入和输出类型相同

    @FunctionalInterface
    public interface BinaryOperator<T> extends BiFunction<T,T,T> {
        /**
         * Returns a {@link BinaryOperator} which returns the lesser of two elements
         * according to the specified {@code Comparator}.
         *
         * @param <T> the type of the input arguments of the comparator
         * @param comparator a {@code Comparator} for comparing the two values
         * @return a {@code BinaryOperator} which returns the lesser of its operands,
         *         according to the supplied {@code Comparator}
         * @throws NullPointerException if the argument is null
         */
        public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
            Objects.requireNonNull(comparator);
            return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
        }
    
        /**
         * Returns a {@link BinaryOperator} which returns the greater of two elements
         * according to the specified {@code Comparator}.
         *
         * @param <T> the type of the input arguments of the comparator
         * @param comparator a {@code Comparator} for comparing the two values
         * @return a {@code BinaryOperator} which returns the greater of its operands,
         *         according to the supplied {@code Comparator}
         * @throws NullPointerException if the argument is null
         */
        public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
            Objects.requireNonNull(comparator);
            return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
        }
    }

     其他的在这个包下还有一些接口名称带有类型的,可以顾名思义它的作用,如DoubleConsumer接口,则表示输入的参数类型为double类型,其他的类似,可以参考源码理解

    @FunctionalInterface
    public interface DoubleConsumer {
    
        /**
         * Performs this operation on the given argument.
         *
         * @param value the input argument
         */
        void accept(double value);
    
        /**
         * Returns a composed {@code DoubleConsumer} that performs, in sequence, this
         * operation followed by the {@code after} operation. If performing either
         * operation throws an exception, it is relayed to the caller of the
         * composed operation.  If performing this operation throws an exception,
         * the {@code after} operation will not be performed.
         *
         * @param after the operation to perform after this operation
         * @return a composed {@code DoubleConsumer} that performs in sequence this
         * operation followed by the {@code after} operation
         * @throws NullPointerException if {@code after} is null
         */
        default DoubleConsumer andThen(DoubleConsumer after) {
            Objects.requireNonNull(after);
            return (double t) -> { accept(t); after.accept(t); };
        }
    }

    labmda表达式语法,只有函数式接口可以使用lambda表达式(正因为函数式接口中只有一个抽象方法,所以lambda表达式不用写方法名,可以推断出来),lambda表达式是对象,是一个函数式接口的实例。

    lambda表达式语法:LambdaParameter -> LambdaBody

    例如 Runnable r=() -> System.out.println();

    args -> expr

    对应

    (object... args)-> {函数式接口抽象方法的实现逻辑}

    左边括号里边的参数个数,根据函数式接口里面抽象方法的参数个数决定。

    当只有一个参数的时候,()可以省略,当方法体逻辑很简单的时候,{}和return 也可以省略

    Lambda表达式示例:

    () -> {}                             // 无参,无返回值

    () -> { System.out.println(1); }   // 无参,无返回值

    () -> System.out.println(1) // 无参,无返回值(上面的简写)

    () -> { return 100; }      // 无参,有返回值

    () -> 100                        // 无参,有返回值(上面的简写)

    () -> null                       // 无参,有返回值(返回null

    (int x) -> { return x+1; }  // 单个参数(可以写出参数类型,不写类型,会自动推断,因此可以写成下边的形式)有返回值

    (int x) -> x+1                // 单个参数,有返回值(上面的简写,省略了return)

    (x) -> x+1                  // 单个参数,有返回值(不指定参数类型,因为可以根据接口中的抽象方法参数类型推断出来,多个参数必须用括号)

    x -> x+1                     // 单个参数,有返回值(不指定参数类型)

    注意事项:

    多个参数类型时,要加上(),并且参数类型不能只省略一部分,

    如:(x,int y)-> x+y

    参数类型不能使用final修饰符,

    如:(x, final y)-> x+y

    不能将lambda表达式赋给一个非函数式接口

    如:Object obj=()-> "hello"

    可以将lambda表达式强转为一个有返回值的函数式接口

    Object obj = (Supplier<?>)() -> "hello"

    不需要也不允许使用throws语句来声明它可能会抛出的异常

    实例演示:

    package com.xiaomifeng1010.rbacboot.common.util;
    import org.apache.commons.lang3.StringUtils;
    
    import java.io.Closeable;
    import	java.util.Observer;
    
    import java.util.concurrent.Callable;
    import java.util.function.*;
    
    /**
     * @author xiaomifeng1010
     * @version 1.0
     * @date: 2020/3/19 18:52
     */
    
    public class LambdaExpression {
        public static void main(String[] args) throws Exception {
            //    无参无返回值接口Runnable,如果不使用lambda表达式,则是下边这样的
            Runnable runnable2=new Runnable() {
                @Override
                public void run() {
    
                }
            };
            //    无参无返回值接口Runnable,使用lambda表达式,效果等同上边的
            Runnable runnable=() -> {};
    //    如果方法体中只有一行逻辑实现的语句,大括号可以省略
            Runnable r=() -> System.out.println("无参无返回值的接口Runnable");
    
            Closeable closeable = () -> System.out.println("closeable也是无参无返回值");
            closeable.close();
    
    //  无参有返回值接口Callable,如果不使用lambda表达式
            Callable<String> callable=new Callable<String> () {
                @Override
                public String call() {
                    return "call方法";
                }
            };
    //无参有返回值接口Callable,使用lambda表达式,效果等同上边的
            Callable<String> callable2 = () -> {
                return "call方法";
            };
    //    因为方法体中只有一句返回语句,可以省略大括号和return
            Callable callable3 = () -> "call方法";
    
            System.out.println(callable.call());
            System.out.println(callable2.call());
            System.out.println(callable3.call());
    
    //        有参无返回值自定义接口UserMapper,不使用lambda表达式时
            UserMapper userMapper=new UserMapper() {
                @Override
                public void insert(User user) {
                    System.out.println("插入一条数据");
                }
            };
    
    //    有参无返回值,使用lambda表达式时
            UserMapper userMapper2=(User user) -> System.out.println("插入一条数据");
    //只有一个参数时,参数括号也可以省略,类型也可以省略(会自动推断)
            UserMapper userMapper3= user -> System.out.println("插入一条数据");
    
    //        有参有返回值的自定义接口OrderMapper,不使用lambda表达式
            OrderMapper orderMapper=new OrderMapper() {
                @Override
                public int insert(Order order) {
                    return 0;
                }
            };
    
    //    有参有返回值的自定义接口OrderMapper,使用lambda表达式(只有一个参数可省略括号和类型,方法体简单,只有一行可省略
    // 大括号和return
            OrderMapper orderMapper2=order -> 0;
            System.out.println("orderMapper插入了"+orderMapper.insert(new Order())+"行数据");
            System.out.println("orderMapper2插入了"+orderMapper2.insert(new Order())+"行数据");
    
    //        Function接口的使用示例,输入a是Integer类型,返回类型是String类型
            Function<Integer,String> function=a -> "function接口返回值";
    
    //     Function接口的使用示例,输入a是Integer类型,返回类型是String类型,由方法获取String.valueOf(b)返回String类型
            Function<Integer,String> function2 = b -> String.valueOf(b);
            System.out.println("function接口传入10,返回:"+function.apply(10));
            System.out.println("function2接口传入10,返回:"+function.apply(10));
    
    //        BiFunction输入两个不同类型参数(当然也可以相同类型参数),输出一个类型数据
            BiFunction<Integer,Double,String> biFunction=(a,b) -> String.valueOf(a)+String.valueOf(b);
            System.out.println("biFunction传入a,b值后:"+biFunction.apply(10,20.56));
    
    //        BinaryOperator输入的两个相同类型参数,输出相同类型的数据类型
            BinaryOperator<Integer> binaryOperator=(a,b) -> 10;
            System.out.println("binaryOperator输入a和b之后:"+binaryOperator.apply(5,6));
    
    //       Supplier接口获取一个输出
            Supplier<String> supplier = () -> "supplier";
            System.out.println("supplier不需要传入参数,就可以获取一个数据:"+supplier.get());
    
    //       Consumer输入一个类型参数,无返回值
            Consumer<String> consumer = (String s) -> {
                System.out.println("consumer");
            };
    //        简写为:
            Consumer<String> consumer2 = s-> System.out.println("consumer");
    
    //        注意函数式接口的方法中有返回值的,使用lambda表达式方法体中必须要有返回值,且返回值类型要相同,而函数式接口抽象方法
    //        没有返回值的,使用lambda表达式的方法体可以使用有返回值的方法。例如Runnable接口的抽象方法run()没有返回值;
    //        Integer.parseInt()会返回int类型
            Runnable runnable4 =() -> Integer.parseInt("10");
    //        但是直接写10,是不行的
    //        Runnable runnable1 = () -> 10;
    //        说明:为什么可以直接用方法,而不能直接使用int值,因为虽然Integer.parseInt("10")会返回一个int值,但是run
    //        方法是无返回值的,所以调用Integer.parseInt("10")可以只是用他的方法逻辑,不用返回值,但是直接写10,就是直接给了
    //        一个返回值,是不可以的
    
        }
    
    }
    
    /**
     * 自定义一个接口
     */
    @FunctionalInterface
     interface UserMapper{
         void insert(User user);
    
    }
    @FunctionalInterface
    interface OrderMapper{
         int insert(Order order);
    }
    
    class User{
    
    }
    
    class Order {
    
    }
    

    写lambda表达式:

    1.看抽象方法参数;2,看返回类型

    二、方法引用

    方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法,方法引用提供了一种引用而不执行方法的方式,如果抽象方法的实现恰好可以使用调用另外一个方法来实现,就有可能可以使用方法引用(因为如果方法体还需要写其他逻辑,就不能直接使用方法引用)。

    当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)方法引用:使用操作符“::” 将方法名和对象或类的名字分隔开来。
    如下四种主要使用情况:

    类型

    语法

    对应的lambda表达式

    静态方法引用

    类名::staticMethod

    (args) -> 类名.staticMethod(args)

    实例方法引用

    inst::instMethod

    (args) -> inst.instMethod(args)

    对象方法引用

    类名::instMethod

    (inst,args)  ->类名.instMethod(args)

    构造方法引用

    类名::new

    (args) -> new 类名(args)

    如果函数式接口的实现恰好可以通过调用一个静态方法来实现,那么就可以使用静态方法引用;

    如果函数式接口的实现恰好可以通过调用一个实例的实例方法来实现,那么就可以使用实例方法引用;

    抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。如果函数式接口的实现能由上面说的实例方法调用来实现的话,那么就可以使用对象方法引用;

    如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,那么就可以使用构造方法引用。
    实例演示:

    package com.xiaomifeng1010.rbacboot.common.util;
    
    import java.util.function.*;
    
    /**
     * @author xiaomifeng1010
     * @version 1.0
     * @date: 2020/3/19 21:32
     */
    
    public class MethodReference {
    
        public static void main(String[] args) {
    //        静态方法引用实例
            BinaryOperator<Double> binaryOperator=(a,b) -> Math.pow(a,b);
    //        Math.pow(a,b)方法需要两个输入参数,返回一个数据,且输入参数类型和返回值类型相同,完全符合BinaryOperator
    //        接口特点
            BinaryOperator<Double> binaryOperator2=Math::pow;
    
    //        构造方法引用
    //      Supplier接口的get()方法不需要输入参数,返回一个值(输出),可以用
    // String类(注意这里的String类不是lang包下的类,而是com.sun.org.apache.xpath.internal.operations.String)的构造方法实现
            Supplier<String> supplier=() -> new String();
            Supplier<String> supplier2=String::new;
    //        类型也可以使用数组类型
            Function<Integer,Integer[]> function6=integer -> new Integer[integer];
            Function<Integer,Integer[]> function7=Integer[]::new;
    
    //        实例方法引用
            MethodReference mr = new MethodReference();
            Function<String,String> function3=str -> mr.toUpper(str);
    
    //        或者直接简写为(下边这种写法不用写MethodReference mr = new MethodReference()
            Function<String,String> function5=new MethodReference()::toUpper;
    //      例子2,较特殊
            Function<String,String> function=str -> str.toUpperCase();
    //        方法引用写法,因为toUpperCase()方法不是静态方法,但是直接使用String引用toUpperCase()方法
            Function<String,String> function2=String::toUpperCase;
    
    //        对象方法引用实例
    //        抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。
    //        如果函数式接口的实现能由上面说的实例方法调用来实现的话,那么就可以使用对象方法引用
    //        lambda表达式方法体有现成方法实现时,lambda表达式完全写法(方法体中用类名.实例方法)
            Consumer<Too> consumer=(Too too) -> new Too().too();
            Consumer<Too> consumer2=Too::too;
    //        接口中有两个参数的情况,BiConsumer接口接收两个输入参数,无返回值
            BiConsumer<Too,String> biConsumer=(Too too2,String str) -> new Too().top(str);
            BiConsumer<Too,String> biConsumer2=Too::top;
    
    //      接口中三个参数类型
            BiFunction<Too,String,String> biFunction=(Too too2,String str) -> new Too().tos(str);
            BiFunction<Too,String,String> biFunction2=Too::tos;
    
    //        说明:如果接口中的抽象方法中没有输入参数,不能使用对象方法引用
            
        }
    
        public String toUpper(String str){
            return str.toUpperCase();
        }
    
    }
    
    class Too{
        public void too(){
    
        }
    
        public void top(String str){
    
        }
    
        public String tos(String str){
            return str;
        }
    }
    

    三、stream API使用

    Stream是一组用来处理数组、集合的API

    Stream特性

    1:不是数据结构,没有内部存储

    2:不支持索引访问

    3:延迟计算

    4:支持并行

    5:很容易生成数组或集合(ListSet

    6:支持过滤,查找,转换,汇总,聚合等操作

    Stream运行机制

    Stream分为 源source中间操作,终止操作

    流的源可以是一个数组、一个集合、一个生成器方法,一个I/O通道等等。

    一个流可以有零个和或者多个中间操作,每一个中间操作都会返回一个新的流,供下一个操作使用。一个流只会有一个终止操作。

    Stream只有遇到终止操作,它的源才开始执行遍历操作。

    Stream常用API

    中间操作:

    过滤 filter

    去重 distinct

    排序 sorted

    截取 limitskip

    转换 map/flatMap

    其他 peek

    终止操作:

    循环 forEach

    计算 minmaxcount average

    匹配 anyMatchallMatchnoneMatchfindFirstfindAny

    汇聚 reduce

    收集器 toArray collect

    Stream的创建

    1、通过数组

    2、通过集合来

    3、通过Stream.generate方法来创建

    4、通过Stream.iterate方法来创建

    5、其他API创建

    实例演示:

    package com.xiaomifeng1010.rbacboot.common.util;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    import java.util.Set;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    import java.util.stream.Stream;
    
    /**
     * @author xiaomifeng1010
     * @version 1.0
     * @date: 2020/3/20 0:03
     */
    
    public class StreamAPI {
        /**
         * 通过数组创建Stream
         */
        static void generate1(){
            String letter="a,b,c,d,e,f,g";
            String[] array=letter.split(",");
            Stream<String> stream=Stream.of(array);
            stream.forEach(x -> System.out.println("generate1方法运行后:"+x));
        }
    
        /**
         * 通过集合创建Steam
         */
        static void generate2() {
            List<String> list= Arrays.asList("a","b","c","d");
            Stream<String> stream=list.stream();
            stream.forEach(x -> System.out.println("依次输出:"+x));
        }
    
        /**
         * 通过Steam的静态方法generate()方法
         */
        static void generate3() {
    //    generate()方法中参数类型是函数式接口Supplier,产生一个无止境的steam流
          Stream<Integer> stream= Stream.generate(()->1);
    //      会死循环(会一直调用supplier接口的get方法)
    //      stream.forEach(x -> System.out.println("generate3方法运行后:"+x));
    //       因此为了避免死循环,可以在终止操作前使用中间操作limit截断流
            stream.limit(10).forEach(x -> System.out.println("generate3方法运行后只取10个元素:"+x));
    
        }
    
        /**
         * 通过Steam的静态方法iterate()方法
         */
        static void generate4(){
    //    iterate()方法,第一个参数是初始元素,第二个参数类型是函数式接口UnaryOperator(输入和输出类型相同)
    //      UnaryOperator输入的x后,一直累加1,无限制累加1循环,生成无止境steam流,初始值为1,然后一直往后加1
          Stream<Integer> stream= Stream.iterate(1,x -> x+1);
    //      会死循环(会一直调用iterate不停迭代)
    //      stream.forEach(x -> System.out.println("generate4方法运行后:"+x));
    //     因此为了避免死循环,可以在终止操作前使用中间操作limit截断流,取前十个数
            stream.limit(10).forEach(x -> System.out.println("generate4方法运行后取前十位数:"+x));
        }
    
        /**
         * 通过String类的chars()方法创建
         */
        static void generate5(){
            String letters="abcd";
          IntStream stream=letters.chars();
    //      流的中间操作步骤可以有多步,或O部,为0则没有中间操作,可以直接使用终止操作,如直接操作stream的终止操作forEach:
    //        stream.forEach(x -> System.out.println(x));
    //        可以使用方法引用,会逐个输出a,b,c,d的ASCII码值(int类型)
            stream.forEach(System.out::println);
        }
    
        public static void main(String[] args) {
    //      第一种和第二种创建流的方式使用较多
    //        generate1();
    //        generate2();
    //        generate3();
    //        generate4();
    //        generate5();
    
    //        演示stream中间操作,但是在终止操作前会延迟计算,并不会真正计算,需要终止操作才能进行计算
    //        Arrays.asList(1,2,3,4,5).stream().filter(x -> x%2==0);
    //        加上终止操作(过滤掉其他的数字,只保留偶数输出),filter()方法中参数是函数式接口Predicate<T>,
    //        boolean test(T t);
            Arrays.asList(1,2,3,4,5).stream().filter(x -> x%2==0).forEach(System.out::println);
    //      过滤出来满足偶数的2,4,6的和,有返回值,可以终止
            int sum= Arrays.asList(1,2,3,4,5,6).stream().filter(x -> x%2==0).mapToInt(x -> x).sum();
            System.out.println("和为:"+sum);
    
    //        终止操作求最大值6
            int max=Arrays.asList(1,2,3,4,5,6).stream().max((a,b) -> a-b).get();
            System.out.println("最大值:"+max);
    
    //        查找满足过滤条件(偶数)中任一个
           int anyone= Arrays.asList(1,2,3,4,5,6).stream().filter(x -> x%2==0).findAny().get();
            System.out.println("查找满足过滤条件的任一个数据:"+anyone);
    
    //        查找满足过滤条件(偶数)的第一个(按排序从大到小 comparator中方法体用第二个参数减第一个参数)
          int findFirst= Arrays.asList(1,2,3,4,5,6).stream().filter(x -> x%2==0).sorted((a,b) -> b-a).findFirst().get();
            System.out.println("查找满足过滤条件的第一个数据:"+findFirst);
    
    //        根据字符串长短排序(长度从小到大排序,并迭代输出)
            Arrays.asList("com","xiaomifeng1010","cn","admin").stream().sorted((a,b) -> a.length()-b.length()).forEach(System.out::println);
    
    //        从1-50里面的所有偶数查找出来,存放到一个list中
            List<Integer> list=Stream.iterate(1,x -> x+1).limit(50).filter(x -> x%2==0).collect(Collectors.toList());
            System.out.println(list);
    
    //        去重操作
            Arrays.asList(1,3,4,5,6,3,5,7).stream().distinct().forEach(System.out::println);
    
    //        去重操作,将stream流转换成set集合类型(达到去重目的)
           Set<Integer> set= Arrays.asList(1,3,4,5,6,3,5,7).stream().collect(Collectors.toSet());
            System.out.println("set集合元素:"+set);
    
    //        产生1-50数据,分页效果(从大到小排序后,跳过10个数据,放在list中)
          List<Integer> list2= Stream.iterate(1,x -> x+1 ).limit(50).sorted((a,b) -> b-a).skip(10).collect(Collectors.toList());
            System.out.println("从50跳过10个数据后:"+list2);
    
    //        转换操作
            String str="11,22,33,44,55";
    //       int sum2=Stream.of(str.split(",")).mapToInt(x -> Integer.parseInt(x)).sum();
    //        写成方法引用
            int sum2=Stream.of(str.split(",")).mapToInt(Integer::parseInt).sum();
            System.out.println("字符串转换为int后求和:"+sum2);
    
    //       字符串对象,转换为Product对象
            String productName="iphone,ipad,macbook,flashlight";
    //        Stream.of(productName.split(",")).map(x -> new Product(x)).forEach(System.out:: println);
    //        方法引用
            Stream.of(productName.split(",")).map(Product::new).forEach(System.out:: println);
    
    //        演示peek操作,peek 操作接收的是一个 Consumer<T> 函数。顾名思义 peek 操作会按照 Consumer<T>
    //        函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性
            Stream.of(productName.split(",")).peek(x -> x="hello"+x).forEach(System.out::println);
    
    //        当前的线程是主线程main,在peek方法中会输出main线程(串行方式,一直由main方法执行,即单线程)
    //        由于stream流支持并行,使用parallel(中间操作)开启并行(多个线程执行)
    //        Optional<Integer> max2=Stream.iterate(1,x -> x+1).limit(200).peek(x -> System.out.println(Thread.currentThread().getName()))
    //                .parallel().max(Integer::compare);
    //        System.out.println(max);
    
    
    //       并行流转换成串行流 (使用sequential()方法)
    //        Optional<Integer> max3=Stream.iterate(1,x -> x+1).limit(200).parallel().peek(x -> System.out.println(Thread.currentThread().getName()))
    //                .sequential().max(Integer::compare);
    //        System.out.println(max);
    //
    //            }
    }
    
    class Product{
        private String name;
    
        public Product(String name){
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Product{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    

    控制台输出结果:

    单元测试:实战应用

    package com.xiaomifeng1010.rbacboot.common.util;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import org.junit.Test;
    
    import java.time.LocalDate;
    import java.util.*;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    /**
     * @author xiaomifeng1010
     * @version 1.0
     * @date: 2020/3/21 12:47
     */
    
    public class Java8LambdaTest {
    
        /**
         * 将请求的地址url中参数和值取出来存放到map中
         * url:http:localhost:8080/index.do?itemId=1&userId=1000&type=2&token=134jojmgoijeo4&key=index
         */
        @Test
        public void test1() {
            String queryString ="itemId=1&userId=1000&type=2&token=134jojmgoijeo4&key=index";
    //        将字符串queryString按照&分割为数组,并创建stream流
            Map<String,String> params= Stream.of(queryString.split("&"))
    //                转换为(返回包含输出结果类型元素的stream,进一步按照等号分割的数组,保存在stream中)
                    .map(str -> str.split("="))
    //                collect方法收集,内部Collectors.toMap()转换为map类型
    //                toMap()方法中的参数为两个Function接口(第一个参数Function接口是获取map的key,
    //                第一个参数Function接口是获取map的value
    //                Function接口中输入参数s是数组,输出是数组元素,key是数组第一个元素,value是数组第二个元素
    //                因为是元素依次输入,所以其实每次的数组都是只有两个元素
                    .collect(Collectors.toMap(s -> s[0],s -> s[1]));
            System.out.println(params);
    
        }
    
        @Test
        public void test2 () {
    //        通过ComputerBooks()获取list,创建流,获取ComputerBook对象所有id,在存放到list中
            List<Integer> ids = ComputerBooks().stream().map( ComputerBook -> ComputerBook.getId()).collect(Collectors.toList());
            System.out.println(ids);
    //        方法引用ComputerBook::getId
            ids = ComputerBooks().stream().map(ComputerBook::getId).collect(Collectors.toList());
            System.out.println(ids);
    //        获取ComputerBook对象所有id,然后以逗号方式连接拼接成字符串
            String str = ComputerBooks().stream().map(ComputerBook -> ComputerBook.getId()+"").collect(Collectors.joining(","));
            System.out.println(str);
    //        获取ComputerBook对象所有id,然后以逗号方式连接拼接成字符串,并且以括号包裹
            str = ComputerBooks().stream().map(ComputerBook -> ComputerBook.getId()+"").collect(Collectors.joining(",", "(", ")"));
            System.out.println(str);
    
            str = ComputerBooks().stream().map(ComputerBook -> "'"+ComputerBook.getId()+"'").collect(Collectors.joining(",", "(", ")"));
            System.out.println(str);
        }
    
        @Test
        public void test3 () {
    //       通过ComputerBooks()获取list,创建流,获取ComputerBook对象所有类型,在存放到list中
    //        list中的泛型类型取决于map()方法转换输出类型(如本例中getType()方法)
            List<String> list = ComputerBooks().stream().map(ComputerBook::getType).collect(Collectors.toList());
            System.out.println(list);
    //      通过ComputerBooks()获取list,创建流,获取ComputerBook对象所有类型(不重复的),在存放到list中
            list = ComputerBooks().stream().map(ComputerBook::getType).distinct().collect(Collectors.toList());
            System.out.println(list);
    //      上边的也可以通过存放在set达到去重
            Set<String> set = ComputerBooks().stream().map(ComputerBook::getType).collect(Collectors.toSet());
            System.out.println(set);
        }
    
        @Test
        public void test4 () {
    //        以ComputerBook类型的价格排序,价格由低到高排序
    //		ComputerBooks().stream().sorted((ComputerBook1, ComputerBook2) -> Double.compare(ComputerBook1.getPrice(), ComputerBook2.getPrice())).forEach(System.out::println);;
    
    //		Comparator<ComputerBook> compa = (ComputerBook1, ComputerBook2) -> Double.compare(ComputerBook1.getPrice(), ComputerBook2.getPrice());
    //        以ComputerBook类型的价格排序,价格由高到低排序(之前的排序规则反转一下)
    //		ComputerBooks().stream().sorted(compa.reversed()).forEach(System.out::println);
    
    //        价格相同情况下,再比较出版日期
    //		Comparator<ComputerBook> compa = (ComputerBook1, ComputerBook2) -> Double.compare(ComputerBook1.getPrice(), ComputerBook2.getPrice());
    //		ComputerBooks().stream().sorted(compa.thenComparing((ComputerBook1,ComputerBook2) -> ComputerBook1.getPublishDate().isAfter(ComputerBook2.getPublishDate()) ? -1 : 1)).forEach(System.out::println);
    
    //		ComputerBooks().stream().sorted(Comparator.comparing(ComputerBook::getPrice)).forEach(System.out::println);
    //		ComputerBooks().stream().sorted(Comparator.comparing(ComputerBook::getPrice).reversed()).forEach(System.out::println);
            ComputerBooks().stream().sorted(Comparator.comparing(ComputerBook::getPrice).reversed().thenComparing(Comparator.comparing(ComputerBook::getPublishDate).reversed())).forEach(System.out::println);
        }
    
        @Test
        public void test5 () {
    //        将ComputerBook的id作为key,ComputerBook作为value存放到map中
    //		Map<Integer, ComputerBook> ComputerBooksMap = ComputerBooks().stream().collect(Collectors.toMap(ComputerBook -> ComputerBook.getId(), ComputerBook -> ComputerBook));
    //		System.out.println(ComputerBooksMap);
    
            Map<Integer, ComputerBook> ComputerBooksMap = ComputerBooks().stream().collect(Collectors.toMap(ComputerBook::getId, ComputerBook -> ComputerBook));
            System.out.println(ComputerBooksMap);
        }
    
        @Test
        public void test6 () {
    //        统计平均价格(ComputerBook::getPrice对象方法引用)
            Double avg = ComputerBooks().stream().collect(Collectors.averagingDouble(ComputerBook::getPrice));
            System.out.println(avg);
        }
    
        @Test
        public void test7 () {
    //        最高价的图书
            Optional<ComputerBook> computerBook = ComputerBooks().stream().collect(Collectors.maxBy(Comparator.comparing(ComputerBook::getPrice)));
            System.out.println(computerBook);
    //         最低价的图书
            computerBook = ComputerBooks().stream().collect(Collectors.minBy(Comparator.comparing(ComputerBook::getPrice)));
            System.out.println(computerBook);
    //         出版日期最早的书
            computerBook = ComputerBooks().stream().collect(Collectors.minBy(Comparator.comparing(ComputerBook::getPublishDate)));
            System.out.println(computerBook);
    //         出版日期最晚的书
            computerBook = ComputerBooks().stream().collect(Collectors.maxBy(Comparator.comparing(ComputerBook::getPublishDate)));
            System.out.println(computerBook);
    //          价格最高且日期最晚出版的图书(例子中有两本150的图书)
            Comparator<ComputerBook> comp = Comparator.comparing(ComputerBook::getPrice);
            computerBook = ComputerBooks().stream().collect(Collectors.maxBy(comp.thenComparing(Comparator.comparing(ComputerBook::getPublishDate))));
            System.out.println(computerBook);
        }
    
        @Test
        public void test8 () {
    //        分组统计(按照类型),map中key为类型type
    //		Map<String, List<ComputerBook>> ComputerBooksMap = ComputerBooks().stream().collect(Collectors.groupingBy(ComputerBook::getType));
    //		ComputerBooksMap.keySet().forEach(key -> {
    //			System.out.println(key);
    //			System.out.println(ComputerBooksMap.get(key));
    //			System.out.println("---------------------");
    //		});
    //          按照类型分组统计,并计算每种类型的数量
    //		Map<String, Long> ComputerBooksCount = ComputerBooks().stream().collect(Collectors.groupingBy(ComputerBook::getType, Collectors.counting()));
    //		System.out.println(ComputerBooksCount);
    //          按照类型分组统计,并计算每种类型图书的价格合计
    //		Map<String, Double> ComputerBooksSum = ComputerBooks().stream().collect(Collectors.groupingBy(ComputerBook::getType, Collectors.summingDouble(ComputerBook::getPrice)));
    //		System.out.println(ComputerBooksSum);
    //          按照类型分组统计,并计算每种类型图书的平均价格
    //		Map<String, Double> ComputerBooksSum = ComputerBooks().stream().collect(Collectors.groupingBy(ComputerBook::getType, Collectors.averagingDouble(ComputerBook::getPrice)));
    //		System.out.println(ComputerBooksSum);
    //          按照类型分组统计,并计算每种类型图书价格的最大值
    //		Map<String, Optional<ComputerBook>> ComputerBooksMaxPrice = ComputerBooks().stream().collect(Collectors.groupingBy(ComputerBook::getType, Collectors.maxBy(Comparator.comparing(ComputerBook::getPrice))));
    //		System.out.println(ComputerBooksMaxPrice);
    //          按照类型分组统计,并计算每种类型图书价格的最小值
    //		Map<String, Optional<ComputerBook>> ComputerBooksMinPrice = ComputerBooks().stream().collect(Collectors.groupingBy(ComputerBook::getType, Collectors.minBy(Comparator.comparing(ComputerBook::getPrice))));
    //		System.out.println(ComputerBooksMinPrice);
    //          每种类型出版时间最晚的
            Map<String, Optional<ComputerBook>> ComputerBooksMaxPubDate = ComputerBooks().stream().collect(Collectors.groupingBy(ComputerBook::getType, Collectors.maxBy(Comparator.comparing(ComputerBook::getPublishDate))));
            System.out.println(ComputerBooksMaxPubDate);
        }
    
        @Test
        public void test9 () {
    //        取出价格80元及以上的图书,按照出版日期从大到小排序
            ComputerBooks().stream().filter(ComputerBook -> ComputerBook.getPrice() >= 80).sorted(Comparator.comparing(ComputerBook::getPublishDate).reversed()).forEach(System.out::println);;
        }
    
        private List<ComputerBook> ComputerBooks(){
            List<ComputerBook> books = new ArrayList<>();
            books.add(new ComputerBook(1, "tomcat", 70d, "服务器", LocalDate.parse("2014-05-17")));
            books.add(new ComputerBook(2, "jetty", 60d, "服务器", LocalDate.parse("2015-12-01")));
            books.add(new ComputerBook(3, "nginx", 65d, "服务器", LocalDate.parse("2016-10-17")));
            books.add(new ComputerBook(4, "java", 66d, "编程语言", LocalDate.parse("2011-04-09")));
            books.add(new ComputerBook(5, "ruby", 80d, "编程语言", LocalDate.parse("2013-05-09")));
            books.add(new ComputerBook(6, "php", 40d, "编程语言", LocalDate.parse("2014-08-06")));
            books.add(new ComputerBook(7, "html", 44d, "编程语言", LocalDate.parse("2011-01-06")));
            books.add(new ComputerBook(8, "oracle", 150d, "数据库", LocalDate.parse("2013-08-09")));
            books.add(new ComputerBook(9, "mysql", 66d, "数据库", LocalDate.parse("2015-04-06")));
            books.add(new ComputerBook(10, "ssh", 70d, "编程语言", LocalDate.parse("2016-12-04")));
            books.add(new ComputerBook(11, "设计模式", 81d, "其他", LocalDate.parse("2017-04-06")));
            books.add(new ComputerBook(12, "重构", 62d, "其他", LocalDate.parse("2012-04-09")));
            books.add(new ComputerBook(13, "敏捷开发", 72d, "其他", LocalDate.parse("2016-09-07")));
            books.add(new ComputerBook(14, "从技术到管理", 42d, "其他", LocalDate.parse("2016-02-19")));
            books.add(new ComputerBook(15, "算法导论", 66d, "其他", LocalDate.parse("2010-05-08")));
            books.add(new ComputerBook(16, "oracle 12c", 150d, "数据库", LocalDate.parse("2017-05-08")));
            return books;
        }
    
    
    }
    @Data
    @AllArgsConstructor
    class ComputerBook {
        private int id;
        private String name;
        private double price;
        private String type;
        private LocalDate publishDate;
    }
    

    @Data和@AllArgsConstrucor注解需要引入lombok依赖,以及idea中安装lombok插件。

     

     

    展开全文
  • Lambda表达式

    2020-05-03 21:18:10
    1.1 什么是Lambda表达式 Lambda表达式也被成为箭头函数、匿名函数、闭包 Lambda表达式体现的是轻量级函数式编程思想 ‘->’符号是Lambda表达式的核心符号,符号左侧是操作参数,符号右侧是操作表达式 1.2 Model...

    慕课网Lambda笔记

    第一章 Java为什么引入 Lmabda表达式

    1.1 什么是Lambda表达式

    Lambda表达式也被成为箭头函数、匿名函数、闭包
    Lambda表达式体现的是轻量级函数式编程思想
    ‘->’符号是Lambda表达式的核心符号,符号左侧是操作参数,符号右侧是操作表达式

    1.2 Model Code as Data

    Model Code as Data,编码及数据,尽可能轻量级的将代码封装成数据。
    解决方案:接口&实现了(匿名内部类)
    存在问题:语法冗余、this关键字、变量捕获、数据控制等

    1.3 功能接口的设计及优化

    传统模式下新线程的创建
    传统模式下新线程的创建
    使用jdk1.8 新特性 lmabda表达式来优化线程模式
    在这里插入图片描述

    1.4 为什么要使用 Lmabda表达式

    1.它不是解决未知问题的新技术
    2. 他是对现有解决方案上语义的优化。
    3. 需要根据实际需求考虑性能问题。

    第二章 函数式接口的概述和定义

    2.1函数式接口定义

    函数式接口(functuon interface),就是Java类型系统中的接口
    函数式接口,是只包含一个接口方法的特殊接口
    语义化检测注解:@FunctionAllnterface,用来检查函数式接口的合法性

    在这里插入图片描述在这里插入图片描述
    定义一个函数式接口只需要在接口中提供一个接口方法,并在接口上添加@FunctionAllnterface注解即可,如果添加了多个饿方法,@FunctionAllnterface就会报错

    2.2默认方法和静态方法

    2.2.1默认接口方法的特性

    以当前用户身份接口为例,创建一个实现类 返回用户的身份
    在这里插入图片描述
    对当前代码进行测试
    在这里插入图片描述
    以上代码当需求进行变动后,比如要求所有的用户验证可以同时获取用户的验证信息【是否认证成功|成功-返回用户|失败-返回null】时,我们就需要修改所有的实现类代码。这时Lambda表达式可以解决这个问题,我们可以在接口中声明一个默认方法
    在这里插入图片描述
    这样我们就可以直接通过代码来调用默认方法
    在这里插入图片描述

    2.2.2 静态接口方法的特性

    以消息发送接口为例,在接口中添加一个静态方法:验证消息格式是否合法
    加粗样式
    添加一个实现类,重写接口中的方法
    在这里插入图片描述
    在format方法中,我们只是打印了一句话 消息转换。。。,并返回了msg,这时候我们可以直接调用接口的静态方法来验证消息的合法性。通过执行结果可以看出,静态方法不会对函数式接口的语义也不会产生影响,默认接口,函数式接口,静态接口可以在一个接口类中同时存在。

    从Object中继承的方法不会影响函数式接口

    由于java中的类都直接的或者间接的继承了Object类,所以 从Object继承的方法,无论是否是抽象的,都不会影响你函数式接口的语义。例如,在 IMessageFormat 中添加一个来自object的方法 toString(),函数式接口是不会报错的
    在这里插入图片描述

    2.2.3Lambda表达式和函数式接口的关系

    Lambda 只能操作一个方法 Java 中的Lambda表达式就是一个函数式接口的实现

    实现接口方法的另一种方式是使用匿名内部类,通过使用匿名内部类的方式来实现用户权限的验证操作。
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    通过观察匿名内部类和前面的实现方式,都会发现,其实和数据相关的代码只有
    " return “admin”.equals(username) ? “超级管理员” : “普通会员”; " 这一行,其他的代码都是冗余代码,那么能不能对代码进行优化呢?这就要使用到JDK8中的Lambda表达式。
    在这里插入图片描述
    相比较前面的匿名内部类,Lambda表达式实现方式更为简洁
    执行结果:
    在这里插入图片描述

    2.2.4 JDK 中常见的函数式接口

    Java类型系统内建函数式接口

    在这里插入图片描述

    JDK8提供了java.util.function包,提供了常用的函数式功能接口

    1.java.util.function.Predicate
    接收参数对象T,返回一个boolean类型结果,适合需要判断的场景
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    2.java.util.function.Comsumer
    接收参数T,不反回结果

    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    3. java.util.function.Function<T,R>
    接收一个参数对象T,返回结果对象R

    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    4. java.util.function.Supplier
    不接受参数,提供T对象的创建工厂

    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    常见的函数式接口使用

    5. java.util.function.UnaryOperator
    接收参数对象T,返回结果对象T ,常用于适配器模式

    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    6. java.util.function.BinaryOperator
    接收两个T对象,返回一个T对象的结果(使用场景:例如对两个对象进行比较,返回较大的结果)

    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    总结:

    java.util.function提供了大量的函数式接口
    Predicate 接收参数对象T,返回一个boolean类型结果
    Comsumer 接收参数T,不反回结果
    Function 接收一个参数对象T,返回结果对象R
    Supplier 不接受参数,提供T对象的创建工厂
    UnaryOperator 接收参数对象T,返回结果对象T
    BinaryOperator 接收两个T对象,返回一个T对象的结果

    2.2.5 Lmabda表达式的基本语法

    			>1)声明:就是 Lambda表达式绑定的接口类型
                2)参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数顺序一致。
                3)操作符:->
                4)执行代码块:包含在一对大括号中,出现在操作符的右侧
                 [接口声明] = (参数) ->{执行代码}
    
    没有返回值的Lambda表达式

    在这里插入图片描述
    如果在执行代码块中,只有一行代码,大括号是可以省略的
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    带有参数 但是没有返回值得Lambda 表达式和接口

    带有参数时,要将参数写在小括号中,参数的顺序和接口中定义的顺序相同
    在这里插入图片描述
    在设置参数时,也可以不写参数的类型,JVM会自动推断出参数的类型,以下方式和上面的代码是一样的
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    带有参数,带有返回值得Lambda表达式

    在这里插入图片描述
    当花括号内只有一行代码时,可以不添加花括号,同时,也不需要添加 return 关键字,虚拟机会自动帮你返回
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    总结

    1 Lambda 表达式,必须和接口进行绑定
    2 Lambda 表达式的参数,可以附带0到n个参数,括号中的参数类型可以不用指定,JVM在运行时,会自动根据绑定的抽象方法进行推导
    3 Lambda 表达式的返回值,如果代码快只有一行并且没有大括号,不用写大括号,单行代码会自动返回,如果添加了大括号,或者代码有多行代码,必须通过return 关键字返回结果

    3.6变量访问

    匿名内部类中的变量访问

    在这里插入图片描述

    lamdba表达式对于变量的访问

    在这里插入图片描述

    Lambda表达式类型检查

    表达式类型检查

    定义一个函数式接口 MyInterface,接收两个范型R,T,并提供一个方法 strategy 接受参数T 返回参数R
    在这里插入图片描述
    定义一个方法test,接受一个Myinterface作为参数,范型为String 和List,在方法内部我们将String 添加到List集合中
    在这里插入图片描述
    分别使用匿名内部类 和Lambda表达式的方式调用test
    在这里插入图片描述
    在Lambda表达式的写法中,并没有指明方法的参数为Myinterface,而是直接传递了参数X,y,这是由底层虚拟机来自动推导出来的。

    执行结果
    在这里插入图片描述

    总结

    当我们使用Lambda表达式语法的时候,jvm会获取当前方法的参数来进行推导,从而自动为我们绑定方法的参数类型.这就是Lambda表达式的类型检查

    3.7方法重载和Lambda表达式

    首先创建一个类App4,在App4中创建两个接口 Parame1 和Parame2,并定义outInfo(String info) 方法
    在这里插入图片描述
    然后定义重载方法lambdaMethod,参数分别是Parame1和Parame2
    在这里插入图片描述
    使用传统的匿名内部类的方式调用,在main方法中创建App4的对象,使用对象点lambdaMethdo的方式new一个Parame1或者Parame2
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    使用lambda表达式的话因为是重载方法,jvm自动推导类型的时候类型检查不通过,会报如下错误
    在这里插入图片描述
    在这种情况下只能使用匿名内部类来替代lambda表达式

    3.8深入理解Lambda表达式

    3.8.1Lambda表达式低层解析运行原理

    创建一个类App,并创建一个用于Lambda表达式执行的函数式接口IMakeUP,提供一个方法makeUp(String msg),在main方法中创建Lambda表达式打印msg
    在这里插入图片描述
    编译后,我们可以看到生成了App.class文件和IMakeUP.class文件
    在这里插入图片描述
    通过使用 javap -p App.class 命令对class文件进行反编译得到结果
    在这里插入图片描述
    Lambda表达式在JVM低层解析成私有静态方法和匿名内部类型

    通过实现接口的匿名内部类型中接口方法调用静态实现方法,完成Lambda表达式的执行

    第四章 Lambda表达式在集合中的运用

    4.1 方法引用

    • 方法引用是结合Lambda表达式的一种语法特性,

    首先创建一个测试类Test, 在里面创建一个内部类Person,添加属性 name(名字),gender(性别),age(年龄),并使用lombok创建get/set方法和构造函数
    在这里插入图片描述

    • 静态方法引用

    原始方式 类型名称.方法名称() -->类型名称::方法名称
    初始化一些数据,并对数据进行排序
    在这里插入图片描述
    使用匿名内部类的方式进行排序
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    Lambda表达式的实现方式
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    静态方法引用

    在Person类中添加一个静态方法comperByAge
    在这里插入图片描述
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    • 实例方法引用

    创建类型对应的对象 -->对象引用::实例方法名称

    在Test下创建一个新类PersonUtils,添加一个方法comerByName(),根据人员的名称的hash值来进行排序
    在这里插入图片描述
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    • 构造方法引用

    构造方法的引用需要绑定一个函数式接口

    首先创造一个函数式接口
    加粗样式
    在这里插入图片描述

    4.2 Steam概述

    什么是Steam
    Steam是Java为了操作数组,集合来进行复杂的聚合操作而推出的一套新的API
    新创建一个测试类Test2 创建一个main方法,在main方法中初始化一个字符串集合

    1.要求长度大于等于五的内容为有效账号
    在这里插入图片描述
    循环方式:
    在这里插入图片描述
    迭代器方式
    在这里插入图片描述
    使用Steam结合Lambda表达式的方式
    在这里插入图片描述
    注意:这三种方式的性能是相同的,只是精简了代码长度

    4.3 SteamAPI

    4.3.1Steam聚合操作

    什么是聚合操作?

    在常规业务处理中,针对业务的批量操作,例如,在电商项目中 ,获取指定数据的年平均消费额,获取指定店铺中最便宜的商品,获取指定店铺的当月有效订单数量等等

    Steam的处理流程
    获取数据源->数据转换(可以执行一次或者多次)->获取结果

    4.3.2获取Steam对象

    从集合,数组中获取

    Collection.steam(),如上一节中的account.steam(); //从集合中获取Steam对象
    Collection.parallelSteam(); //获取到一个支持并发的Steam对象;
    Arrs.Stream(T t); //从数组中获取Stream对象

    从缓冲流中获取

    BufferReader
    BufferReader.lines()->stream();

    静态工厂

    java.util.stream.Intstream().range()…
    java.nio.file.Files.work()…

    自行构建

    java.util.Spliterator

    更多的方式

    Random.ints()
    Pattern.splitAsStream()…

    4.3.3 Stream操作类型

    Stream的操作类型主要分为两种主要类型和一种辅助类型

    中间操作API

    API:intermediate中间:记录操作[无状态|有状态]

    它的操作结果是一个Stream对象,中间操作可以有一个或者多个连续的中间操作.

    需要注意的是,所有的中间操作,只记录操作方式,不做具体执行,直到结束操作执行时,才做数据的最终执行.中间操作就是业务的逻辑处理

    中间操作的过程分为有状态和无状态的操作.

    无状态: 数据处理时,不受前置中间操作的影响,就是前面的中间操作不会对当前的中间操作产生影响.
    无状态API:
    map
    filter
    peek
    parallel
    sequential
    unordered

    有状态: 数据处理时,会受到前一个中间操作的影响.
    例如,前一个中间操作是一个排序操作,当前中间操作是一个截取操作,有状态下,当前操作会对排序后的结果进行截取.
    有状态API:
    distinct
    sorted
    limit
    skip

    结束操作(终结操作)(Terminal)

    一个Stream只能有一个终结操作,一旦执行终结操作,Stream就会真实处理数据,生成对应的处理结果,并且这个结果是不可逆的…
    终结操作又区分为:短路和非短路操作. 短路操作和非短路操作是根据处理结果来定义的

    非短路操作:Stream对象必须处理完集合中所有的数据,才能返回处理结果
    非短路操作API:
    forEach
    forEachOrdered
    toArray
    reduce
    collect
    min
    max
    count
    iterator

    短路操作:当前的Strame对象在处理过程中,一旦满足某个条件,就可以获取结果,并不需要处理所有的数据
    短路操作API:
    anyMatch
    allMatch
    noneMatch
    findFirst
    findAny等
    短路操作也被称作:Short-circuiting.
    什么时候使用短路操作:
    例如从一个无限大的Stream中返回一个有限大的Stream

    4.4 Steam操作集合中的数据

    4.1.1将多个数据转换为Stream对象

    多个数据转换得到Stream对象

    在这里插入图片描述

    数组转换Stream对象

    在这里插入图片描述

    列表转换Stream对象

    在这里插入图片描述

    集合操作

    在这里插入图片描述

    Map操作

    在这里插入图片描述

    4.1.2Stream 对于基本数据类型的功能性封装

    针对于基本数据类型Strame在运算时会进行频繁的装箱拆箱,所以对于基本数据类型进行功能性封装
    只针对于常用的 int lang double 类型 ,其他的没有

    以int为例
    在这里插入图片描述

    4.1.3Stream转换得到指定的数据类型(数组,集合,字符串,map)

    数组

    在这里插入图片描述

    字符串

    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    列表

    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    集合

    在这里插入图片描述
    >执行结果:
    在这里插入图片描述

    Map

    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    在这里,Collectors.toMap方法需要传入一个Function,这个接口在处理的过程中会得到两个不同的数据 ,由于我们的stream
    中只包含了一个单个数据,所以我们在处理的过程中需要将Map中的数据进行单独的处理

    在这里插入图片描述

    注意:由于stream一旦进行终结操作,那么久意味着整个过程的结束,所以上述代码无法一次性全部执行,只能在执行一个的时候,将其他代码注释掉.

    4.1.4 Stream常见的API操作

    准备数据

    在这里插入图片描述

    在每个数据的前面增加一个字段,梁山好汉

    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    添加过滤条件,来过滤符合条件的用户

    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    循环遍历

    在这里插入图片描述

    使用peek来进行多次的迭代操作

    在这里插入图片描述
    通过peek可以对数据进行多次的迭代操作,由于peek是中间操作,所以在peek的过程中不会遍历数据,只有等到执行终结操作的时候才会处理数据,所以虽然进行了三次peek,但实际上只进行了一次迭代操作.

    stream对于数字运算的支持

    构建数据

    在这里插入图片描述

    skip()

    skip操作是一个中间操作,他是一个有状态的操作 跳过部分数据,完成数据的提取

    在这里插入图片描述
    执行结果
    在这里插入图片描述

    limit()

    limit 操作是一个中间操作,他是一个有状态的操作 限制输出数据的输出数量

    先跳过3个数据在限制输出两个数据
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    distinct()

    distinct 操作是一个中间操作,他是一个有状态的操作 去除重复数据

    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    初始化数据的时候忘了加重复元素…所以这的结果和原来的数据一样 emmmm…

    sorted()

    sorted 操作是一个中间操作,他是一个有状态的操作 对数据进行排序

    x-y是从小到大排序, y-x是从大到小排序
    在这里插入图片描述
    执行结果
    在这里插入图片描述

    max()

    max 操作是一个中间操作,他是一 个有状态的操作 获取最大值

    如果改变了x-y的顺序的话得到的结果也会改编.调用max后会返回一个Optional对象,调用Optional.get()即可获取结果
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    min()

    minx 操作是一个中间操作,他是一个有状态的操作 获取最小值

    如果改变了x-y的顺序的话得到的结果也会改编.调用min后会返回一个Optional对象,调用Optional.get()即可获取结果
    在这里插入图片描述
    执行结果
    在这里插入图片描述

    reduce()

    reduce 操作是一个中间操作,他是一个有状态的操作 合并处理数据

    这里对数据做了一个累加,即前面的数据想加合并,然后在与下一个数据想加调用reduce后会返回一个Optional对象,调用Optional.get()即可获取结果,这里直接使用方法链调用了
    在这里插入图片描述
    执行结果:
    在这里插入图片描述

    5 Lambda表达式在实际生产中的应用

    5.1Lambda表达式重构项目

    5.2 Lambda和Stream的性能问题

    1 通过基本数据类型:整数进行测试
    创建一个Integer list 集合,添加一些随机数集合数据
    在这里插入图片描述

    性能测试
    1 stream 对象

    创建一个方法testStreame 接收一个list集合方法,通过stream对象获取最大值
    在这里插入图片描述
    执行时间 : 641ms
    在这里插入图片描述

    2.for循环

    创建一个方法testForloop 接收一个list集合方法,通过stream对象获取最大值
    在这里插入图片描述
    执行时间:94 ms
    在这里插入图片描述

    3 parallelstream

    创建一个方法testParallelstream 接收一个list集合方法,通过stream对象获取最大值在这里插入图片描述
    执行时间: 169ms
    在这里插入图片描述

    4 增强型for循环
    创建一个方法testStrongstream 接收一个list集合方法,通过stream对象获取最大值![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503202317431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3NjEyNzU1,size_16,color_FFFFFF,t_70)
    执行时间:83 ms
    

    在这里插入图片描述

    5 迭代器操作

    创建一个方法testIterator接收一个list集合方法,通过stream对象获取最大值
    在这里插入图片描述
    执行时间: 74ms
    在这里插入图片描述

    总结:串行的Stream 的执行时间比较长,并行的Stream和增强for很接近了,\

    2 通过复杂数据类型:对象进行测试

    创建一个对象产品.并增加一个全属性的构造方法
    在这里插入图片描述
    我们来测试获取热度最高的产品对象

    创建一个对象集合,并初始化数据
    在这里插入图片描述

    通过五种方式对获取热度最高的产品进行测试
    1 Stream

    在这里插入图片描述执行时间 :380ms
    在这里插入图片描述

    2 parallelstream

    在这里插入图片描述
    执行时间:90ms
    在这里插入图片描述

    3 for

    在这里插入图片描述
    执行时间:110ms
    在这里插入图片描述

    4 增强for

    在这里插入图片描述
    执行时间85ms’
    在这里插入图片描述

    5迭代器

    在这里插入图片描述
    执行时间 70ms
    在这里插入图片描述
    总结:在处理对象的过程中,串行Stream 依旧是执行时间最长的,parallelstream的处理事件已经可以和迭代器媲美,比普通的for要高,最快的还是迭代器.和增强for

    5.3 Stream 的线程安全问题
    1 Stream并行原理

    在这里插入图片描述
    通过整数列表的并行复制来测试Stream是否存在安全问题
    初始化一个整数集合,分别通过串行Stream和并行Stream来进行集合间的复制,并打印源集合和复制后的集合的长度
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    源数据长度为1000,串行的Stream的长度没变,但是并行的Stream的集合长度变小了.说明并行Stream存在线程安全问题.

    展开全文
  • using System; using System.Collections.Generic; using System.Linq; using System.Text; ...namespace LambdaSample { class Program { static void Main(string[] args) { ...
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace LambdaSample
    {
        class Program
        {
            static void Main(string[] args)
            {
                int[] myIntArray = { 1,2, 3, 4, 5 };
                //累加器计算数组总和
                int result = myIntArray.Aggregate((paramA, paramB) => paramA + paramB);
                int result1 = myIntArray.Aggregate<int>((paramA, paramB) => paramA + paramB);
                int result2 = myIntArray.Aggregate<int, int>(0, (paramA, paramB) => paramA + paramB);
                //递归计算数组总和
                int result3 = count(5);
                Console.WriteLine("The Result is: " + result);
                Console.WriteLine("The Result1 is: " + result1);
                Console.WriteLine("The Result2 is: " + result2);
                Console.WriteLine("The Result3 is: " + result3);
    
                string[] myStrArray = { "Admin", "Change", "ByPage" };
                //字符串数组拼接
                string strResult = myStrArray.Aggregate((paramA, paramB) => paramA + " " + paramB);
                //字符串数组字符串总长度计算
                int result4 = myStrArray.Aggregate<string, int>(0, (a, b) => a + b.Length);
                //字符串数组拼接2,增加字符串"Some curries: "
                string strResult1 = myStrArray.Aggregate<string, string>("Some curries: ", (a, b) => a + " " + b);
                //字符串数组字符串总长度计算2
                int result5 = myStrArray.Aggregate<string, string, int>("Some curries: ", (a, b) => a + " " + b, a => a.Length);
                Console.WriteLine("The StrResult is: " + strResult);
                Console.WriteLine("The Result4 is: " + result4);
                Console.WriteLine(strResult1);
                Console.WriteLine("The Result5 is: " + result5);
                Console.ReadKey();
            }
    
            static int count(int n)
            {
                if (n == 1)
                {
                    return 1;
                }
                return n + count(n - 1);
            }
        }
    }

     

    该例子源代码摘自C#入门经典第5版第14章(387页)

     

    转载于:https://www.cnblogs.com/qiu2013/p/5144487.html

    展开全文
  • Java Lambda表达式

    2020-12-17 14:33:26
    Java Lambda表达式的一个重要用法是简化某些匿名内部类(Anonymous Classes)的写法。实际上Lambda表达式并不仅仅是匿名内部类的语法糖,JVM内部是通过invokedynamic指令来实现Lambda表达式的。具体原理放到下一篇。...
  • 详解Java Lambda表达式

    2020-10-28 16:11:12
    Java Lambda表达式的一个重要用法是简化某些匿名内部类(Anonymous Classes)的写法。实际上Lambda表达式并不仅仅是匿名内部类的语法糖,JVM内部是通过***invokedynamic***指令来实现Lambda表达式的。具体原理放到下...
  • C++:lambda表达式

    2020-07-26 10:32:35
    使用lambda表达式可编写内嵌的匿名函数。而不必编写独立函数或函数对象,使代码更容易阅读和理解。 Lambda语法 lambda表达式以方括号[]开始(这称为lambda引入符),其后是花括号{},其中包含lambda表达式体。...
  • C++高阶-lambda表达式

    2021-04-21 16:35:44
    在作用上, Lambda表达式跟函数指针和函数对象相同,它同样可以方便地应用于 STL 算法中,对算法进行自定义。 在使用上, Lambda 表达式可以在使用函数的地方对其进行定义,使整个代码更加自然流畅。 更方便的是,...
  • Java 8中的Lambda表达式Lambda表达式变量作用域使用示例注意点 Lambda表达式 Lambda中允许将一个函数作为方法的参数,即函数作为参数传递进方法中 使用Lambda表达式可以使代码更加简洁 变量作用域 Lambda表达式...
  • Lambda表达式常用场景

    2020-01-25 16:38:38
    Lambda 表达式是 Java 8 引入的新特性,结合 forEach 方法可以更方便地实现遍历。此外,它还可代替 Runnable 类,大大简化了代码的编写。 下面介绍了一些常见的应用场景,在这些场景中适时地使用 Lambda 表达式要比...
  • java8 lambda表达式

    2017-03-27 00:32:56
    java8 lambda表达式
  • Java8 lambda表达式

    2018-05-09 18:14:25
    Java8发布已经有相当长的时间,其中新增加的lambda表达式支持函数式编程,实现功能的时候代码量少而且简洁,现在就开始学习lambda表达式的使用方法。 函数式接口 任何有函数式接口的地方,只有一个抽象方法的...
  • java8之后,java引入了一个新的特性,lambda表达式,非常方便。以下内容来自其他博主的一些总结,在此表示感谢。 目录 1.用Lambda表达式实现Runnable接口 2.用Lambda表达式实现事件监听程序 3.用Lambda表达式...
  • 24.C# Lambda表达式

    2019-04-10 16:38:00
    1.Lambda表达式的含义 Lambda表达式是C#3.0引入的一种结构,使用它可以简化C#编程。 2.Lambda表达式与匿名方法 我们知道匿名方法可用于事件处理,如下delegate声明了一个匿名方法,它用于timer的Elapsed事件。 ...
  • lambda表达式详解

    2020-06-21 18:18:38
    实现lambda表达式与传统接口的对比 public class lambdaTest { interface Print{ void print(String str); } void printSomeThing(String str, Print print){ print.print(str); } public static void ...
  • C++11 Lambda表达式简介

    2020-04-30 18:19:58
    Lambda表达式一般都是从方括号[]开始,然后结束于花括号{},主要包括五个部分: [捕获列表 capture]:对外部变量的捕获方式说明,是必须的; (操作符重载函数参数 params):没有参数及修饰时可省略,是可选...
  • Java Lambda 表达式的常见应用场景

    千次阅读 2018-03-03 00:00:35
    Lambda 表达式是 Java 8 引入的新特性,结合 forEach 方法可以更方便地实现遍历。此外,它还可代替 Runnable 类,大大简化了代码的编写。下面介绍了一些常见的应用场景,在这些场景中适时地使用 Lambda 表达式要比...
  • Lambda表达式使用

    2018-11-15 20:05:01
    函数接口是只有一个抽象方法的接口,用作Lambda表达式的类型。 例如:ActionListener接口就是一个函数接口 只有一个抽象方法,由于定义在接口中abstract关键字可以不写 2.可以使用以下图表示 ActionEvent ----&...
  • Kotlin学习笔记7——高阶函数和Lambda表达式前言高阶函数函数用作函数参数函数用作函数返回值自定义高阶函数Lambda 表达式与匿名函数Lambda 表达式语法拖尾 lambda 表达式it:单个参数的隐式名称lambda 表达式中返回...
  • 本文主要着重讲解LAMBDA表达式的用法,关于LAMBDA的实现可能会在后续的文章中讲解。本文内容基本基于一个公众号的内容,在此基础上进行精简。如果有兴趣深入了解的同学请看链接:关于Java Lambda表达式看这一篇就够了...
  • java8之Lambda表达式

    2021-01-22 10:41:05
    java8最大的特性就是引入Lambda表达式,即函数式编程,可以将行为进行传递。总结就是:使用不可变值与函数,函数对不可变值进行处理,映射成另一个值。 二、java重要的函数式接口 1、什么是函数式接口 函数接口是...
  • 1. Lambda表达式 1. Lambda表达式的概述:Lambda表达式是JDK1.8之后引入的一种新的语法,这种语法可以对匿名内部类进行简写 2. Lambda表达式的语法 (1)JDK1.8之后引入了一个箭头符号 -> (2)这个箭头符号将...
  • lambda表达式用法

    2020-09-16 17:55:37
    Lambda 表达式实际上是一个函数,只是它没有名字,叫匿名函数。C++11新增了lambda函数,其基本格式如下 [捕捉列表] (参数) mutable -> 返回值类型 {函数体} "捕捉列表",也叫“外部变量访问方式说明符”,...
  • 文章目录01、文章目录02、Lambda表达式03、函数式编程04、Lambda Syntax05、Lambda的使用场景06、Lambda示例6.1 示例一6.2 示例二07、小结 02、Lambda表达式 lamda表达式是c++11规范引入的新语法。“Lambda 表达式”...
  • C++ Lambda表达式

    2020-03-16 17:58:49
    我们在使用 STL 时,往往会大量用到函数对象,为此要编写很多函数对象类。有的函数对象类只用来定义了一个对象,而且这个对象也只使用了一次,编写这样的函数对象类就有点浪费。...Lambda 表达式能够...
  • Lambda表达式及StreamAPI详解前言一、Lanbda表达式二、初识Stream1、将List转换为管道流2、将数组转换为管道流三、Stream的中间操作:有状态与无状态3.1、有状态操作(filter、map)3.2、无状态操作(排序、截取)四...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,493
精华内容 2,197
关键字:

lambda表达式累加