学习笔记11--Lambda表达式

Lambda

函数式编程思想概述

在数学中,函数就是有输入流,输出流的一套计算方案,也就是“拿数据做操作”,面向对象思想强调必须通过对象的形式来做事情,而函数式思想则尽量忽略面向对象的复杂语句,强调做什么,额不是以书面形式去做,而Lambda表达式正是函数式思想的体现

Lambda表达式的标准格式

  • 匿名内部类中重写run()方法的代码分析
    • 方法形式参数为空,说明调用方法时不需要传递参数
    • 方法返回值类型为void,说明方法执行没有结果返回
    • 方法体中的内容,是具体要做的事情
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello,world");
    }
}).start();
  • Lambda表达式的代码分析
    • ():里面没有内容,可以看成是方法形式参数为空
    • ->:用箭头指向后面要做的事情
    • {}:包含一段代码,称之为代码块,可以看做是方法体中的内容
new Thread(() -> {
    System.out.println("hello,world");
}).start();
  • 组成Lambda表达式的三要素:形式参数,箭头,代码块

  • Lambda表达式的格式

    • 格式:(形式参数) ->{代码块}
    • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
    • ->:由英文中划线和大于符号组成,固定写法,代表指向动作
    • 代码块:是具体要做的事情,也就是写的方法体内容
  • Lambda表达式的使用前提

    • 有一个接口
    • 接口中有且仅有一个抽象方法
  • Lambda表达式的省略模式

    • 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
    usePrint((s) ->{
        System.out.println(s);
    });
    
    • 如果参数有且仅有一个,那么小括号可以省略
    usePrint(s ->{
        System.out.println(s);
    });
    
    • 如果代码块的语句只有一条,则可以省略大括号和分号,甚至是return
    usePrint(s -> System.out.println(s));
    
  • Lambda表达式的注意事项

    • 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
    • 必须有上下文环境,才能推导出Lambda对应的接口
      • 根据局部变量的赋值的值Lambda对应的接口:Runnable r = () -> System.out.println(“Lambda表达式”);
      • 根据调用方法的参数的值Lambda对应的接口:new Thread(() -> System.out.println(“Lambda表达式”)).start();
  • Lambda表达式和匿名内部类的区别

    • 所需类型不同
      • 匿名内部类:可以使接口,也可以是抽象类,还可以是具体类
      • Lambda表达式:只能是接口
    • 使用权限不同
      • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
      • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
    • 实现原理不同
      • 匿名内部类:编译之后,产生一个单独的.class字节码文件
      • Lambda表达式:编译之后,没有一个单独的.class。对应的字节码会在运行时动态生成

接口组成更新

  • 接口的组成

    • 常量:public static final
    • 抽象方法:public abstract
    • 默认方法(Java 8)
    • 静态方法(Java 8)
    • 私有方法(Java 9)
  • 接口中的默认方法

    • 格式:public default 返回值类型 方法名(参数列表){ }
  • 接口中默认方法的注意事项

    • 默认方法不是抽象方法,所以不强制被重写,但是也可以被重写,重写时候去掉default关键字
    • public可以省略,default不能省略
  • 接口中的静态方法

    • 格式:public static 返回值类型 方法名(参数列表){ }
  • 接口中静态方法的注意事项

    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略
  • 接口中的私有方法

    当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有隐藏起来

  • 私有方法的定义格式

    • 格式1:private 返回值类型 方法名(参数列表){ }
    • 格式2:private static 返回值类型 方法名(参数列表){ }
  • 接口中私有方法的注意事项

    • 默认方法可以调用私有的静态方法和非静态方法
    • 静态方法只能调用私有的静态方法
    • 该方法是在Java 9 中新增的功能

方法引用

  • 方法引用符 :: 该方法为引用运算符,而它所在的表达式被称为方法引用

  • Lambda表达式:usePrintable(System.out.println(s));

    • 分析:拿到参数s之后通过Lambda表达式,传递给System.out.println方法去处理
  • 方法引用:usePrintable(System.out::println);

    • 分析:直接使用System.out中的println方法来取代Lambda,代码更加简洁
  • 常见的引用方式

    • 引用类方法
    • 引用对象的实例方法
    • 引用类的实例方法
    • 应用构造器
  • 引用类方法

    • 引用类方法,其实就是引用类的静态方法
    • 格式:类名::静态方法
    • 范例:Integer::parseInt
    • Lambda表达式被类方法替代时,它的形式参数全部传递给静态方法作为参数
  • 引用对象的实例方法

    • 引用对象的实例方法,其实就是引用类中的成员方法
    • 格式:对象::成员方法
    • 范例:“HelloWorld”::toUpperCase
    • Lambda表达式被对象的实例方法替代时,它的形式参数全部传递给该方法作为参数
  • 引用类的实例方法

    • 引用类的实例方法,其实就是引用类中的成员方法
    • 格式:类名::成员方法
    • 范例:String::substring
    • Lambda表达式被类的实例方法替代时,第一个参数作为调用者,后面的参数全部传递给该方法作为参数
  • 引用构造器

    • 引用构造器,其实就是引用构造方法
    • 格式:类名::new
    • 范例:Student::new
    • Lambda表达式被构造器替代时,它的形式参数全部传递给构造器作为参数

