zoukankan      html  css  js  c++  java
  • 夯实Java基础(二十二)——Java8新特性之Lambda表达式

    1、前言

    Java 8于14年发布到现在已经有5年时间了,经过时间的磨练,毫无疑问,Java 8是继Java 5(发布于2004年)之后的又一个非常最重要的版本。因为Java 8里面出现了非常多新的特征,这些特征主要包含语言、编译器、库、工具和JVM等方面,具体如下:

    1. Lambda表达式         Left hug传送门Right hug
    2. 方法引用/构造器引用  Left hug传送门Right hug
    3. Stream API             Left hug传送门Right hug
    4. 新的日期处理类         Left hug传送门Right hug
    5. 函数式接口               Left hug传送门Right hug
    6. 接口中允许定义默认方法
    7. Optional类              Left hug传送门Right hug
    8. 重复注解、类型注解、通用类型推断
    9. 新的编译工具:jjs、jdeps
    10. JVM中的PermGen被Metaspace取代
    11. 新的Nashron引擎,允许在JVM上允许JS代码
    12. ……

    以上最值得我们学习的应该就是Lambda表达式、Stream API和新的日期处理类。并不是说其他的就不用去学了,还是要去了解一下的,而这三个对我们来说很重要所以必须学习。

    2、Lambda表达式简介

    Lambda表达式本质上是一个匿名函数(方法),它没有方法名,没有权限修饰符,没有返回值声明。看起来就是一个箭头(->)从左边指向右边。我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递),它的核心思想是将面向对象中的传递数据变成传递行为。Lambda表达式的出现就是为了简化匿名内部类,让匿名内部类在方法中作为参数的使用更加方便(这里个人理解,可能有误!Hot smile)。所以使用Lambda表达式可以让我们的代码更少,看上去更简洁,代码更加灵活。而Lambda表达式作为一种更紧凑的代码风格,使得Java的语言表达能力得到了提升。但也有它的缺点所在,如果Lambda表达式用的不好的话,调试运行和后期维护非常的麻烦。

    3、Lambda表达式语法

    Lambda表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符为"->",该操作符被称为Lambda操作符或箭头操作符,它将Lambda分为两个部分:

    • 左侧:指定了Lambda表达式所需要的所有参数。
    • 右侧:指定了Lambda体,即Lambda表达式所要执行的功能。

    Java8中的Lambda表达式的基本语法为:

    (params) -> expression
    (params) -> statement
    (params) -> { statements }

    上面只是基本的语法而已,所以看起来比较的简单,其中的具体使用方法有很多,如下:

    ①、无参数,无返回值 void。
    () -> System.out.print(“Lambda…”) ;

    ②、有一个参数,但无返回值 void

    (String s) -> System.out.print(“Lambda…”) ;

    ③、有参数,但是参数数据类型省略,由编译器推断,称为‘类型推断’。

    (s) –> System.out.print(“Lambda…”) ;

    ④、若只有一个参数,方法的括号可以省略,如果多个参数则必须写上

    s–> System.out.print(“Lambda…”) ;

    ⑤、有参数,且有返回值,如果显式返回语句时就必须使用花括号“{}”。

    (s,t) –> s+t ;

    (s,t) –> {return s+t;};

    ⑥、如果有两个或两个以上的参数,并且有多条语句则需要加上“{}”,一条执行语句可以省略。

    (s,t) –> {

            System.out.print(s) ;

            System.out.print(t) ;

            return s+t;

        };

    所以到目前为止,我们对Lambda表达式有了基本的认识,而前面讲了那么多的理论,就是为了接下来的快乐时光(写代码Be right backBe right back),用几个简单的例子来让我们好好理解一下Lambda表达式的使用:

    public class LambdaTest {
        public static void main(String[] args) {
            //1、创建线程举例
            //普通写法
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Ordinary Writing");
                }
            });
            thread.start();
    
            //Lambda写法。无参无返回void
            Thread thread1 = new Thread(() -> System.out.println("Lambda Writing"));
            thread1.start();
    
    
            //2、排序举例
            //普通写法,默认升序
            List<Integer> list = Arrays.asList(26, 65, 13, 79, 6, 123);
            Collections.sort(list,new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return Integer.compare(o1, o2);
                }
            });
            System.out.println(list.toString());
    
            //Lambda表达式写法。有参有返回,“类型推断”
            Collections.sort(list,(o1,o2)-> Integer.compare(o1,o2));
            System.out.println(list.toString());
    
    
            //3、遍历list集合
            //普通写法
            list.forEach(new Consumer<Integer>() {
                @Override
                public void accept(Integer integer) {
                    System.out.println(integer);
                }
            });
    
            //Lambda表达式写法。遍历List集合,forEach方法中的参数是Consumer<? super T> action
            //其中Consumer是Java8新的新出现的函数式接口,下面会讲到函数式接口
            list.forEach(alist-> System.out.println(alist));//只有一个参数可省略括号
        }
    }

    注意:要使用Lambda表达式的前提是函数式接口,所以接下来学习一下函数式接口。

    3、函数式接口

    函数式接口(Functional Interface)也是Java8中的新特征。

    函数式接口就是只能有一个抽象方法,同时可以有多个非抽象方法的接口(Java8中接口可以定义普通方法)。

    这样接口就可以被隐式转换为Lambda表达式。

    如果我们需要自定义一个函数式接口,就需要用到Java 8提供的一个特殊的注解@FunctionalInterface,该注解会检查它是否是一个函数式接口,简单的举个定义函数式接口的例子,代码如下:

    //定义函数式接口注解
    @FunctionalInterface
    public interface MyInterface {
        //抽象方法
        void method();
        //void method1();不能再定义
    
        //默认方法,必须用default修饰
        default void defaultMethod(){
            System.out.println("默认方法...");
        }
    
        //静态方法方法
        static void staticMethod(){
            System.out.println("静态方法...");
        }
    }

    上面的例子可以很容易的转换成如下Lambda表达式:

    MyInterface myInterface = () -> System.out.println("MyInterface...");

    我需要注意的一点是,接口中的默认方法和静态方法并不会破坏函数式接口的定义,既不会影响到Lambda表达式。同时也正因为Lambda表达式的引入,所以函数式接口也变得流行起来。

    其实早在Java8之前就有很多接口是函数式接口,只是在Java8才正式提出之一特性,例如:

    • java.lang.Runnable
    • java.util.concurrent.Callable
    • java.util.Comparator
    • java.io.FileFilter
    • java.awt.event.ActionListener
    • ……

    除了以上这些,在Java 8中还增加了一个新的包:java.util.function。它们里面包含了常用的函数式接口,该包下定义的函数式接口非常多,这里只列举比较重要的四个,如下:

    (博客园的表格编辑器真的无力吐槽,实在太垃圾了,然后从其他编辑器编辑好了在截图过来!Steaming madSteaming madSteaming mad)

    image

    以下是这四个函数式接口的简单举例,理解Lambda表达式之后很容易写出来:

    public class FunctionInterfaceTest {
        public static void main(String[] args) {
            //Consumer<T> : void accept(T t);
            Consumer<Integer> consumer = (a) -> System.out.println("消费型接口..."+a);
            consumer.accept(1000);
    
            //Function<T, R> : R apply(T t);
            Function<String,String> function = (b) -> b;
            Object apply = function.apply("函数型接口...");
            System.out.println(apply);
    
            //Predicate<T> : boolean test(T t);
            Predicate predicate = (c) -> c.equals(10);
            boolean test = predicate.test(10);
            System.out.println("断定型接口..."+test);
    
            //Supplier<T> : T get();
            Supplier supplier=()->(int)(Math.random() * 50);
            Object o = supplier.get();
            System.out.println("供给型接口..."+o);
        }
    }

    除了上面的这些基本的函数式接口,java.util.function包下还提供了一些针对多个参数的函数式接口,例如BiFunction<T,U,R>,它接收类型为T和U的对象,然后返回R对象。后面还有BiConsumer<T,U>、BiPredicate<T,U>等。同样还提供一些针对基本数据类型的特化函数式接口,例如XXXFunction:表示只接收XXX数据类型、XXXToXXXFunction:接收前一个XXX类型,然后返回后一个XXX类型、ToXXXFunction:表示返回值为XXX类型等等很多这样的类。(其中XXX只能是Int、Double和Long这三个基本数据类型

    如果有需要学习这些API的,可以自行去java.util.function包下查看学习,这里不多做描述。

    4、方法引用和构造器引用

    通过上面Lambda表达式的学习,如果你认为Lambda表达式已经让代码够简洁了,那么这里还有一个更加简洁的方法——方法引用。

    简单来说,方法引用就是进一步的简化Lambda表达式声明的一种语法糖。也正是因为方法引用实在太简洁了,所以学习方法引用前必须要对Lambda表达式非常的熟悉,否则学习方法引用会有点吃力Open-mouthed smile

    方法引用使用操作符 “::” 将对象或类的名字和方法名分隔开来。方法引用有很多种,它们的语法如下(注意后面是不要写括号的):

    • 静态方法引用:ClassName::staticMethodName
    • 实例上的实例方法引用:instanceName::methodName
    • 类上的实例方法引用:ClassName::methodName
    • 父类上的实例方法引用:super::methodName
    • 构造方法引用:ClassName::new
    • 数组构造方法引用:TypeName[]::new

    在使用方法引用时要注意一点:引用的函数必定与定义的接口形参和返回值类型一致

    先用System.out.println()简单举例:

        //System.out.println()简单举例
        @Test
        public void test() {
            Consumer<String> consumer = (str) -> System.out.println(str);
            consumer.accept("Lambda表达式");
    
            PrintStream out = System.out;
            Consumer consumer1 = out::println;
            consumer1.accept("方法引用");
        }
    其中Consumer中的accept(T t)方法中是一个参数,返回值是void,PrintStream中的println(Object x)也是一个参数,返回值也是void 。

    ①、静态方法引用。语法格式:ClassName::staticMethodName。

        //1、静态方法用——ClassName::staticMethodName
        @Test
        public void test1() {
            //Lambda表达式
            BiFunction<Double, Double, Double> biFunction = (x, y) -> Math.max(x, y);
            System.out.println(biFunction.apply(11.1, 22.2));
            System.out.println("-------------");
            //方法引用
            BiFunction<Double, Double, Double> biFunction1 = Math::max;
            System.out.println(biFunction1.apply(33.3, 44.4));
    
            //另外一组例子,其中c1与c2是一样的
            Comparator<Integer> c1 = (x, y) -> Integer.compare(x, y);
            Comparator<Integer> c2 = Integer::compare;
        }

    ②、实例上的实例方法引用。语法格式:instanceName::methodName。

        //2、实例上的实例方法引用——instanceName::methodName
        @Test
        public void test2(){
            Consumer<String> consumer = (str) -> System.out.println(str);
            Consumer consumer1 = System.out::println;
    
            Person person = new Person("唐浩荣", 20, "China");
            Supplier supplier = () -> person.getName();
            Supplier supplier1  = person::getName;
        }

    ③、类上的实例方法引用。语法格式:ClassName::methodName 。

        //3、类上的实例方法引用——ClassName::methodName
        public void test3(){
    
            BiPredicate<String, String> biPredicate = (x, y) -> x.equals(y);
            BiPredicate<String, String> biPredicate1 = String::equals;
    
            Function<Person, String> fun = (p) -> p.getName();
            Function<Person, String> fun2 = Person::getName;
        }

    ④、父类上的实例方法引用。语法格式:super::methodName。

        //4、父类上的实例方法引用——super::methodName
        @Test
        public void test4(){
            Person person=new Person();
            Supplier supplier = () -> super.toString();
            Supplier supplier1 =super::toString;
        }

    ⑤、构造方法引用。语法格式:ClassName::new。

        //5、构造方法引用——ClassName::new
        @Test
        public void test5() {
            Function<String, String> function = (n) -> new String(n);
            String apply = function.apply("Lambda构造方法");
            System.out.println(apply);
    
            Function<String, String> function1 = String::new;
            String apply1 = function.apply("构造方法引用");
            System.out.println(apply1);
        }

    ⑥、数组构造方法引用:TypeName[]::new。

        //6、数组构造方法引用——TypeName[]::new
        @Test
        public void test6() {
            Function<Integer, Integer[]> function = (n) -> new Integer[n];
            //Integer integer[]=new Integer[20];
            Integer[] apply = function.apply(3);
            apply[0] = 1;
            for (Integer integer : apply) {
                System.out.println(integer);
            }
            System.out.println("-----------------");
    
            Function<Integer, Integer[]> function1 = Integer[]::new;
            Integer[] apply1 = function1.apply(5);
            apply1[0] = 11;
            apply1[1] = 22;
            for (Integer integer : apply1) {
                System.out.println(integer);
            }
        }
  • 相关阅读:
    Redis 设计与实现(第三章) -- 链表adlist
    Redis 设计与实现(第二章) -- SDS
    MySQL索引背后的数据结构及算法原理(转)
    MySQL索引原理及慢查询优化(转)
    MySQL常见的一些面试题(未完待续)
    js 获取前天、昨天、今天、明天、后天的时间
    linux 下nohup 使用
    java split 分割字符串用法
    Python 获取URL访问的HEAD头信息
    MySQL日期数据类型、时间类型使用总结
  • 原文地址:https://www.cnblogs.com/tanghaorong/p/11563226.html
Copyright © 2011-2022 走看看