Java8新特性 ----- Lambda表达式和方法引用/构造器引用详解

简介: Java8新特性 ----- Lambda表达式和方法引用/构造器引用详解

前言

在讲一下内容之前,我们需要引入函数式接口的概念

什么是函数式接口呢?

函数式接口:有且仅有一个抽象方法的接口

java中函数式编程的体现就是Lambda表达式,你可以认为函数式接口就是适用于Lambda表达式的接口.

也可以加上注解来在编译层次上限制函数式接口

  • @Functionallnterface
  • 放在 接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
  • 注:自定义的函数式接口可以加上这个表示是函数式接口,不加也可以,但是建议添加

常见的函数式接口有如下四种

接口                                 抽象方法
Consumer<T> 消费型接口           void accept(T t)
Supplier<T> 供给型接口           T get()
Function<T>函数型                R apply(T t)
Predicted<T>判断型接口           boolean test(T t)

以下对Lambda表达式的说明都是基于函数式接口的

为什么需要Lambda表达式?

本身我们最原始的方法定义类来实现接口,在用类来实例化对象调用方法显得冗余且重,我们简化到匿名内部类的时候也显得冗余,主要目的是为了简化代码,并提供更加简洁和灵活的函数式编程方式。也与后面要说的stream api有关,这里不做过多赘述.

1.Lambda表达式

Lambda表达式的本质其实是一个接口实现类的对象,也是一个匿名函数.

下面我们就谈谈几种lambda表达式的应用场景

1.实现Runnable接口,注意此处不涉及到线程!!

Lambda表达式的思想就是能省略的就省略,不产生歧义即可

于是就有了这样的写法

@Test
    public void test1()
    {
        //语法格式1:无参数,无返回值
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("我爱北京天安门");
            }
        };
        r1.run();
        System.out.println("*******************");
        Runnable r2 = ()->{ System.out.println("我爱北京天安门");};
        r2.run();
    }

2.当lanbda表达式中的参数类型确定时,参数类型也可以省略

@Test
    public void test3()
    {
        //数据类型可以省略,因为可以由编译器进行推断
        Consumer<String> con1 = (String s)->{
            System.out.println(s);
        };
        con1.accept("如果大学可以重来,你最想重来的事是啥?");
        System.out.println("****************");
        Consumer<String> con2 = (s)->{
            System.out.println(s);
        };
        con2.accept("如果大学可以重来,你最想重来的事是啥?");
    }

3.只有一个参数的时候,小括号可以省略

@Test
    public void test5()
    {
        //当只有一个参数的时候,参数的小括号可以省略
        Consumer<String> con1 = s->{System.out.println(s);};
        con1.accept("世界那么大,我想去看看");
    }

4.只有一条语句时,return语句和大括号可以省略(必须一起省略)

Comparator<Integer> com1 = (o1,o2) ->{
            return o1.compareTo(o2);
        };
        System.out.println(com1.compare(12,6));
        System.out.println("***********************");
        Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);

总结:

格式

->:箭头操作符

->的左边:Lambda形参列表:对应着要重写的接口中要重写的形参列表

->的右边:Lambda体,对应着接口的实现类要重写的方法体

Lambda形参列表 ->Lambda体

细节注意

->的左边 :lambda 形参列表 :参数类型可以省略,如果形参列表只有一个,小括号也可以省略

->的右边:lambda体: 对应着重写方法的方法体,如果方法体中只有一条执行语句,则大括号可以省略,有return关键字,则return需要一并省略

2.方法引用

可以看做是Lambda表达式的进一步延伸

使用说明

情况1: 对象 :: 实例方法(非静态方法)
要求:函数式接口的抽象方法a与其内部实现时调用的某个方法b的形参列表和返回值类型都相同(或一致),
我们就可以考虑用方法b对方法a进行替换,此替换或覆盖称为方法引用
注:此时b是非静态的方法,需要对象来调用
情况2: 类 :: 静态方法
要求:函数式接口的抽象方法a与其内部实现时调用的某个方法b的形参列表和返回值类型都相同,
我们就可以考虑用b对方法a进行替换,此替换或覆盖称为方法引用
注:此时b是静态的方法,需要类来调用
情况3: 类 :: 实例方法
要求:函数式接口的抽象方法a与其内部实现时时调用的对象的某个方法b的返回值类型相同
同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第一个参数作为方法b的调用者,且抽象方法a
的后n-1个参数与方法b的n-1个参数类型相同或一致,则可以使用方法引用
注意:此方法b是静态方法,需要对象调用,但是形式上,写出a所属的类.

