一文搞懂Java8 Lambda表达式、方法引用

目录

Lambda表达式介绍

体验Lambda表达式

Lambda表达式语法

Lambda表达式语法细讲

Lambda表达式精简语法

方法引用

构造方法引用

数组的构造器引用:

@FunctionalInterface注解


本文转自:一文搞懂Java8 Lambda表达式(附视频教程)

有所增删~

Lambda表达式介绍

Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。通过Lambda表达式,可以替代我们以前经常写的匿名内部类来实现接口。Lambda表达式本质是一个匿名函数;

体验Lambda表达式

我们通过一个小例子来体验下Lambda表达式;

我们定义一个计算接口 只有一个方法add;

public class Program {

    public static void main(String[] args) {
        Cal c1=new Cal() {
            @Override
            public int add(int a, int b) {
                return a+b;
            }
        };
       int c=c1.add(1,2);
        System.out.println(c);
    }
}

interface Cal{
     int add(int a,int b);
}

这个是我们以前的实现,匿名内部类,然后调用执行;

我们现在用Lambda表达式改写下:

public class Program {

    public static void main(String[] args) {
        Cal c1=(int a,int b) ->{return a+b;};
        int c=c1.add(1,2);
        System.out.println(c);
    }

    int add(int a,int b){
        return a+b;
    }
}

interface Cal{
     int add(int a,int b);
}

匿名内部类,直接改成了:

Cal c1=(int a,int b) ->{return a+b;};

简洁多了;

是不是感觉Lambda表达式挺强大,

接下来我们来看看Lambda表达式的语法吧;

Lambda表达式语法

我们看下这个Lambda表达式:

(int a,int b) ->{return a+b;};

这个本质是一个函数;

一般的函数类似如下:

int add(int a,int b){
  return a+b;
}

返回值方法名参数列表方法体

Lambda表达式函数的话,只有参数列表,和方法体

( 参数列表 ) -> { 方法体 }

( ) :用来描述参数列表;

{ } : 用来描述方法体;

->  :Lambda运算符,可以叫做箭头符号,或者goes to

注意:

1、lambda表达式要求接口是函数式接口。

2、lambda表达式引用的外部变量必须是事实最终变量。即初始化后值不会再改变。(不能在lambda改变外部变量的值。也不能在lambda内引用在外部会被改变的值,不管该值在外部的改变是在lambda表达式执行的前后)

3、在lambda表达式中声明 一个与方法的局部变量同名的参数 或 一个与方法的局部变量的同名的局部变量 是不合法的。

4、在一个方法中不能定义两个同名的局部变量,因此,lambda表达式中也同样不能有同名的局部变量。

5、在一个lambda表达式中使用this关键字,是指 创建这个lambda表达式的方法 所属的类 的this参数。如:

public class Application{
    public void init(){
        ActionListener Listener = event -> {
            System.out.println(this.toString());    
        }
    }
}

这里调用的是Application对象的toString方法,而不是ActionListener接口的实例对象的方法。

Lambda表达式语法细讲

我们搞一个案例,接口方法(无参,单个参数,两个参数)X(有返回值,没有返回值)这六种情况都罗列下:

interface If1{

    /**
     * 无参数无返回值
     */
     void test();
}


interface If2{

    /**
     * 单个参数无返回值
     * @param a
     */
    void test(int a);
}

interface If3{

    /**
     * 两个参数无返回值
     * @param a
     * @param b
     */
    void test(int a,int b);
}


interface If4{

    /**
     * 无参数有返回值
     * @return
     */
    int test();
}

interface If5{

    /**
     * 单个参数有返回值
     * @param a
     * @return
     */
    int test(int a);
}

interface If6{

    /**
     * 多个参数有返回值
     * @param a
     * @param b
     * @return
     */
    int test(int a,int b);
}

我们用Lambda表达式实现:

// 无参数无返回值
If1 if1=()->{
  System.out.println("无参数无返回值");
};
if1.test();

// 单个参数无返回值
If2 if2=(int a)->{
  System.out.println("单个参数无返回值 a="+a);
};
if2.test(3);

// 两个参数无返回值
If3 if3=(int a,int b)->{
  System.out.println("两个参数无返回值 a+b="+(a+b));
};
if3.test(2,3);

// 无参数有返回值
If4 if4=()->{
  System.out.print("无参数有返回值 ");
  return 100;
};
System.out.println(if4.test());


// 单个参数有返回值
If5 if5=(int a)->{
  System.out.print("单个参数有返回值 ");
  return a;
};
System.out.println(if5.test(200));

