JDK8新特性 - Lambda表达式

目录

1.Lambda表达式简介

    1.1什么是Lambda表达式

    1.2 Lambda表达式对接口的要求

2.函数式接口

    2.1 函数式接口基础概念

    2.2 @FunctionalInterface

3.Lambda表达式语法

    3.1 Lambda表达式基础语法

    3.2 Lambda表达式语法进阶

            3.2.1 参数部分精简

            3.2.2 方法体部分精简

4.Lambda表达式函数引用

    4.1 函数引用

    4.2 静态方法引用

    4.3 非静态方法引用

    4.4 构造方法引用

    4.5 对象方法特殊引用

5.使用Lambda表达式需要注意的一个问题


1.Lambda表达式简介

    1.1什么是Lambda表达式

       Lambda表达式从本质上是一个匿名函数,作用为可以对接口中的方法进行非常简洁的实现,从而达到简化代码的目的。来个Lambda表达式最简单的应用,遍历集合我们可以用for循环遍历:

for (int j = 0; j < list.size(); j++) {
    System.out.print(list.get(j));
}

       也可以用迭代器遍历:

Iterator<Object> iterator = list.iterator();
    while(iterator.hasNext()){
        int j=(int)iterator.next();
        System.out.print(j);
}

       还可以用Lambda表达式的写法:

list.forEach(s -> System.out.print(s));

       再简化一下甚至可以写成这样:

list.forEach(System.out::print);

       笔者第一次看的时候是很懵,这是个啥,C++?别急,看完本篇博文相信小伙伴们都会体验到Lambda表达式其实很好理解的。

    1.2 Lambda表达式对接口的要求

       虽然Lambda表达式可以简化接口的实现,但并不是对所有的接口都适用,因为其毕竟只是一个匿名方法,当实现的接口中方法数量大于1(无法在一个方法体内实现两个方法)或者小于1(方法都没,我该去实现谁),所以Lambda表达式只能实现函数式接口

2.函数式接口

    2.1 函数式接口基础概念

      一个接口中,要求实现类实现的抽象方法有且只有一个,这样的接口称为函数式接口,上Demo:

//接口中有且只有一个实现类必须实现的方法,是函数式接口
interface test1{
    void test();
}
//接口中有两个实现类必须实现的方法,非函数式接口
interface test2{
    void test1();
    void test2();
}
//接口中没有实现类必须实现的方法,非函数式接口
interface test3{

}
//接口中虽然没有方法,但可以从父接口继承到一个抽象方法,是函数式接口
interface test4 extends test1{

}
//接口中虽然有两个方法,但被default定义过的方法是不需要子类实现的,所以这个接口需要实现类实现的抽象方法只有test5()一个,是函数式接口
interface test5{
    void test5();
    default void test(){}
}
//接口中虽然有两个方法,但toString()可以从父类Object继承,实现类在实现此接口时此方法可以不重写,所以这个接口需要实现类实现的抽象方法只有test6()一个,是函数式接口
interface test6{
    void test6();
    String toString();
}
//接口中只有一个方法,toString()可以从父类Object继承,实现类实现该接口时可以重写也可以不重写,那么此接口就不是函数式接口
interface test7{
    String toString();
}
//此接口中有四个方法,test8()为实现类必须重写的方法,test9()有默认实现无需重写,test10()静态方法无需重写,toString()从Object继承可重写也可不重写,所以实现类必须重写的方法只有test8()一个,是函数式接口
interface test8{
    void test8();
    default void test9(){}
    static void test10(){}
    String toString();
}

    2.2 @FunctionalInterface

      作用:写在接口之前,可以判断当前接口是否为函数式接口,如果不是函数式接口则会报错,功能类似于@Override判断是否为重写。

@FunctionalInterface
interface test8{
    void test8();
    default void test9(){}
    static void test10(){}
    @Override
    String toString();
}

        有的小伙伴感觉被欺骗了,有这个注解来判断你上面在这和我扯啥呢?了解的多一点准没错的啦,而且还有面试笔试题嘞。

3.Lambda表达式语法

    3.1 Lambda表达式基础语法

(参数) -> {
   方法体
}

      参数:方法体的参数列表,要求和实现接口中的方法参数一致,包括参数的数量和类型。

      方法体:方法的实现部分,如果接口中定义的方法有返回值,在实现的时候注意返回值类型。

      ->:分隔参数部分和方法体

public class FunctionInterfaceDemo {
    @FunctionalInterface
    interface Predicate1 {
        void test();
    }
    @FunctionalInterface
    interface Predicate2 {
        Integer test(Integer count);
    }
    
    public static void main(String[] args) {
        Predicate1 predicate1 = () -> {
            System.out.println("无参无返回值的接口方法实现");
        };
        predicate1.test();
        Predicate2 predicate2 = (Integer a) -> {
            System.out.println("有参有返回值的接口方法实现 a = " + a);
            return a;
        };
        predicate2.test(1);
    }
}

       Lambda表达式基本语法比较简单,无参有返回值、有参无返回值、多参数无返回值.....等等其他类型方法同理类比即可。

    3.2 Lambda表达式语法进阶

            3.2.1 参数部分精简

                参数类型精简:由于在接口方法中已经定义了每一个参数的类型,而且Lambda表达式规定在实现接口的时候,需要保证参数的数量、类型需要和接口中声明的方法保持一致。所以Lambda表达式中参数的类型可以省略不写。