举例说明

1.对象::方法类型

此时我们发现get方法是空参方法,返回值是String,emp.getName()方法返回值是String形参也为空,这样就可以用这个实现的方法来覆盖原有的get方法,于是可以写作

emp::getName()

注意:这里的相同可以理解为满足多态即可.

@Test
    public void test2()
    {
        //供给型 Supplier中的T() get
        //Employee 中的String getName()
        Employee emp = new Employee(1001,"马化腾",34,6000.38);
        Supplier<String> sup1 = new Supplier<String>() {
            @Override
            public String get() {
                return emp.getName();
            }
        };
        System.out.println(sup1.get());
        //Lambda表达式写法
        Supplier<String> sup2 = ()-> emp.getName();
        System.out.println(sup2.get());
        //3.方法引用
        Supplier<String> sup3 = emp :: getName;
        System.out.println(sup3.get());
    }

2.类::静态方法举例

这里我们发现compare方法和实现中的Integer的compare方法参数和返回值一直,就可以使用方法引用,只不过这里的compare方法是静态方法,要使用类来调用,看做对原本抽象方法的一个覆盖,写作Integer :: compare;

@Test
    public void test3()
    {
        //类 :: 静态对象
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };
        Comparator<Integer> com2 = (o1,o2) ->Integer.compare(o1,o2);
        System.out.println(com2.compare(12,21));
    }
    Comparator<Integer> com3 = Integer :: compare;

3.类 :: 实例方法

这个的理解就想对困难一点点,本质和之前一样

这里我么假设抽象方法的形参有n个,实现的语句是形参1为调用者的语句

这里就可以把形参1抽象为其对应的类,剩余的返回值和形参都与原抽象方法一致

这样就可以用这种形式的方法引用代替

@Test
    public void test5()
    {
        //情况3 类 :: 实例方法(难)
        Comparator<String> com1 = new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        };
        //2.Lambda表达式
        Comparator<String> com2 = (o1,o2) ->o1.compareTo(o2);
        //满足参数是 重写的函数的参数是需要调用的函数的参数的n+1个,也可以使用方法引用的方式
        Comparator<String> com3 = String :: compareTo;
    }

3.构造器引用/数组引用

实际上是对方法引用的一种特殊操作

就是抽象方法里面返回一个构造器,如果把构造器看做一个方法,其实本质上就一样了

Supplier<Employee> sup1 = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
Supplier<Employee> sup2 = Employee :: new;

也可以是多参数的构造器引用,因为前面的泛型参数可以直接推断你的构造器类型

public void test2()
    {
        Function<Integer,Employee> func1 = new Function<Integer, Employee>() {
            @Override
            public Employee apply(Integer id) {
                return new Employee(id);
            }
        };
        System.out.println(func1.apply(12));
        //构造器引用
        Function<Integer,Employee> func2 = Employee :: new;
        //调用的是Employee类中参数是Integer类型的构造器
        func2.apply(11);
    }

三个,四个也是可以的

数组引用

和上面类似,不做过多解释

Function<Integer,Employee[]> func1 = new Function<Integer, Employee[]>() {
            @Override
            public Employee[] apply(Integer length) {
                return new Employee[length];
            }
        };
        Function<Integer,Employee[]> func2 = Employee[] :: new;

变量捕获

lambda表达式的本质其实是一个匿名内部类,在lambda表达式或者是匿名内部类中使用局部变量或全局变量时,java会捕获这些值,以便使用,即使外部的方法已经执行结束,这里被引用的局部变量仍然可以被使用,但是变量要求的final修饰或者是事实final

事实final就是指没有被final修饰,但是其一直没有被修改过,只不过没有去限制他,而final就是从语法层次上去限制这个变量不可以被修改