// 多个参数有返回值
If6 if6=(int a,int b)->{
  System.out.print("多个参数有返回值 ");
  return a+b;
};
System.out.println(if6.test(1,2));

运行输出:

无参数无返回值
单个参数无返回值 a=3
两个参数无返回值 a+b=5
无参数有返回值 100
单个参数有返回值 200
多个参数有返回值 3

Lambda表达式精简语法

那件语法注意点:

1、参数类型可以省略。

2、假如只有一个参数,()括号可以省略。

3、如果方法体只有一条语句,{}大括号可以省略。

4、如果方法体中唯一的语句是return返回语句,那省略大括号的同时return也要省略。

改写示例:

/**
 * @author java1234_小锋
 * @site www.java1234.com
 * @company Java知识分享网
 * @create 2020-08-12 16:43
 */
public class Program2 {

    public static void main(String[] args) {
        // 1,参数类型可以省略
        // 2,假如只有一个参数,()括号可以省略
        // 3,如果方法体只有一条语句,{}大括号可以省略
        // 4,如果方法体中唯一的语句是return返回语句,那省略大括号的同事return也要省略

        // 无参数无返回值
        If1 if1=()->System.out.println("无参数无返回值");
        if1.test();

        // 单个参数无返回值
        If2 if2=a->System.out.println("单个参数无返回值 a="+a);
        if2.test(3);

        // 两个参数无返回值
        If3 if3=(a,b)->{
            System.out.println("两个参数无返回值 a+b="+(a+b));
        };
        if3.test(2,3);

        // 无参数有返回值
        If4 if4=()->100;
        System.out.println(if4.test());


        // 单个参数有返回值
        If5 if5=a->{
            System.out.print("单个参数有返回值 ");
            return a;
        };
        System.out.println(if5.test(200));

        // 多个参数有返回值 参数类型可以省略
        If6 if6=(a,b)->a+b;
        System.out.println(if6.test(1,2));

    }

}

方法引用

有时候多个lambda表达式实现函数是一样的话,我们可以封装成通用方法,以便于维护(有错误时只需要修改一处,不需要把所以同样的lambda表达式都修改)

或者说,如果lambda表达式要表达的函数方案已经存在于某个方法的实现中,那么就可以通过双冒号来引用该方法作为lambda表达式的替代者。

这时候可以使用方法引用来达到目的:

语法是:对象::方法

假如是static方法,可以直接 类名::方法

注意:因为方法引用并不是把所引用的方法整体作为接口抽象方法的实现,而是把所引用的方法的方法体作为抽象方法的实现。因此,对所引用的方法的方法名、实例还是静态的都是不做要求的,只要返回值和参数列表与抽象方法相同,那么就可以替代lambda表达式作为抽象方法的实现。

(如果是接口的实现类那么重写方法时必须定义成实例的)

注意:接口必须是函数式接口!!(即只能含有一个抽象方法)(接口可以含多个静态方法)

另:方法参数列表可以是接口类型的引用,但要传入一个对该接口的实现。该方法可以是方法引用,也可以是lambda表达式。

示例如下:

package B;

public class Program2{
    public static void main(String[] args) {
        // 使用lambda表达式的方法实现接口
        If5 i0 = (i)->{
            return i;
        };
        i0.test(0);

        // 实例方法 对象名::方法名
        Program2 p = new Program2();
        If5 i1 = p::A;
        System.out.println(i1.test(5));

        // 静态方法 类名::方法名
        If6 i2 = Program2::B;
        System.out.println(i2.test2(6));
        
        ling.KK(999,i1);    
        // 另,方法参数列表可以是接口类型的引用,但要传一个对该接口的实现
        // 该实现可以是方法引用,也可以是lambda表达式

    }

    public int A(int i) {   // 对名字不做要求、对静态的实例的也不做要求
        return i;
    }

    public static int B(int i) {    // 对名字不做要求、对静态的实例的也不做要求
        return i;
    }
    
}



interface If5{
    int test(int i);    // 必须是函数式接口
}

interface If6{
    int test2(int i);    // 必须是函数式接口
}

class ling{
    public static void KK(int number,If5 ii){
        ii.test(number);
    }
}

如果想调用的方法所在的类是父类的方法,则可以使用super::方法名,

如果想调用的方法所在的类是父类的方法,则可以使用this::方法名。

示例如下:

interface If5{
    int test(int i);    // 必须是函数式接口
}

class father{
    public int A(int i) {
        return i;
    }
}

