Java8新特性-Lambda表达式

153 阅读5分钟

简介

  • 什么是Lambda表达式?
  1. Lambda是java8引入的新特性,是Java函数式编程的基础,减少了代码长度和代码复杂度。
  2. 理解: 他就是一个没有名字的函数,那这不就是匿名函数吗?但是写Lambda表达式可比写匿名函数方便的多。
  3. 特点: 允许将函数当作参数传递,叫做行为参数化,这里的传递的参数也是Function函数式接口,与函数式接口结合使用,效果更佳。

匿名内部类

在了解lambda之前,先来看看不使用Lambda表达式时,如何使用匿名内部类简化代码。关于匿名内部类的详解,参考文章, 想了解为什么要使用内部类可以看这篇:为什么要使用内部类

  • 为什么要使用匿名内部类?
  1. 最基础的方法调用是:新建一个中间类去继承接口/抽象类/普通类,实现或者重写接口,然后在实例化这个中间类,去调用这个类中实现或者重写方法,但其实这个中间类是没必要创建的。
  2. 匿名内部类就是没有名字的内部类,但是一个类应该具有的成员,它都是具备的。虽然也叫类,不过编写代码时,是没有显示创建类的。
  • 没有名字那么如何去new对象出来呢?
  1. 匿名调用需要继承父类或者实现接口中的方法来达到这一目的。
  2. 无需重新去新建一个类实现或者继承接口,再去实例化这个类进行调用,省去了重新创建类的步骤。
public class LamdbaTest {
    public static void main(String[] args) {
        System.out.println("================调用内部类时,传递的参数可以是 接口 / 抽象类 / 普通类================");
        // 注:这里不是实例化接口,而是创建一个接口的实现类。
        new Inter() {
            @Override
            public void show() {
                System.out.println("接口Inter");
            }
        }.show();

        // 抽象类
        new People() {
            @Override
            void method() {
                System.out.println("抽象类People");
            }
        }.method();

        // 普通类
        new Employee() {
            @Override
            void work() {
                System.out.println("具体类Employee");
            }
        }.work();
    }
}

// 接口
interface Inter {
    void show();
}

// 抽象类
abstract class People {
    abstract void method();
}

// 普通类
class Employee {
    void work(){
        System.out.println("雇员要工作");
    }
}
  • 当内部类中有多个方法时,该如何调用?

这里就要用到多态了,将这个内部类实例化出来,然后在进行调用。

public class LamdbaTest {
    public static void main(String[] args) {
        // 注意: 这里不是实例化接口,而是创建一个接口的实现类。
        Inter inter = new Inter() {
            @Override
            public void show() {
                System.out.println("接口Inter");
            }

            @Override
            public void display() {
                System.out.println("来吧,展示!");
            }
        };
        
        inter.display();
        inter.show();
    }
}

// 接口
interface Inter {
    void show();
    void display();
}

Lambda表达式

表达式写法

如下图所示:一个lambda分为三部分,即参数列表 + 操作符 + lambda体。

image.png

以下是lambda表达式的重要特征:

  • 可选类型声明: 不需要声明参数类型,编译器会自动识别参数值,进行类型推断。例如:
  1. s -> System.out.println(s)
  2. (String s) -> System.out.println(s)
  • 可选的参数圆括号: 一个参数无需定义圆括号,但多个参数需要定义圆括号。例如:
  1. s -> System.out.println(s) 一个参数不需要添加圆括号。
  2. (x, y) -> Integer.compare(y, x) 两个参数添加了圆括号,否则编译器报错。
  • 可选的大括号: 如果主体包含了一个语句,则不需要使用大括号。例如:
  1. s -> System.out.println(s) 不需要大括号
  2. (s) -> { if (s.equals("s")){ System.out.println(s); } }; 需要大括号
  • 可选的返回关键字: 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要加return语句,指定明表达式返回了一个数值。

方法引用

方法引用是用来直接访问类或者实例已经存在的方法或构造方法,提供了一种引用而不执行方法的方式。

  • 当Lambda表达式中只是执行一个方法调用时,直接使用方法引用的形式可读性更高一些。
  • 方法引用使用 :: 操作符来表示,左边是类名或实例名,右边是方法名,方法名无需加()。
  • 方法引用有以下三种形式:
  1. 类 :: 静态方法
  2. 类 :: 实例方法
  3. 对象 :: 实例方法
public class TestLambda {

    public static void main(String[] args) {

        List<Student> students = new ArrayList<>();
        students.add(Student.builder().name("daiaoqi").age(23).sex("man").grade(80).build());
        students.add(Student.builder().name("wutong").age(25).sex("woman").grade(78).build());
        students.add(Student.builder().name("kanghaike").age(19).sex("woman").grade(98).build());
        students.add(Student.builder().name("jieweicheng").age(23).sex("man").grade(89).build());
        students.add(Student.builder().name("jieweicheng").age(23).sex("man").grade(89).build());

        TestLambda testLambda = new TestLambda();

        // 测试类的实例方法引用
        testLambda.testConsumer(students);

        // 类的静态方法引用
        students.stream().forEach(TestLambda::testStaticMethod);
        
        // 对象的实例方法引用
        students.stream().forEach(testLambda::testObjectRef);
        
    }

    /**
     * @Description 类的实例方法引用
     * @param students
     * @Return
     */
    public void testConsumer(List<Student> students){
        Consumer<Student> consumer = new Consumer<Student>() {
            @Override
            public void accept(Student student) {
                System.out.println(student.getAge());
            }
        };

        students.stream()
                .peek(consumer)
                .collect(Collectors.toList());
    }
    
    /**
     * @Description 对象的实例方法引用
     * @param student
     * @Return
     */
    public void testObjectRef(Student student) {
        System.out.println(student.getGrade());
    }

    /**
     * @Description 类的静态方法应用
     * @param student
     * @Return
     */
    public static void testStaticMethod(Student student) {
        System.out.println(student.getGrade());
    }

}

注意: Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型需要保存一致

二者区别

看了上述lambda的写法之后,在结合第二章节中匿名内部类的写法,替换成lambda的语法就是:

public class LamdbaTest {
    public static void main(String[] args) {
        // 匿名内部类写法
        new Inter() {
            @Override
            public void show() {
                System.out.println("匿名内部类写法");
            }
        }.show();
        
        // 用lambda的写法
        byLambda(() -> System.out.println("采用lambda写法,这就是重写的show的方法体,多个语句则用{}"));

    }

    public static void byLambda(Inter inter){
        inter.show();
    }
}

// 接口
interface Inter {
    void show();
}

lambda的使用还是有约束的,对比匿名内部类,主要有以下几点区别:

  1. 所需类型不同。匿名内部类可以是接口、抽象类、具体类。Lambda表达式的参数只能是接口。
  2. 使用限制不同。如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类。 但接口中有多个方法时,lambda的这种写法就不适用了。
  3. 实现原理不同。匿名内部类虽然跟使用Lambda效果一样,但是会自动多生成一个.class字节码文件,这也是匿名内部类跟Lambda的一大区别。

待补充...