美文网首页Spring.Net
lambda函数式编程详解

lambda函数式编程详解

作者: 蒙古code | 来源:发表于2019-08-13 00:19 被阅读0次

    概念

    Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性,使用 Lambda 表达式可以使代码变的更加简洁紧凑,Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
    函数式接口是Java8新增加的内容。如果一个接口只有一个抽象方法,那么该接口就是函数式接口。

    语法

    1.(parameters) -> expression
    2.(parameters) ->{ statements; }
    3.()->expression
    

    特性

    1.可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
    2.可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    3.可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    4.可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

       // 类型声明
          MathOperation addition = (int a, int b) -> a + b;
            
          // 不用类型声明
          MathOperation subtraction = (a, b) -> a - b;
            
          // 大括号中的返回语句
          MathOperation multiplication = (int a, int b) -> { return a * b; };
            
          // 没有大括号及返回语句
          MathOperation division = (int a, int b) -> a / b;
    
       interface MathOperation {
          int operation(int a, int b);
       }
        
       interface GreetingService {
          void sayMessage(String message);
       }
        
       private int operate(int a, int b, MathOperation mathOperation){
          return mathOperation.operation(a, b);
       }
    

    注意

    1.Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
    2.Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
    3.在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量

    String first = "";  
    Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错
    

    4.lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

    int num = 1;  
    Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
    s.convert(2);
    num = 5;  
    //报错信息:Local variable num defined in an enclosing scope must be final or effectively 
     final
    

    5.lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

       final static String salutation = "Hello! ";
       
       public static void main(String args[]){
          GreetingService greetService1 = message -> 
          System.out.println(salutation + message);
          greetService1.sayMessage("Runoob");
       }
        
       interface GreetingService {
          void sayMessage(String message);
       }
    

    函数式编程的特性与优缺点

    1、函数是"第一等公民"
    什么是"第一等公民"?所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,它不仅拥有一切传统函数的使用方式(声明和调用),可以赋值给其他变量(赋值),也可以作为参数,传入另一个函数(传参),或者作为别的函数的返回值(返回)。函数可以作为参数进行传递,意味我们可以把行为"参数化",处理逻辑可以从外部传入,这样程序就可以设计得更灵活。
    2、没有"副作用"
    所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
    3、引用透明
    引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。这里强调了一点"输入"不变则"输出"也不变,就像数学函数里面的f(x),只要输入的x一样那得到的结果也肯定定是一样的。

    优点

    1、代码简洁,开发快速。
    函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。Paul Graham在《黑客与画家》一书中写道:同样功能的程序,极端情况下,Lisp代码的长度可能是C代码的二十分之一。如果程序员每天所写的代码行数基本相同,这就意味着,"C语言需要一年时间完成开发某个功能,Lisp语言只需要不到三星期。反过来说,如果某个新功能,Lisp语言完成开发需要三个月,C语言需要写五年。"当然,这样的对比故意夸大了差异,但是"在一个高度竞争的市场中,即使开发速度只相差两三倍,也足以使得你永远处在落后的位置。"

    1. 接近自然语言,易于理解
    Map<String,List<Student>> studentsMap = students.stream().collect(Collectors.groupingBy(Student::getSex));
    
    1. 更方便的代码管理
      函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合
    2. 易于"并发编程"
      函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
      请看下面的代码:
      var s1 = Op1();
      var s2 = Op2();
      var s3 = concat(s1, s2);
      由于s1和s2互不干扰,不会修改变量,谁先执行是无所谓的,所以可以放心地增加线程,把它们分配在两个线程上完成。其他类型的语言就做不到这一点,因为s1可能会修改系统状态,而s2可能会用到这些状态,所以必须保证s2在s1之后运行,自然也就不能部署到其他线程上了。多核CPU是将来的潮流,所以函数式编程的这个特性非常重要。
    3. 代码的热升级
      函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。Erlang语言早就证明了这一点,它是瑞典爱立信公司为了管理电话系统而开发的,电话系统的升级当然是不能停机的。

    lambda 优缺点吗???

    1、函数式编程常被认为严重耗费在CPU和存储器资源。主因有二:

    • 早期的函数式编程语言实现时并无考虑过效率问题。

    • 有些非函数式编程语言为求提升速度,不提供自动边界检查或自动垃圾回收等功能。

      惰性求值亦为语言如Haskell增加了额外的管理工作。
      2、语言学习曲线陡峭,难度高
      函数式语言对开发者的要求比较高,学习曲线比较陡,而且很容易因为其灵活的语法控制不好程序的结构。

    编程世界我来啦

    1.我们再看一下IntBinaryOperator的定义

    @FunctionalInterface
    public interface IntBinaryOperator {
        /**
         * Applies this operator to the given operands.
         * @param left the first operand
         * @param right the second operand
         * @return the operator result
         */
        int applyAsInt(int left, int right);
    }
    

    我们得知IntBinaryOperator是一个接口并且上面有一个@FunctionalInterface的注解,@FunctionalInterface标注了这是一个函数式接口,所以我们知道了(int a, int b) -> {return a + b;}返回的一个IntBinaryOperator的匿名实现类。

    2.在java 8中已经为我们定义了很多常用的函数式接口它们都放在java.util.function包下面,一般有以下常用的四大核心接口:

    Consumer<T>(消费型接口) T void 对类型为T的对象应用操作。void accept(T t)
    Supplier<T>(供给型接口) 无 T 返回类型为T的对象。 T get();
    Function<T, R>(函数型接口) T R 对类型为T的对象应用操作并返回R类型的对象R apply(T t);
    Predicate<T>(断言型接口) T boolean 确定类型为T的对象是否满足约束。boolean test(T t);

    以下是扩展 或者 增强上边的接口

    UnaryOperator<T>:继承自Function<T, T>,接受一个参数T,返回相同类型T的结果
    BiFunction<T, U, R>:接受两个参数T和U,返回结果R
    BinaryOperator<T>:继承自BiFunction<T, T, T>,接受两个相同类型T的参数,返回相同类型T的结果
    Runnable:实际上是不接受任何参数,也不返回结果
    Comparable<T>:实际上是接受两个相同类型T的参数,返回int
    Callable<V>:不接受任何参数,返回结果V

    Consumer 应用的例子:

     /**
         * Performs this operation on the given argument.
         *
         * @param t the input argument
         */
        void accept(T t);
    // 应用
      Consumer t = (c)-> {System.out.println(c);};
            t.accept("v");
    
    default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    Consumer<Integer> cons = (x)->{
        System.out.println("第一个Consumer"+(++x)); // 51
    };      
    Consumer<Integer> cons1 = (x)->{
        System.out.println("第二个Consumer"+(--x)); // 49
    };
    cons.andThen(cons1).accept(50);
    BiConsumer<Integer,Integer> bicons = (x,y)->{
        System.out.println("第一个biConsumer:"+(x+y));
    };
    
    BiConsumer<Integer,Integer> bicons1 = (x,y)->{
        System.out.println("第二个biConsumer:"+x*y);
    };
    // 第一个biConsumer:8
    bicons.accept(3, 5);
    // 第一个biConsumer:8
    // 第二个biConsumer:15     
    bicons.andThen(bicons1).accept(3, 5);
    

    Supplier 例子

    
     Supplier gs = () ->{return "b";};
            gs.get();
    

    Function 应用例子

       /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    / myFun就是上面定义的myFUn
    Integer applyResult = myFun.apply(1); // applyResult =2
    Function<Integer,Integer> myFun = (x)->{
         return x+1;
    };
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    Function<Integer,Integer> plus = (x)->{
        return x+1;
    };
    
    Function<Integer,Integer> multip = (x)->{
        return x*3;
    };
    Integer apply = plus.compose(multip).apply(2); // 此处执行的就是2*3+1=7
    // 两个Funtion的调用反过来一下
    Integer overApply = multip.compose(plus).apply(2); // 此处执行的就是(2+1)*3=9
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    Function<Integer,Integer> plus = (x)->{
        return x+1;
    };
    
    Function<Integer,Integer> multip = (x)->{
        return x*3;
    };
    Integer apply = plus.andThen(multip).apply(2); // 此处执行的就是(2+1)*3=9
    // 两个Funtion的调用反过来一下,跟上面的一样
    Integer overApply = multip.andThen(plus).apply(2); // 此处执行的就是2*3+1=7
     /**
         * 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;
        }
    Function<Integer, Integer> identity = Function.identity();
    Integer apply = identity.apply(5); // apply=5
    BiFunction<Integer,Integer,Integer> plus = (x,y)->{
        return x+y;
    };
    
    BiFunction<Integer,Integer,Integer> multip = (x,y)->{
         return x*y;
    };
    
    Integer apply = plus.apply(3, 2); // 3+2=5
    Integer apply1 = multip.apply(3, 2); // 3*2=6
    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));
        }
    BiFunction<Integer, Integer, Integer> multip = (x, y) -> {
        return x * y;
    };
    Function<Integer, Integer> sqr = (x) -> {
        return x * x;
    };   
    Integer apply = multip.andThen(sqr).apply(3, 5); //(3*5)*(3*5)= 225
    //他是这么执行的,先执行2个参数的就是上面的multip 方法,然后得到一个结果,再将结果当做参数去执行只有一个参数的sqr方法。所以难怪没有反过来执行的compose方法了,因为不能同时得到2个返回值,所以只能先执行BiFunction方法了。
    

    Predicate 断言例子 适合 集合操作的过滤

    
        /**
         * Evaluates this predicate on the given argument.
         *
         * @param t the input argument
         * @return {@code true} if the input argument matches the predicate,
         * otherwise {@code false}
         */
        boolean test(T t);
    Predicate<String> pre = (x)->{
        return x.equals("hello word!");
    };
    boolean test = pre.test("hello world!"); // test = false
    default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
        }
    Predicate<Integer> preInt = (x)->{
        return x>0;
    };  
    Predicate<Number> preNum = (x)->{
        return x.equals(10);
    };
    preNum.and(preInt).test(10); //编译报错
    boolean test = preInt.and(preNum).test(10); //true
    
    default Predicate<T> negate() {
            return (t) -> !test(t);
        }
    Predicate<Integer> preInt = (x)->{
        return x>0;
    };  
    Predicate<Number> preNum = (x)->{
        return x.equals(10);
    };
    boolean test = preInt.or(preNum).test(5); //true
    
    Predicate<Integer> preInt = (x)->{
        return x>0;
    };
    boolean test = preInt.negate().test(5); // false 非操作
    

    跟Function一样,Predicate也有传2个参数的BiPredicate接口

    BiPredicate<Integer,Integer> biprePlus = (x,y)->{
        return x+y>10;
    };
    BiPredicate<Integer,Integer> bipreMutip = (x,y)->{
        return x*y>10;
    };
    boolean t1 = biprePlus.test(3, 5); // false
    boolean t2 = biprePlus.and(bipreMutip).test(3, 5); // false
    boolean t3 = biprePlus.or(bipreMutip).test(3, 5); // true
    boolean t4 = biprePlus.negate().test(3, 5); // true
    

    lambda方法引用

    1.类名::静态方法名

    如果函数式接口的抽象方法的实现刚好可以通过调用一个静态方法来实现,那么就可以使用该类型的方法引用。例如,一个Integer类型集合,现在需要把它转换成对应的String类型的集合,就可以使用下面的代码

    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    List<String> strList = list.stream()
                            .map(String::valueOf)
                            .collect(Collectors.toList());
    
    2. 对象名::实例方法名
    public class Student {
    
        private String name;
    
        private int score;
    
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getScore() {
            return score;
        }
    
        public void setScore(int score) {
            this.score = score;
        }
    
        public int compareByScore(Student student1, Student student2) {
            return student1.getScore() - student2.getScore();
        }
    
        public int compareByName(Student student1, Student student2) {
            return student1.getName()
                    .compareToIgnoreCase(student2.getName());
        }
    }
    Student student1 = new Student("zhangsan", 10);
    Student student2 = new Student("lisi", 90);
    Student student3 = new Student("wangwu", 50);
    Student student4 = new Student("zhaoliu", 40);
    
    List<Student> students = Arrays.asList(student1, student2, student3, student4);
    students.sort(student1::compareByName);
    
    3. 类名::实例方法名
    
     public int compareByName(Student student) {
            return this.getName()
            .compareToIgnoreCase(student.getName());
        }
    Student student1 = new Student("zhangsan", 10);
    Student student2 = new Student("lisi", 90);
    Student student3 = new Student("wangwu", 50);
    Student student4 = new Student("zhaoliu", 40);
    
    List<Student> students = Arrays.asList(student1, student2, student3, student4);
    students.sort(Student::compareByName);
    
    4. 构造方法引用: 类名::new
    public class MethodReferenceTest {
    
        public String getString(Supplier<String> supplier) {
            return supplier.get() + "test";
        }
    
        public static void main(String[] args) {
            MethodReferenceTest test = new MethodReferenceTest();
            System.out.println(test.getString(String::new));
        }
    }
    

    1.中间操作:

    结合Predicate接口,Filter对流对象中的所有元素进行过滤,该操作是一个中间操作,这意味着你可以在操作返回结果的基础上进行其他操作

    public static void sreamFilterTest(List<String> lists){ //要明确这list的泛型类型,否则jvm不能根据上下文确定参数类型
            lists.stream().filter((s -> s.startsWith("a"))).forEach(System.out::println);//将开头是a的过滤出来
    
            //等价于以上操作
            Predicate<String> predicate = (s) -> s.startsWith("a");//将开头是a的过滤出来
            lists.stream().filter(predicate).forEach(System.out::println);
    
            //连续过滤
            Predicate<String> predicate1 = (s -> s.endsWith("1"));//将开头是a,并且结尾是1的过滤出来
            lists.stream().filter(predicate).filter(predicate1).forEach(System.out::println);
        }
    
    排序(sorted)

    结合Comparator,该操作返回一个排序过后的流的视图,原始流的顺序不会改变。通过Comparator来指定排序规则,默认是自然排序

     private static void streamSortedTest(List<String> list){
            //默认排序
            list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);
            System.out.println("- - - - - - - - -");
            //自定义排序
            list.stream().sorted(((s, t1) -> t1.compareTo(s))).filter(s -> s.startsWith("a")).forEach(System.out::println);
        }
    

    映射(map)

    private static void streamMapTest(List<String> list){
            list.stream().map(String::toUpperCase).sorted((s, t1) -> t1.compareTo(s)).forEach(System.out::println);
            System.out.println("- - - - - - ");
            //自定义映射规则
            Function<String,String> function = s -> {return  s + ".map3";};
            list.stream().map(function).forEach(System.out::println);
        }
    

    完结操作方法

    匹配(match)

    用来判断某个predicate是否和流对象相匹配,最终返回boolean类型的结果

     private static void streamMatchTest(List<String> list){
            //流对象中只要有一个元素匹配就返回true
            boolean anyStartWithA = list.stream().anyMatch(s -> s.startsWith("a"));
            System.out.println("集合中是否有以'a'来头:"+ anyStartWithA);
            //流对象中每一个元素都匹配才返回true
            boolean allStartWithA = list.stream().allMatch(s -> s.startsWith("a"));
            System.out.println("集合中每一个都是以'a'开头:"+ allStartWithA);
            //流对象中没有匹配时返回true
            boolean noneStartWithA = list.stream().noneMatch(s -> s.startsWith("c"));
            System.out.println("集合中没有以'c'开头:"+ noneStartWithA);
        }
    

    收集(collect)

    在对经过变换后,将变换的stream元素收集,比如将这些元素存在集合中,可以使用stream提供的collect方法

    private static void streamCollectTest(List<String> list){
            List<String> listNew = list.stream().filter(s -> s.startsWith("b")).sorted().collect(Collectors.toList());
            System.out.println(listNew );
        }
    

    规约(reduce)

    允许我们用自己的方式计算元素或者将一个stream中元素以某种规律关联

    private static void streamReduceTest(List<String> list){
            Optional<String> optional = list.stream().sorted().reduce((s, s2) -> {
                System.out.println(s+"-"+s2);
                return s+"-"+s2;
            });
        }
    

    计数(count)

    用来统计流中元素的总数

    private static void streamCountTest(List<String> list){
            long count = list.stream().filter(s -> s.startsWith("b")).count();
            System.out.println("以'b'开头的数量:"+ count);
        }
    

    并行操作stream

    并行Stream:基于Fork-join并行分解框架实现,将大数据集合切分为多个小数据结合交给不同的线程去处理,这样在多核处理情况下,性能会得到很大的提高。
    这和MapReduce的设计理念一致:大任务化小,小任务再分配到不同的机器执行。只不过这里的小任务是交给不同的处理器。
    结果是性能提高50%,单核下还是串行流性能比较好,并行流的使用场景是多核+大数据

     //创建一个大集合
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000000; i++) {
                UUID uuid = UUID.randomUUID();
                list.add(uuid.toString());
            }
    //并行stream
        private static void parallelStreamSortedTest(List<String> list){
            long startTime = System.nanoTime();//返回最准确的可用系统计时器的当前值,以毫微秒为单位。
            long count = list.parallelStream().sorted().count();
            long endTime = System.nanoTime();
            long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
            System.out.printf("并行排序花费时间:%d ms",millis);
        }
        //串行stream
        private static void streamSortedTest(List<String> list){
            long startTime = System.nanoTime();//返回最准确的可用系统计时器的当前值,以毫微秒为单位。
            long count = list.stream().sorted().count();
            long endTime = System.nanoTime();
            long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
            System.out.printf("串行排序花费时间:%d ms",millis);
        }
    

    高阶玩法

    1.传入数组ids,在list<Obj>上操作,找出Obj中id想匹配的,并且按照id进行collect成map(这里假设找出来的按照id不重复

    public Map<Integer, MyObj> getOperationByShipmentIds(Collection<Integer> ids) {
     return storage
       .stream()
       .filter(op -> ids.contains(op.getId()))
       .collect(Collectors.toMap(MyObj::getId, Function.identity()));
    }
    // 接上面的,假设id可以重复
    public Map<Integer, MyObj> getOperationByShipmentIds(Collection<Integer> ids) {
     return storage
       .stream()
       .filter(op -> ids.contains(op.getId()))
       .collect(Collectors.groupingBy(MyObj::getId));
    }
    // 对象列表某一列求和
    list.values().stream().mapToInt(obj -> obj.getIntField()).sum();
    //多个list追加到同一个中
    List<MyObject> list = services.stream()
            .flatMap(s -> s.getObjects().stream())
            .collect(Collectors.toList());
    // 计算List中的元素的最大值,最小值,总和及平均值
    List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
    IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x)
                                                .summaryStatistics();
    System.out.println("Highest prime number in List : " + stats.getMax());
    System.out.println("Lowest prime number in List : " + stats.getMin());
    System.out.println("Sum of all prime numbers : " + stats.getSum());
    System.out.println("Average of all prime numbers : " + stats.getAverage());
    

    相关文章

      网友评论

        本文标题:lambda函数式编程详解

        本文链接:https://www.haomeiwen.com/subject/xjipjctx.html