此处特指:实例变量,类变量或者静态变量可以改变

举例

public class VariableCaptureExample {
    public static void main(String[] args) {
        int num = 10; // 外部局部变量
        // Lambda表达式捕获num变量
        Runnable r = () -> {
            // 在Lambda内部访问外部变量num
            System.out.println("Value of num: " + num);
            // num = 15; // 错误,试图修改effectively final的变量将会导致编译错误
        };
        r.run(); // 执行Lambda表达式
    }
}

总结

Lambda表达式可以对函数式接口的实现代码进行精简,(满足一定条件)进一步引出了方法引用/构造器引用/数组引用等...

秋秋语录:今日事今日毕,语法基础一定要打扎实,大处着眼,小处着手,多看多练.

相关文章
|
2天前
|
Java API 开发者
探索Java中的Lambda表达式与函数式编程
本文旨在深入探讨Java中Lambda表达式的概念、语法结构及其在函数式编程中的应用。通过对比传统编程模式,展示Lambda表达式如何简化代码、提高开发效率,并结合实例分析其在实际项目开发中的运用。文章还将讨论Lambda表达式的性能考量和在并发编程场景下的优势。
|
4天前
|
Java 开发者
Java中的Lambda表达式与函数式接口
【7月更文挑战第20天】本文深入探讨Java 8引入的Lambda表达式及其在函数式编程中的应用。我们将分析Lambda表达式如何简化代码、提高可读性,以及它与传统匿名内部类的区别。文章还将介绍函数式接口的概念,并通过实际示例展示如何利用Lambda表达式和函数式接口优化Java代码。
|
5天前
|
Java API 数据处理
探索Java中的Lambda表达式
【7月更文挑战第19天】本文将深入探讨Java 8中引入的Lambda表达式,这一特性极大地简化了代码编写,提高了开发效率。我们将从Lambda表达式的基础概念入手,逐步过渡到其语法结构、使用场景以及性能考量,最后通过实际案例演示其在Java中的应用。Lambda表达式不仅让代码更加简洁,还促进了函数式编程思想在Java中的普及。
|
5天前
|
并行计算 Java API
深入理解Java中的Lambda表达式与函数式接口
【7月更文挑战第19天】在Java 8中引入的Lambda表达式,不仅简化了代码编写,还为函数式编程提供了支持。本文将探讨Lambda表达式的核心概念、其与函数式接口的关系以及如何在Java中高效利用这一特性来提升代码的简洁性和可读性。我们将通过实例分析Lambda表达式的语法规则和常见用法,同时解释函数式接口的设计原则及其在Java标准库中的应用,旨在帮助开发者更好地理解和运用这一强大的工具。
|
1天前
|
Java 编译器 开发者
深入理解Java中的Lambda表达式
【7月更文挑战第23天】Lambda表达式在Java 8中引入,旨在简化代码编写和提高函数式编程的可读性。本文将探讨Lambda表达式的基本概念、语法结构以及如何有效利用它们来简化集合操作和事件处理等常见任务。通过实例演示,我们将看到Lambda表达式如何让代码更加简洁明了,同时也会讨论它们带来的性能考量和最佳实践。
|
10月前
|
Java
JAVA方法的定义
JAVA方法的定义
49 0
|
27天前
|
安全 Java 编译器
杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
17 1
|
2月前
|
存储 Java
Java数组与带参数方法:定义、调用及实践
Java数组与带参数方法:定义、调用及实践
30 1
|
2月前
|
存储 Java
Java中带返回值方法的定义与调用技术
Java中带返回值方法的定义与调用技术
33 1
|
2月前
|
Java
Java一分钟之-方法定义与调用基础
【5月更文挑战第8天】本文介绍了Java编程中的方法定义和调用,包括基本结构、常见问题和避免策略。方法定义涉及返回类型、参数列表和方法体,易错点有返回类型不匹配、参数错误和忘记返回值。在方法调用时,要注意参数传递、静态与非静态方法的区分,以及重载方法的调用。避免错误的策略包括明确返回类型、参数校验、理解值传递、区分静态和非静态方法以及合理利用重载。通过学习和实践,可以提升编写清晰、可维护代码的能力。
36 0