美文网首页
Lambda表达式的使用

Lambda表达式的使用

作者: 达文西_阿泽 | 来源:发表于2022-04-28 18:53 被阅读0次

    虽然从java8就开始支持lambda特性,如今java更是已经升级了11等更高级的版本,但是本人一直使用的还是java8以下的语法,对于lambda语法更是甚少了解,刚开始接触这个lambda还是十分不习惯的,感觉有点绕;怎么看怎么不顺眼,在我看来,lambada特性单纯只是为了简化java代码的书写的,所以实质上并没有什么用,甚至自己一度觉得很讨厌,但作为Android开发者,为了更好的学习kotlin,也只能迎着头皮接受这个自己讨厌的东西;(获取将来某一天会觉得他很香吧)开始学习java 8的lambda表达式。以下是针对一些教程以及本身对lambda的一些理解所作的作结,写的不好,还请大家指正;
    学习的时候参考了很多资料,学着学着就越来越懵逼了,可能是看的资料太多反倒产生了影响。学完又忘记了。额,感觉自己在写日记了。丢~~~

    lambda表达式是什么?有什么作用?什么时候可以使用lambda表达式?

    按照官方说法:lambda表达式就是一个匿名函数,也就是没有函数名的函数。什么叫没有函数名?

    //定义接口
    public interface IUser {
        String getUserName(String name);
    }
    
        public void test() {
            //正常情况下的接口实现方法
            IUser user = new IUser() {
                @Override
                public String getUserName(String name) {
                    return name;
                }
            };
            system.out.print(user.getUserName("达文西"))
        }
    

    上面的new IUser{...}就是一个匿名函数,所谓匿名,就是我完全不需要知道你的名字,我也不关心,我真正关心的只是你的内容,你的实现方法;故此,如果按照以往的写法,如果每一个匿名函数我都需要new xxx那这些没用的代码就现的很多余了,而用lambda表达式我便可以省略这些没有用的内容,就一句IUser user=name->name,简单!!!。所以简单来说lambda表示是为了简化java书写冗余的。如果你觉得没有必要,那是真的就没有必要-_-!!。
    那什么情况下才能使用lambda表达式呢?那就是只有当你的接口符合函数式接口的时候?什么是函数式接口?只有一个抽象方法的接口就是函数式接口。
    上面的 IUser接口便是函数式接口;
    再举个例子

    @FunctionalInterface
    public interface Comparator<T>{
      ...
      int compare(T o1,T o2);
    }
    

    其中的@FunctionalInterface是用来修饰函数式接口的,也就是说只要有这个修饰符,你的接口就必须有且仅有一个接口。如果你用了这个修饰符,还没有来得及写方法,那他会报No target method found的错误,如果你的抽象方法不止一个,那就会报Multiple non-overriding abstract methods found in interface xxx

    lambda表达式语法

    (参数)->{实现方法}
    
    小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
    -> 是新引入的语法格式,是运算符,代表指向动作。
    大括号内的语法与传统方法体要求基本一致。
    

    有时候()和{}甚至可以省略,比如上面提到的IUser user=name->name接口的实现;

            //完整的lambda的语法
            IUser user0 = (String name) -> {
                return name;
            };
            //省略{}
            IUser user2 = (String uname) -> uname;
            //省略()和{}
            IUser user = name -> null;
            
    

    总结表达式一些总结:

    1. 当方法体只有一行的时候,花括号{}可以省略;
    2. 当接口方法有返回值,且方法体只有一行的时候,省略花括号{}的同时可省略return,直接写返回的表达式/值即可,单行代码的执行结果会自动返回;
    3. 当无参数的时候,()不可以省略;
    4. 当参数只有一个的时候,()可以省略;
    5. 当参数的类型确定的时候,可以省略参数的类型;jvm在运行时,会自动根据绑定的抽象方法中的参数进行推导;
      //通常使用
       Supplier<String> sup1 = new Supplier<String>() {
              @Override
              public String get() {
                  return "达文西";
              }
          };
      //lambda表达式
      Supplier<String> sup2 = ()-> "达文西";
    

    JAVA内置的4大核心函数式接口

    • 消费型接口 ConSumer void accept(T t)
      典型的代表就是list集合的使用
    private static void test4() {
            String[] strs = new String[]{"1", "2", "3", "4", "5"};
            List<String> list = Arrays.asList(strs);
            //正常情况遍历
            for (String str : list) {
                System.out.print(str + ";");
            }
            //使用lambda表达式进行遍历,其中forEach方法接收的就是一个消费型接口;
            list.forEach(str -> System.out.println(str + ";"));
        }
    
    • 供给型接口 Supplier T get()
    • 函数型接口 Function<T,R> R apply(T t)
        public static void test5() {
            //传统方式
            Runnable run1 = new Runnable() {
                @Override
                public void run() {
                    System.out.println("传统方式的Runnable");
                }
            };
            Thread t1 = new Thread(run1);
            t1.start();
    
            //使用lambda表达式
            Thread t2 = new Thread(() -> System.out.println("----------lambda方式----------------"));
            t2.start();
        }
    
    • 断定型接口 Predicate boolean test(T t)

    下面展示的是Java类型系统内部的函数式接口

        public static void test6() {
            /* Java8中的java.util.function包中提供了一些常用的函数式功能接口*/
            // 1.java.util.function.Predicate<T>
            //接受参数T,返回一个Boolean类型的结果
            Predicate<String> predicate = String::isEmpty;
            boolean flag = predicate.test("");
            System.out.println(flag);
    
            // 2.java.util.function.Consumer<T>
            // 接收一个参数T,不返回结果
            Consumer<String> consumer = System.out::println;
            consumer.accept("哈哈哈");
    
            // 3.java.util.function.Function<T,R>
            // 接收参数对象T,返回结果对象R
            Function<String, Integer> function = (String userName) -> {
                return userName.equals("admin") ? 1 : 0;
            };
            int funflag = function.apply("admin");
    
            // 4.java.util.function.Supplier<T>
            // 不接收参数,返回T对象创建的工厂
            Supplier<String> supplier = () -> UUID.randomUUID().toString();
            String s = supplier.get();
    
            // 5.java.util.UnaryOperator<T>
            // 接收参数对象T,返回结果参数对象T
            UnaryOperator<String> operator = (String userName) -> "Hello " + userName;
        
            //6.java.util.BinaryOperator<T>
            // 接收两个T对象,返回一个T对象结果
            BinaryOperator<String> binary = (String userName, String userId) -> {
                return userName + ":" + userId;
            };
        }
    

    静态方法的引用
    这个怎么解释呢?我的理解就是,使用一个静态的方法去替代你需要实现的接口方法,当然前提是这个静态方法必须跟你的接口方法是一样的(即请求参数以及返回值一致)
    例如:

    //工具类Utils中含有一个静态类
      public class Utils {
        public static final int compar(Integer p1, Integer p2) {
            return p1 - p2;
        }
      }
    
    //调用
     List<Integer> list = Arrays.asList(33, 44, 55, 33, 22);
     Collections.sort(list, Utils::compar2);
    

    其实就相当于你在你的实现类中,调用了外部的静态方法类,且把参数传递过去之后获取到返回值进行返回(即没有对参数进行任何加工就给第三方去处理了。外包出去了....):

          
            Collections.sort(list, new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return Utils.compar(o1, o2);
                }
            });
    

    上述的调用方法有一个专业的名字叫【方法应用】,直接使用符号 :: (两个冒号)。
    除了上述的静态方法的引用之外,还有以下常见的几种引用方式:

        1. 对象引用成员方法
              对象名::方法名
        2. 类名引用静态方法
              类名::静态方法
        3. 类的构造器引用
            类的构造器(类似于构造方法)引用要在创建对象的时候使用。
             类名::new
        4. 数组的构造器引用
             数组的构造器引用在创建数组的时候使用。
             数据类型[]::new
    
    

    Stream概念的引入(要去做核酸了,今天先到这里)

    在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
    让我先来看两个例子:

        public static void main(String[] args) {
            List<String> list1 = new ArrayList<>();
            list1.add("黄毛");list1.add("黄文名");list1.add("黄文雄");list1.add("黄文离");list1.add("黄文金");list1.add("叶小平");list1.add("黄狗");
            System.out.println("------------方式1-------------");
            List<String> list2 = new ArrayList<>();
            list1.forEach(name -> {
                if (name.startsWith("黄")) {
                    list2.add(name);
                }
            });
            ArrayList<String> list3 = new ArrayList<>();
            list2.forEach(name -> {
                if (name.length() == 3) {
                    list3.add(name);
                }
            });
            list3.forEach(name -> {
                System.out.println("符合条件的姓名:" + name);
            });
            System.out.println("------------方式2-------------");
            list1.stream().filter(name -> name.startsWith("黄")).filter(name -> name.length() == 3).forEach(name -> System.out.println("符合条件的姓名:" + name));
        }
    

    乍一看方式1跟方式2的结果是一样的,都是想要遍历出带有三个字的姓黄的名字。但是很显然第2种方式更为简洁,就一句话到底。这就是流式思想.

    获取流的方式
    java.util.stream.Stream 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)在 Java 8 中, 集合接口有两个方法来生成流: ① stream() − 为集合创建串行流。 ② parallelStream() − 为集合创建并行流。

    1. 通过Collection(单列)集合获取流
      Stream stream(): 获取一个流对象
    2. 通过Map(双列)集合获取流
      通过Map集合获取流(了解),Map集合不能直接获取流对象, 只能间接获取, 有三种间接获取方式。
      ①. 先获取Map集合中的所有的key, 然后获取所有key的stream流
      ②. 先获取Map集合中的所有的value,然后获取所有value的stream流
      ③. 先获取Map集合中的所有的Entry(键值对), 获取所有entry的stream流。
    3. 通过数组获取流
      方式一:使用Stream的静态方法of完成(推荐, 因为参数不仅可以传递数组, 也可以传递任意个数据)
      static Stream of(T… values): 根据一个数组获取一个stream流
      方式二: 使用Arrays的静态方法stream完成
      static Stream stream(T[] array) : 根据一个数组获取对应的stream流

    流式布局常见用法

    方法名称 方法作用 方法种类 是否支持链式调用
    forEach 逐一处理 终结方法 NO
    count 获取个数 终结方法 NO
    filter 过滤 非终结方法 YES
    limit 获取前几个 非终结方法 YES
    skip 跳过前几个 非终结方法 YES
    concat 合并 非终结方法 YES
    map 映射 非终结方法 YES

    Stream中用于收集的方法
    1. 收集到集合中:
    R collect(Collector collector):可以将流中的数据收集到集合。 参数Collector表示收集到哪种集合。Collector是一个接口,如果要传递需要传递Collector实现类对象, 这个实现类对象不能由我们自己去new,要通过Collectors工具类的静态方法进行获取, 使用不同方法获取的Collector,表示收集到了不同的集合中。
    static Collector toList():如果使用toList获取的Collector对象,表示将数据收集到List集合中。
    static Collector toSet()::如果使用toSet获取的Collector对象,表示将数据收集到Set集合中。

    2. stream收集到数组
    我们可以使用Stream中的toArray方法进行收集
    Object[] toArray(): 将流中的数据收集到数组, 返回Object[]

    注意:
    1. stream流不会改变源对象.Stream流中的非终结方法返回的都是Stream对象, 返回的Stream是一个新的Stream
    2. Stream流只能一次性使用,不能多次操作,否则会报错。
    3. stream流本身不保存任何元素.便于理解可以把流看成容器,但是它不是容器.流本身不保存任何元素,他保存的是一些的中间操作步骤.
    4. stream流操作是延迟执行的.当调用终结方法时,会按顺序将操作步骤执行.

    参考资料:

    1. https://www.runoob.com/java/java8-lambda-expressions.html
    2. https://blog.csdn.net/qq_29660549/article/details/106564964
    3. https://blog.csdn.net/weixin_45735355/article/details/119911204
    4. https://blog.csdn.net/fengkuagn123/article/details/106315317
    5. 关于stream的用法
    6. https://blog.csdn.net/weixin_45590174/article/details/103542176

    相关文章

      网友评论

          本文标题:Lambda表达式的使用

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