函数式接口

  • 函数式接口:有且仅一个抽象方法的接口

    • Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda使用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导

    • 将@FunctionalInterface放在接口定义的上方,如果接口是函数式接口,编译通过,如果不是,编译失败

    • 我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算不写这个注释,只要保证满足函数式接口定义的条件,也照样是函数式接口,建议加上该注释

  • 函数式接口作为方法的参数

    • 如果方法的参数是一个函数式接口,我们可以使用Lambda表达式作为参数传递
    • startThread(()-> System.out.println(Thread.currentThread().getName() + “线程启动了”));
  • 函数式接口作为方法的返回值

    • 如果方法的返回值是一个函数接口,我们可以使用Lambda表达式作为结果返回

    • private static Comparator getComparator(){

      ​ return (s1,s2) -> s1.length() - s2.length();

      }

常用的函数式接口

  • Supplier:包含一个无参的方法
    • T get():获得结果
    • 该方法不需要参数,他会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
    • Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
  • Consumer:包含两个方法
    • void accept(T t):对给定的参数执行此操作
    • default Consumer andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行then操作
    • Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
  • Predicate:常用的四个方法
    • boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
    • default Predicate negate():返回一个逻辑的否定,对应逻辑非
    • default Predicate and(Predicate other):返回一个组合判断,对应短路与
    • default Predicate or(Predicate other):返回一个组合判断,对应短路或
    • Predicate接口通常用于判断参数是否满足指定的条件
  • Function:常用的两个方法
    • R apply(T t):将此函数应用于给定的参数
    • default Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
    • Function接口通常用于对参数进行处理,转换(逻辑处理由Lambda表达式实现),然后返回一个新的值

Stream流

  • 使用Stream流的方式完成过滤操作

    • 例如:list.stream().filter(s -> s.startsWith(“张”)).filter(s -> s.length() == 3).forEach(System.out::println);
    • 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:生成流、过滤姓张、过滤长度为3、逐一打印
    • Stream流把真正的函数式编程风格引入到Java中
  • Stream流的使用

    • 生成流
      • 通过数据源(集合,数组等)生成流
      • list.stream()
    • 中间操作
      • 一个流后面可以跟随零个或多个中间操作,其主要目的是打开流,作出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用
      • filter()
    • 终结操作
      • 一个流只能有一个终结操作,当这个操作执行后,流就被使用光了,无法在被操作。所以这必定是最后一个操作
      • forEach()
  • Stream流的常见生成方式

    • Collection体系的集合可以使用默认方法stream()生成流
      • default Stream stream()
    • Map体系的集合间接的生成流
    • 数组可以通过Stream接口的静态方法of(T…values)生成流