class son extends father {
    public void KK() {
        If5 i1 = super::A;
        System.out.println(i1.test(1));

        If5 i2 = this::B;
        System.out.println(i2.test(2));
    }

    public int B(int i) {
        return i;
    }
}

构造方法引用

如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,

那么就可以使用构造方法引用;

语法:类名::new

示例如下:

public class Dog {

    private String name;

    private int age;

    public Dog() {
        System.out.println("无参构造方法");
    }

    public Dog(String name, int age) {
        System.out.println("有参构造方法");
        this.name = name;
        this.age = age;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

interface DogService{
    Dog getDog();

}

interface DogService2{
    Dog getDog(String name,int age);
}

public class Program3 {

    public static void main(String[] args) {

        // 普通方式
        DogService dogService=()->{
            return new Dog();
        };
        dogService.getDog();

        // 简化方式
        DogService dogService2=()->new Dog();
        dogService2.getDog();

        // 构造方法引用
        DogService dogService3=Dog::new;
        dogService3.getDog();

        // 构造方法引用 有参
        DogService2 dogService4=Dog::new;
        dogService4.getDog("小米",11);

        // 错误方式:
        DogService dogService5=()->Dog::new;
        dogService5.getDog();
        // 相当于以上的简化方法,但这样是return Dog::new ,这不是对接口的实现,接口的抽象方法是返回Dog类型,不是DogService类型的Dog::new
    }
}

执行结果:

无参构造方法
无参构造方法
无参构造方法
有参构造方法

综合示例:

下面我们通过一个lambda操作集合的综合示例,来深入体验下Lambda表达式用法;

public class Program4 {

    public static void main(String[] args) {
        List<Dog> list=new ArrayList<>();
        list.add(new Dog("aa",1));
        list.add(new Dog("bb",4));
        list.add(new Dog("cc",3));
        list.add(new Dog("dd",2));
        list.add(new Dog("ee",5));
        // 排序
        System.out.println("lambda集合排序");
        list.sort((o1,o2)->o1.getAge()-o2.getAge());
        // 需要一个实现Comparator接口的类对象,可用lambda表达式代替。
        
        // 遍历集合
        System.out.println("lambda遍历集合");
        list.forEach(System.out::println);
        // 每读取一个元素,就调用System.out类的println方法
    }
}

运行输出:

lambda集合排序
[Dog{name='aa', age=1}, Dog{name='dd', age=2}, Dog{name='cc', age=3}, Dog{name='bb', age=4}, Dog{name='ee', age=5}]
lambda遍历集合
Dog{name='aa', age=1}
Dog{name='dd', age=2}
Dog{name='cc', age=3}
Dog{name='bb', age=4}
Dog{name='ee', age=5}

数组的构造器引用:

如果接口的抽象方法是接收一个数组长度然后返回一个数组,那么可以使用数组的构造器引用来实现该抽象方法。

示例如下:

// 定义一个函数式接口
@FunctionalInterface
interface BuildArrays{
    double[] buildArrays(int length);
}

public class Demo{
    public static double[] buildArrays(int length,BuildArrays buildArrays) {
        return buildArrays.buildArrays(length);
        // 执行到此处时接口已被实现为double类型数组的创建,调用时传入所需要创建的长度即可。
    }

    public static void main(String[] args) {
        // 调用本类的buildArrays方法,该方法又通过接口的引用调用接口的buildArrays方法
        double[] arr01 = buildArrays(10, length -> new double[length]);
        System.out.println(arr01.length); // 10

        double[] arr02 = buildArrays(10, double[] :: new);
        System.out.println(arr02.length); // 10

	}
}

@FunctionalInterface注解

前面我们会发现Consumer接口,Comparator接口都有

@FunctionalInterface注解;

这个注解是函数式接口注解,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。lambda表达式要求是函数式接口,方法引用也要求是函数式接口。

这种类型的接口也称为SAM接口,即Single Abstract Method interfaces

特点

  • 接口有且仅有一个抽象方法

  • 允许定义静态方法

  • 允许定义默认方法

  • 允许java.lang.Object中的public方法

  • 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错

示例如下:

// 错误的函数式接口(有多个抽象方法)
@FunctionalInterface
public interface TestInterface2 {

    void add();
    
    void sub();
}

系统内置函数式接口

Java8的推出,是以Lambda重要特性,一起推出的,其中系统内置了一系列函数式接口;

再jdk的java.util.function包下,有一系列的内置函数式接口:

比如常用的Consumer,Comparator,Predicate,Supplier等;

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值