• java lambda表达式学习笔记


    lambda是函数式编程(FP,functional program),在java8中引入,而C#很早之前就有了。在java中lambda表达式是'->',在C#中是‘=>’。

    杜甫说:射人先射马,擒贼先擒王。学习一个库要学习它的入口类。lambda的入口类是Stream,一看Stream中的函数就会发现Function,Predicate等lambda元素。

    一.几个概念

        函数式接口 Functional Interface,除了static和default类型的方法外,只有一个函数的接口。以前,接口中的一切方法都是public的,现在接口中可以包含default类型的实现方法了。java中没有函数指针的概念,C#中有delegate委托相当于函数指针,但java也是有办法的,用一个类,类里面有一个函数,这个类就相当于函数指针。这么整实现简单,理解简单,但是代码比较冗长。

      谓词 Predicate, 简单来说,谓词就是条件。正规来说,谓词就是一个函数boolean f(x1,x2...),表示变量x1,x2...是否满足条件f。在java中谓词的定义就是一个函数式接口。

        函数(映射) Function,将一种类型的对象映射为另一种或同种类型的对象,它就是一个函数ObjectA f(ObjectB)。在java中映射的定义也是一个函数式接口。

    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        } 
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        } 
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }

    可见,除了apply()函数以外,其余default方法在外部都是不可见的。所以定义函数式接口的时候需要把其他函数声明称static或者default类型的。

    Optional这个值表示一个对象,这个对象可能为空也可能不为空,对于它可以产生许多行为:

    如果它是null,该怎么做orElse()和orElseGet()

    如果不为null,该怎么做ifPresent()

    判断是否为null,isPresent()

    这个类看上去十分鸡肋,但用处十分广泛

    package aaa;
    
    import java.util.NoSuchElementException;
    import java.util.Optional;
    
    public class OptionalDemo {
         
          public static void main(String[] args) {
            //创建Optional实例,也可以通过方法返回值得到。
            Optional<String> name = Optional.of("Sanaulla");
         
            //创建没有值的Optional实例,例如值为'null'
            Optional<Object> empty = Optional.ofNullable(null);
         
            //isPresent方法用来检查Optional实例是否有值。
            if (name.isPresent()) {
              //调用get()返回Optional值。
              System.out.println(name.get());
            }
         
            try {
              //在Optional实例上调用get()抛出NoSuchElementException。
              System.out.println(empty.get());
            } catch (NoSuchElementException ex) {
              System.out.println(ex.getMessage());
            }
         
            //ifPresent方法接受lambda表达式参数。
            //如果Optional值不为空,lambda表达式会处理并在其上执行操作。
            name.ifPresent((value) -> {
              System.out.println("The length of the value is: " + value.length());
            });
         
            //如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
            System.out.println(empty.orElse("There is no value present!"));
            System.out.println(name.orElse("There is some value!"));
         
            //orElseGet与orElse类似,区别在于传入的默认值。
            //orElseGet接受lambda表达式生成默认值。
            System.out.println(empty.orElseGet(() -> "Default Value"));
            System.out.println(name.orElseGet(() -> "Default Value"));
         
            try {
              //orElseThrow与orElse方法类似,区别在于返回值。
              //orElseThrow抛出由传入的lambda表达式/方法生成异常。
              empty.orElseThrow(Exception::new);
            } catch (Throwable ex) {
              System.out.println(ex.getMessage());
            }
         
            //map方法通过传入的lambda表达式修改Optonal实例默认值。 
            //lambda表达式返回值会包装为Optional实例。
            Optional<String> upperName = name.map((value) -> value.toUpperCase());
            System.out.println(upperName.orElse("No value found"));
         
            //flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
            //map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
            //但是flatMap方法的lambda返回值总是Optional类型。
            upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
            System.out.println(upperName.orElse("No value found"));
         
            //filter方法检查Optiona值是否满足给定条件。
            //如果满足返回Optional实例值,否则返回空Optional。
            Optional<String> longName = name.filter((value) -> value.length() > 6);
            System.out.println(longName.orElse("The name is less than 6 characters"));
         
            //另一个示例,Optional值不满足给定条件。
            Optional<String> anotherName = Optional.of("Sana");
            Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
            System.out.println(shortName.orElse("The name is less than 6 characters"));
         
          }
         
        }
    View Code

    二.lambda表达式

    有两个作用

    * 作为函数指针

    * 替代匿名内部类,替代函数式接口(FunctionalInterface)

    lambda不是语法糖,它在内部实现上也跟匿名内部类不同,匿名内部类需要进行类文件加载,而lambda表达式不用,所以lambda表达式效率比匿名内部类高。

    三种用法

    用'(x1,x2,x3)'表示传入参数

    如果无参写作’()‘

    参数类型可以指明,也可以不指明,java会根据后半部分函数形参自动推断出来。

            List<String> a = Arrays.asList("we i di ao is great".split(" "));
            a.forEach((s) -> System.out.println(s));// 表达式
            a.forEach((String s) -> {
                System.out.println(s);
            });// 语句块
            a.forEach(System.out::println);// 函数

    方法引用

    object::fun()

    className::fun()静态方法引用

    className::new  构造函数引用

    public class LambdaIntro {
        // functional interface 函数式接口
        public static interface ItemWithIndexVisitor<E> {
            public void visit(E item, int index);
        }
    
        public static <E> void eachWithIndex(List<E> list,
                ItemWithIndexVisitor<E> visitor) {
            for (int i = 0; i < list.size(); i++) {
                visitor.visit(list.get(i), i);
            }
        }
        // 一个普通函数,用作函数指针
        public static <E> void printItem(E value, int index) {
            String output = String.format("%d -> %s", index, value.toString());
            System.out.println(output);
        }
        public static void main(String[] args) {
            List<String> list = Arrays.asList("A", "B", "C");
            // 第一种方式
            eachWithIndex(list, (value, index) -> {
                String output = String.format("%d -> %s", index, value);
                System.out.println(output);
            });
            // 第二种方式
            eachWithIndex(list, LambdaIntro::printItem);
        }
    }

    三.使用Stream

    创建Stream的两种方式

    * Stream接口的工厂方法

    * 集合框架的stream()函数

    首先来了解使用Stream接口来创建Stream,可以创建三种流:普通枚举流,产生器,迭代器。

            //of:通过枚举方式创建流
            Stream<Integer> one = Stream.of(1, 2, 3);
            //流是可以拼接的,从而产生新流
            Stream<Integer> two = Stream.concat(one, Stream.of(4, 5, 6));
            two.forEach(System.out::println);
            //逐个加入,那就用Builder构建器来实现
            Builder<Integer> builder = Stream.builder();
            Stream<Integer> three = builder.add(3).add(4).build();
            three.forEach((s) -> System.out.println(s));
            //产生器流generator
            Random random = new Random();
            Stream<Integer> four = Stream.generate(() -> random.nextInt());
            four.limit(10).forEach(System.out::println);
            //迭代器iterator,UnaryOperator一元运算符可以通过lambda表达式来创建
            Stream<Integer>five=Stream.iterate(2, new UnaryOperator<Integer>() {
                @Override
                public Integer apply(Integer t) {
                    return t = (t * 5 + 7) % 13;
                }
            });
            five.limit(10).forEach(System.out::println);

    注意产生器generator和迭代器iterator是无限输出的,可以用limit来约束之。

    集合框架都继承了Collection接口,而Collection接口就有一个stream()函数。所以剩下的任务就是如何利用流的强大特性来写出优雅的代码来。

    要想深刻的了解Stream的一些函数,那就先不要使用lambda表达式,一旦了解它的普通实现,很容易改写成lambda的形式。

    流Stream中的函数明显分为两类,一类返回值还是Stream,可以继续用流来处理,另一类返回值不是Stream,不能再用流中函数处理了。

    Stream filter(Predicate)删除掉流中不满足条件的元素并返回新的Stream

    Stream map(Function)映射,把流中的元素映射一下变成一个新流,还有mapToInt(),mapToLong(),mapToDouble()等函数,它们终究还是映射,只是映射结果更单一。map是一对一映射,flatMap是一对多映射。把一个元素映射成多个元素

            Arrays.asList(1, 2, 3).stream()
                    .flatMap(new Function<Integer, Stream<Integer>>() {
                        @Override
                        public Stream<Integer> apply(Integer t) {
                            return Arrays.asList(t, t + 10, t + 100).stream();
                        }
                    }).forEach(System.out::println);

    输出为1 11 101 2 12 102 3 13 103   中间我省略了换行符 

    distinct()去除流中重复元素

    sorted()和sorted(Comparator cmp)对流中元素排序

    peek()弹出一个元素

            Stream.of("one", "two", "three", "four").filter(e -> e.length() > 3)
                    .peek(e -> System.out.println("Filtered value: " + e))
                    .map(String::toUpperCase)
                    .peek(e -> System.out.println("Mapped value: " + e))
                    .collect(Collectors.toList());

    执行结果

    Filtered value: three
    Mapped value: THREE
    Filtered value: four
    Mapped value: FOUR

    limit(int cnt)只返回cnt个元素,skip(int cnt)跳过cnt个元素。

    forEach(Consumer consumer)对于每一个元素都执行某种操作

    reduce()将多个值映射为一个值,实现多对一映射

             Stream.of("one", "two", "three", "four")
                    .reduce(new BinaryOperator<String>() {
                        @Override
                        public String apply(String t, String u) {
                            System.out.println(t+":"+u);
                            return t + "," + u;
                        }
                    }).ifPresent(System.out::println);

    输出为

    one:two
    one,two:three
    one,two,three:four
    one,two,three,four

    可见,apply(t,u)函数中的t表示当前总量,u表示当前元素。

    reduce(T identity,BinaryOperator<T>f)表示带初始值的reduce,比如求和函数,如果identity=9,表示一开始sum=9,此函数返回具体的对象。

            String s = Stream.of("one", "two", "three", "four").reduce("baga",
                    new BinaryOperator<String>() {
    
                        @Override
                        public String apply(String t, String u) {
                            return t +","+ u;
                        }
                    });
            System.out.println(s);

    输出:

    baga,one,two,three,four

    collect()

    Map<String, Map<String, List<Person>>> peopleByStateAndCity
             = personStream.collect(Collectors.groupingBy(Person::getState,
                                                          Collectors.groupingBy(Person::getCity)));
            String s=Stream.of("one", "two", "three", "four")
                    .collect(Collectors.joining(","));
            System.out.println(s);

    输出one,two,three,four

    Collectors包含许多有用的静态方法

    聚集函数min(),max(),count()很像sql中的聚集函数

    匹配函数allMatch(Predicate p),anyMatch(Predicate p),noneMath(Predicate p)流中全部匹配,部分匹配,完全不匹配,返回布尔值 

  • 相关阅读:
    18 | 案例篇:内存泄漏了,我该如何定位和处理?
    17 | 案例篇:如何利用系统缓存优化程序的运行效率?
    16 | 基础篇:怎么理解内存中的Buffer和Cache?
    Scrapyd 改进第一步: Web Interface 添加 charset=UTF-8, 避免查看 log 出现中文乱码
    scrapy_redis 相关: 将 jobdir 保存的爬虫进度转移到 Redis
    lxml.etree.HTML(text) 解析HTML文档
    CSS/Xpath 选择器 第几个子节点/父节点/兄弟节点
    scrapy_redis 相关: 查看保存的数据
    scrapy 通过FormRequest模拟登录再继续
    python2 python3 转换,兼容
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/5468967.html
Copyright © 2020-2023  润新知