public static void main(String[] args) {
    //Collection体系的集合可以使用默认方法stream()生成流
    List<String> list = new ArrayList<String>();
    Stream<String> listStream = list.stream();

    Set<String> set = new HashSet<String>();
    Stream<String> setStream = set.stream();

    //Map体系的集合间接的生成流
    Map<String,Integer> map = new HashMap<String, Integer>();
    Stream<String> keyStream = map.keySet().stream();
    Stream<Integer> valueStream = map.values().stream();
    Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

    //数组可以通过Stream接口的静态方法of(T...values)生成流
    String[] strArray = {"hello","world","java"};
    Stream<String> strArrayStream = Stream.of(strArray);
    Stream<String> strArrayStream2 = Stream.of("hello","world","java");
    Stream<Integer> intStream = Stream.of(10,20,30);
}
  • Stream流的常见中间操作方法
    • Stream filter(Predicate predicate):用于对流中的数据进行过滤
    • Stream limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
    • Stream skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
    • static Stream concat(Stream a,Stream b):合并a和b两个流为一个流
    • Stream distinct():返回由该流的不同元素(根据Object.equals(Object))组成的流
    • Stream sorted():返回由此流的元素组成的流,根据自然顺序排序
    • Stream sorted(Comparator comparator):返回由此流的元素组成的流,根据提供的Comparator排序
    • Stream map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
    • IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<String>();
    list.add("星际争霸");
    list.add("星球大战");
    list.add("炉石");
    list.add("魔兽");
    list.add("lol");
    list.add("风暴要火");
    list.add("荒野大嫖客");

    //需求1:把长度为 4的数据输出到控制台
    list.stream().filter(s -> s.length() == 4).forEach(System.out::println);
    System.out.println("=================");
    //需求2:把前三个数据输出到控制台
    list.stream().limit(3).forEach(System.out::println);
    System.out.println("=================");
    //需求3:跳过三个元素,把剩下的在控制台输出
    list.stream().skip(3).forEach(System.out::println);
    System.out.println("=================");
    //需求4:把需求 1和需求 2得到的的数据合并
    Stream<String> s1 = list.stream().filter(s -> s.length() == 4);
    Stream<String> s2 = list.stream().limit(3);
    Stream.concat(s1, s2).forEach(System.out::println);
    System.out.println("=================");
    //需求4:把需求 1和需求 2得到的的数据合并,要求结果不能重复
    Stream<String> s3 = list.stream().filter(s -> s.length() == 4);
    Stream<String> s4 = list.stream().limit(3);
    Stream.concat(s3, s4).distinct().forEach(System.out::println);
    System.out.println("=================");
    //需求5:按照字符串长度进行排序
    list.stream().sorted((ss1, ss2) -> ss1.length() - ss2.length()).forEach(System.out::println);
    System.out.println("=================");

    ArrayList<String> list2 = new ArrayList<String>();
    list2.add("10");
    list2.add("20");
    list2.add("30");
    list2.add("40");
    list2.add("50");

    //需求6:将集合中的字符串转换为整数之后在控制台输出
    list2.stream().map(Integer::parseInt).forEach(System.out::println);
    System.out.println("=================");
    //需求7:将集合中的字符串转换为整数之后求和并在控制台输出结果
    int sum = list2.stream().mapToInt(Integer::parseInt).sum();
    System.out.println(sum);
}
  • Stream流的常见终结操作方法
    • void forEach(Consumer action):对此流的每个元素执行操作
    • long count():返回此流中的元素数
public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<String>();
    list.add("张三四");
    list.add("张九六");
    list.add("王五五");
    list.add("赵七八");
    list.add("孙二三");
    list.add("张四七");
    list.add("刘八二");

    //需求1:把集合中的每个元素在控制台输出
    list.stream().forEach(System.out::println);

    //需求2:统计集合中有几个姓张的元素,并把统计结果输出在控制台
    long count = list.stream().filter(s -> s.startsWith("张")).count();
    System.out.println(count);
}
  • Stream流的收集操作
    • R collect(Collector collector)
    • 但是这个收集方法的参数是一个Collector接口
    • 工具类Collectors提供了具体的收集方式
      • public static Collector toList():把元素收集到List集合中
      • public static Collector toSet():把元素收集到Set集合中
      • public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
public static void main(String[] args) {
    //创建list集合对象
    List<String> list = new ArrayList<String>();
    list.add("周星驰");
    list.add("成龙");
    list.add("彭于晏");
    list.add("吴彦祖");

    //需求1:得到名字为3个字的流
    Stream<String> listStream = list.stream().filter(s -> s.length() == 3);
    //需求2:使用Stream流操作完毕的数据收集到list集合中并遍历
    List<String> name = listStream.collect(Collectors.toList());
    for (String s : name) {
        System.out.println(s);
    }

    //创建set集合对象
    Set<Integer> set = new HashSet<Integer>();
    set.add(20);
    set.add(25);
    set.add(30);
    set.add(35);
    set.add(40);

    //需求3:得到年龄大于25的流
    Stream<Integer> setStream = set.stream().filter(s -> s > 25);
    //需求4:使用Stream流操作完毕的数据收集到Set集合中并遍历
    Set<Integer> age = setStream.collect(Collectors.toSet());
    for (int i : age) {
        System.out.println(i);
    }
    System.out.println("=============");
    //定义一个字符串数组,每一个字符串数据由姓名和年龄组成
    String[] str = {"周星驰,20", "成龙,25", "彭于晏,30", "吴彦祖,40"};
    //需求5:得到字符串中年龄数据大于25的流
    Stream<String> stringStream = Stream.of(str).filter(s -> Integer.parseInt(s.split(",")[1]) > 25);
    //需求6:使用Stream流操作完毕的数据收集到Map集合中并遍历
    Map<String, Integer> map = stringStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));

    Set<String> keySet = map.keySet();
    for (String s : keySet) {
        Integer value = map.get(s);
        System.out.println(s + "," + value);
    }
}

你可能感兴趣的:(学习,java,开发语言)