@FunctionalInterface
interface Predicate2 {
   Integer test(Integer count);
}
Predicate2 predicate2 = (Integer a) -> {
    System.out.println("有参有返回值的接口方法实现 a = " + a);
    return a;
};
//上述代码可省略方法参数类型:Integer 
Predicate2 predicate2 = (a) -> {
    System.out.println("有参有返回值的接口方法实现 a = " + a);
    return a;
};

                注:在多参数的接口方法中要保证,选择省略参数类型,就把所有参数的类型一起省略,不能有的参数类型省略,有的不省略。

                参数小括号部分精简:如果方法参数列表中的参数数量有且只有一个,此时参数列表的小括号可以省略不写。

@FunctionalInterface
interface Predicate2 {
   Integer test(Integer count);
}
Predicate2 predicate2 = (a) -> {
    System.out.println("有参有返回值的接口方法实现 a = " + a);
    return a;
};
//上述代码可省略小括号
Predicate2 predicate2 = a -> {
    System.out.println("有参有返回值的接口方法实现 a = " + a);
    return a;
};

                注:参数数量必须有且只有一个,省略小括号的同时,参数类型必须一起省略。

            3.2.2 方法体部分精简

                 方法体大括号精简:当一个方法体中的逻辑,有且只有一句话时,大括号可以省略。

@FunctionalInterface
interface Predicate1 {
    void test();
}
Predicate1 predicate1 = () -> {
    System.out.println("无参无返回值的接口方法实现");
};
//上述代码可省略大括号
Predicate1 predicate1 = () -> System.out.println("无参无返回值的接口方法实现");

                 return精简:如果一个方法中唯一的一条语句是一个返回语句,在省略大括号的同时,也必须省略return。

@FunctionalInterface
interface Predicate3 {
    Integer test3(Integer count);
}
Predicate3 predicate3 = (a) -> {
    return a * a;
};
//上述代码可简化为
Predicate3 predicate3 = (a) -> a * a;

4.Lambda表达式函数引用

    4.1 函数引用

               函数引用:引用一个已经存在的方法,使其代替Lambda表达式完成接口的实现。 Lambda表达式是为了简化接口实现的。在Lambda表达式中b不应该出现比较复杂的逻辑。如果Lambda表达式中需要处理的逻辑比较复杂,一般会单独写一个方法,需要的时候直接引用这个方法即可。Demo:

@FunctionalInterface
private static interface Calculate {
    int calculate(int a, int b);
}
private static int calculate(int x, int y) {
    if (x > y) {
        return x - y;
    } else if (x < y) {
        return y - x;
    }
    return x + y;
}
public static void main(String[] args) {
    Calculate calculate1 = (x, y) -> calculate(x, y);
    System.out.println(calculate1.calculate(10,20));
}

    4.2 静态方法引用

                  语法:类::静态方法

                  注意事项:引用方法的后面不需要添加小括号,同时引用的这个方法,参数数量、参数类型和返回值类型必须要和接口中定义的一致。

public class FunctionInterfaceDemo {
    @FunctionalInterface
    private static interface Calculate {
        int calculate(int a, int b);
    }
    private static int calculate(int x, int y) {
        if (x > y) {
            return x - y;
        } else if (x < y) {
            return y - x;
        }
        return x + y;
    }
    public static void main(String[] args) {
        Calculate calculate1 = (x, y) -> calculate(x, y);
        System.out.println(calculate1.calculate(10,20));
        //上述代码可改为以下静态方法引用
        Calculate calculate2 = FunctionInterfaceDemo::calculate;
        System.out.println(calculate2.calculate(10,20));
    }
}

    4.3 非静态方法引用

                  语法:对象::非静态方法

                  注意事项:引用方法的后面不需要添加小括号,同时引用的这个方法,参数数量、参数类型和返回值类型必须要和接口中定义的一致。

public class FunctionInterfaceDemo {
    @FunctionalInterface
    private interface Calculate {
        int calculate(int a, int b);
    }
    private int calculate(int x, int y) {
        if (x > y) {
            return x - y;
        } else if (x < y) {
            return y - x;
        }
        return x + y;
    }
    public static void main(String[] args) {
        Calculate calculate1 = new FunctionInterfaceDemo()::calculate;
        System.out.println(calculate1.calculate(10,20));
    }
}

    4.4 构造方法引用

                  语法:类名::new

                  注意事项:可通过接口中方法的参数区分不同的构造方法。

public class FunctionInterfaceDemo {
    private static class Person {
        String name;
        public Person() {
            System.out.println("无参构造被执行了");
        }
        public Person(String name) {
            this.name = name;
            System.out.println("有参构造被执行了 name = " + name);
        }
    }
    private static interface GetPersonWithNoneParameter{
        Person getPerson();
    }
    private static interface GetPersonWithSingleParameter {
        Person getPerson(String name);
    }
    public static void main(String[] args) {
        //使用Lambda表达式实现无参构造GetPersonWithNoneParameter接口
        GetPersonWithNoneParameter getPerson1 = () -> new Person();
        //使用Lambda表达式简化上述接口实现
        GetPersonWithNoneParameter getPerson2 = Person::new;
        //使用Lambda表达式实现有参构造GetPersonWithSingleParameter接口
        GetPersonWithSingleParameter getPerson3 = (x) -> new Person();
        //使用Lambda表达式简化上述接口实现
        GetPersonWithSingleParameter getPerson4 = Person::new;
    }
}

    4.5 对象方法特殊引用

                  强烈建议在解对象方法特殊引用之前,小伙伴们务必要把上述四种引用完全掌握,因为特殊引用听名字就知道是一种特殊的情况,很容易和上述四种引用混淆。

                  对象方法特殊引用:如果在使用Lambda表达式实现某些接口时,表达式中包含了某一个对象,此时方法体中直接使用这个对象调用它的某一个方法就可以实现整体的逻辑,其他的参数可作为调用方法的参数,此时可以对这种实现方式进行简化。光看概念确实很容易懵逼,来个Demo就很容易懂了:

public class FunctionInterfaceDemo {
    private static class Person {
        String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    @FunctionalInterface
    private interface GetName {
        String getName(Person person);
    }
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Tom");
        //使用Lambda表达式实现无参构造GetPersonWithNoneParameter接口
        GetName getName1 = (x) -> x.getName();
        System.out.println(getName1.getName(person));
        //上述可以简化为
        GetName getName2 = Person::getName;
        System.out.println(getName2.getName(person));
    }
}

       重点看简化后的Lambda表达式Person::getName,看起来和4.3非静态方法引用很相似,但仔细看也有不同,非静态方法语法为:类实例::非静态方法,而在这里直接使用的是类名(Person)。我们看着简化后的代码再读一下对象方法特殊引用的定义,表达式中包含了某一个对象x,并且方法体中直接使用x调用其getName()就可以实现整体的逻辑,那么此时(x) -> x.getName()就可以简化为Person::getName,细心的小伙伴可能发现了我漏写了一句话“其他的参数可作为调用方法的参数”,我们来看下面一个Demo:

public class FunctionInterfaceDemo {
    private static class Person {
        String name;
        public void setName(String name) {
            this.name = name;
        }
    }
    @FunctionalInterface
    private interface SetName {
        void setName(Person person,String name);
    }
    public static void main(String[] args) {
        Person person = new Person();
        //使用Lambda表达式实现无参构造GetPersonWithNoneParameter接口
        SetName setName1 = (x,n) -> x.setName(n);
        setName1.setName(person,"Tom");
        //上述可以简化为
        SetName setName2 = Person::setName;
        setName2.setName(person,"Tom");
    }
}

       表达式中包含了某一个对象x,并且方法体中直接使用x调用其getName()就可以实现整体的逻辑,同时其他的参数n可作为方法调用的参数,那么(x,n) -> x.setName(n)即可简写成Person::setName。现在让我们回过头来看一下最上面我们埋下的那个坑:list.forEach(System.out::print)是怎么来的?

 

       forEach源码我截取的是Iterable类中的源码,这个看起来简单一些,继承自Iterable类的不同List也只是根据自己的特性加了一些具体实现。入参是一个Consumer,JDK提供的函数式接口,其内部仅有一个accept(T t)抽象方法,作用为替换用户传入的方法体并执行。接下来就来看一下list.forEach(System.out::print)究竟是怎样一步一步演化出来的:

public static void main(String[] args) {
    List<Object> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        list.add(i);
    }
    //Lambda表达式没出现之前我们可以用匿名内部类的方式实现一个接口中的方法
    Consumer<Object> consumer = new Consumer<Object>(){
        @Override
        public void accept(Object o) {
            System.out.println(o);
        }
    };
    list.forEach(consumer);
    //演化1:java8出现后用Lambda表达式可以写成下面这种形式
    Consumer<Object> consumer1 = s-> System.out.println(s);//Lamdba表达式
    list.forEach(consumer1);
    //演化2:继续利用Lambda表达式对象方法特殊引用特性简化
    Consumer<Object> consumer3 = System.out::println;
    list.forEach(consumer3);
    //演化3:Consumer的定义也省略掉,就变成了最终我们看到样子
    list.forEach(System.out::println);
}

5.使用Lambda表达式需要注意的一个问题

       这其实是一个局部内部类的问题,如果某一个局部变量应用在某一个代码段中,那么就形成了对这个局部变量的闭包,这时此局部变量即使没有用final修饰,它默认也是final,既不能发生值的改变。

猜你喜欢

转载自blog.csdn.net/qq_36756682/article/details/111057374