Java8 Lambda表达式用法详解

一、匿名内部类

1.1 匿名内部类概述

我们都知道 Lambda 是一个匿名函数,使用Lambda可以帮我们简化匿名内部类的使用,而匿名内部类在实际开发当中并不怎么用,很多人对他的印象其实并不深,所以我们有必要先了解清楚匿名内部类。

什么是匿名内部类。

匿名内部类,就是没有名字的一种嵌套类。它是Java对类的定义方式之一。

为什么要使用匿名内部类?

在实际开发中,我们常常遇到这样的情况:一个接口/类的方法的某个实现方式在程序中只会执行一次,但为了使用它,我们需要创建它的实现类/子类去实现/重写。此时可以使用匿名内部类的方式,可以无需创建新的类,减少代码冗余。

匿名内部类只可以使用在接口上吗

不是的。匿名内部类可以用在具体类、抽象类、接口上,且对方法个数没有要求。

1.2 匿名内部类使用场景

下面详细说明一下

假设当前有一个接口,接口中只有一个方法:

public interface Interface01 {
    void show();
}

为了使用该接口的show方法,我们需要去创建一个实现类,同时书写show方法的具体实现方式

public class Interface01Impl implements Interface01{
    @Override
    public void show() {
        System.out.println("I'm a impl class...");
    }
}

如果实现类Interface01Impl全程只使用一次,那么为了这一次的使用去创建一个类,未免太过麻烦。我们需要一个方式来帮助我们摆脱这个困境。匿名内部类则可以很好的解决这个问题。

我们使用匿名内部类

public static void main(String[] args) {
    Interface01 interface01 = new Interface01() {
        @Override
        public void show() {
            System.out.println("这里使用了匿名内部类");
        }
    };
    //调用接口方法
    interface01.show();
}

注意:匿名内部类不能有普通的静态变量声明,只能有静态常量。

1.3 创建线程示例

通常,我们也习惯用匿名内部类的方式创建并启动线程

通过下面示例会发现:Thread是个普通类,可以用匿名内部类,Runnable是个接口他也可以使用匿名内部类创建。

// 创建线程的方式一:创建一个继承与Thread类的子类,重写run方法
new Thread(){
      @Override
      public void run() {
          System.out.println(Thread.currentThread().getName()+"创建新线程1");
      }
}.start();

// 创建线程的方式二:实现Runnable接口,再将Runnable的实现类传入Thread构造器,然后通过Thread启动线程
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("我是一个线程");
    }
};
new Thread(runnable).start();

// 创建线程的方式二::创建一个实现Callable接口的实现类,重写call方法

二、函数式(Functional)接口

2.1 函数式接口概述

什么是函数式(Functional)接口

函数式接口就是只有一个抽象方法的接口,同时,只有函数式接口可以使用Lambda表达式。

在java.util.function包下定义了Java 8 的丰富的函数式接口

Java当中的lambda是函数式编程吗

在函数式编程语言当中,Lambda表达式的类型是函数。在Java8中,Lambda表达式是对象,而不是函数它们必须依附于一类特别的对象类型——函数式接口

2.2 函数式接口示例

Runnable接口就是一个典型的函数式接口,在上面创建线程的时候也用到了,他是可以用 匿名内部类 的形式进行实例化的。

在这里插入图片描述

2.3 自定义函数式接口

@FunctionalInterface
public interface MyNumber {
    public double getValue();
}

函数式接口中使用泛型:

@FunctionalInterface
public interface MyFunc<T> {
    public T getValue(T t);
}

2.4 @FunctionlInterface用法

  • 该注解只能标记在"有且仅有一个抽象方法"的接口上,表示函数式接口。
  • 该注解不是必须的,如果一个接口符合"函数式编程"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查,如果编写的不是函数式接口,但是加上了@Functionallnterface 那么编译器会报错。
  • 在函数式接口使用该注解,javadoc 也会包含一条声明,说明这个接口是一个函数式接口
    在这里插入图片描述

代码如下:

@FunctionalInterface
public interface TestInterface {
    void showList();
}

2.5 四大核心函数式接口

这四个有必要了解一下,我现在还记着去年面试的时候当时就被问到了。
在这里插入图片描述

2.6 其他函数式接口

在这里插入图片描述
JDK1.8在线API:https://docs.oracle.com/javase/8/docs/api/

java.util.function包下都是函数式接口。

三、Lambda表达式

3.1 Lambda表达式概述

什么是Lambda 表达式?

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码

Lambda表达式 和 函数式接口 的关系

Lambda表达式就是一个函数式接口的实例(注意是实例,Object a = new Object()这个a只是个引用变量,而new Object这是一个实例)。只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

Lambda表达式 和 匿名内部类 的关系

函数式接口是可以使用匿名内部类进行创建实例,而lambda可以简化使用匿名内部类创建。

注意: 使用lambda简化匿名内部类的前提条件是,接口必须是函数式接口。而匿名内部类创建实例却不一定非得是函数式接口,甚至都不一定是接口,还可以是普通类。

匿名内部类、函数式接口、lambda表达式 这三个关系一定要搞清楚

Lambda 语法

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

  • 左侧:指定了 Lambda 表达式需要的参数列表
  • 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。

3.2 匿名内部类 转换 Lambda

从匿名类到 Lambda 的转换举例1:

通过下面示例我们能发现一个问题:使用匿名内部类,没有变量引用也可以创建,而lambda则不可以,他必须要有变量引用。这个变量引用可以是直接的变量引用,也可以是方法内参数传递,Lambda表达式就是一个函数式接口的实例

假如没有变量引用例如:

() -> System.out.println("我是一个函数式"); 只有这个肯定是不行的,他需要靠前面的TestInterface变量引用来做类型推断。

@FunctionalInterface
interface TestInterface {
    void showList();
}

public class Test {
    public static void main(String[] args) {
        new TestInterface() {
            @Override
            public void showList() {
                System.out.println("我是一个函数式");
            }
        };
		
		// 没有变量引用:() -> System.out.println("我是一个函数式");
        TestInterface testInterface = () -> System.out.println("我是一个函数式");
    }
}

从匿名类到 Lambda 的转换举例2:

通过下面这个示例会发现,他其实就是在不断的优化,尽可能的让我们开发人员少写代码。他是如何做到一步一步省略的?其实这就是Java语法糖。

对于Java语法糖这里我就不过多提了,想了解的可以去看一下我的这一篇文章:
https://blog.csdn.net/weixin_43888891/article/details/124567498?spm=1001.2014.3001.5501

public static void main(String[] args) {
     // 匿名内部类创建多线程
     Runnable runnable = new Runnable() {
         @Override
         public void run() {
             System.out.println("我是一个线程");
         }
     };
     new Thread(runnable).start();

     // 省去runnable变量,这种方式等同于上面的
     new Thread(new Runnable() {
         @Override
         public void run() {
             System.out.println("我是一个线程");
         }
     }).start();

     // 使用Lambda表达式,实现多线程,省去了new Runnable和重写的方法名字
     new Thread(() -> {
         System.out.println("我是一个线程");
     }).start();

     // 优化Lambda,去掉大括号
     new Thread(() -> System.out.println("我是一个线程")).start();
 }

3.3 Lambda语法格式

代码说明:使用Lambda表达式。一般的格式是()-> 0,如果0里面只有一行代码,则0可以省略。
->左边的()表示参数列表,如果有形参,则在()中添加形参,->右边0表示具体逻辑。如果方法体返回值是void,则甚至可以在0中不写任何逻辑(当然也要结合场景)。返回值如果有值,则需要写具体的逻辑, return处理后的值。

在这里插入图片描述
在这里插入图片描述

3.4 类型推断

Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,由编译器推断出来的。这就是所谓的“类型推断”。

在这里插入图片描述
说白了就是利用泛型类的特性,直接可以让我们省去类型。

代码示例:

public static void main(String[] args) {
    // 匿名内部类创建 带有泛型参数的
    new Consumer<String>() {
        @Override
        public void accept(String o) {
            System.out.println(o);
        }
    };

    // 使用lambda进行改造
    Consumer<String> consumer = (String a) -> System.out.println(a);

    // 由于前面变量泛型声明了数据类型,通过引用变量的泛型类型,所以后面的lambda数据类型可以省略
    Consumer<String> consumer1 = (a) -> System.out.println(a);
}

Consumer是Java自带的函数式接口
在这里插入图片描述

3.5 参数传递

我们都知道lambda是一个实例,使用lambda的时候必须要有变量引用,除变量引用外,这样进行方法内的参数传递也是可以的

代码示例一:

@FunctionalInterface
interface MyFunc<T> {
    T getValue(T t);
}

public class Test {
    public static void main(String[] args) {
        
        // str的小括号是可以去掉的
        String abc = toUpperString((str) -> str.toUpperCase(), "abc");
        // String abc = toUpperString(str -> str.toUpperCase(), "abc");
        System.out.println(abc);
    }

    public static String toUpperString(MyFunc<String> consumer, String str) {
        return consumer.getValue(str);
    }
}

将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型 必须是 与该 Lambda 表达式兼容的函数式接口的类型。

代码示例二:

Java当中很多都是基于参数传递来使用lambda的,就拿集合的forEach来说。

import java.util.Arrays;
import java.util.List;

public class Test1 {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4);
        list.forEach(a -> System.out.println(a));
    }
}

3.6 引用

3.6.1 方法引用

什么是方法引用?

方法引用也是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

什么时候使用方法引用?

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

如下三种主要使用情况:

  1. 对象::实例方法名
  2. 类::静态方法名
  3. 类::实例方法名

格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来

ClassName::methodName

使用要求

实现接口的抽象方法的参数列表 和 返回值类型,必须与方法引用的方法的 参数列表 和 返回值类型 保持一致!

代码示例一:

Consumer消费型函数,也就是只有入参,没有返回值的。

public static void main(String[] args) {
    // 匿名函数创建
     Consumer consumer = new Consumer() {
         @Override
         public void accept(Object o) {
             System.out.println("消费型接口");
         }
     };

     // lambda创建
     Consumer consumer1 = (x) -> System.out.println(x);

     // 方法引用
     Consumer consumer2 = System.out::print;
 }

代码示例二:

Comparator 比较器 函数,有两个入参,和一个返回值。

public static void main(String[] args) {
    // 匿名内部类创建,<Integer>这个泛型一定要带,不然下面就只能是object
    Comparator comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer a, Integer b) {
            return Integer.compare(a, b);
        }
    };

    // lambda方式创建,<Integer>这个泛型一定要带,不然报错
    Comparator<Integer> comparator1 = (a, b) -> Integer.compare(a, b);

    // 方法引用,<Integer>这个泛型一定要带,不然报错
    Comparator<Integer> comparator2 = Integer::compare;
}

代码示例三:
BiPredicate是一个判定型的 函数,有两个入参,和一个Boolean返回值。

public static void main(String[] args) {
    // 这里的<String, String>类型不能丢,因为下面用的是String,如果不声明类型就是Object
    BiPredicate biPredicate = new BiPredicate<String, String>() {
        @Override
        public boolean test(String o, String o2) {
            return o.equals(o2);
        }
    };

    // 这里变量没有声明类型,那么就是Object,equals也是调用的Object的
    BiPredicate biPredicate1 = (a, b) -> a.equals(b);

    // 这里类型必须要写,因为后面写明了调用String的equals
    BiPredicate<String, String> biPredicate2 = String::equals;
}

3.6.2 构造器引用

格式: ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。

要求构造器参数列表要与接口中抽象方法的 参数列表一致!且方法的返回值即为构造器对应类的对象。

代码示例

import java.util.function.Function;

class Teacher {
    private String name;

    public String getName() {
        return name;
    }

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

    public Teacher(String name) {
        this.name = name;
    }
}

public class Test4 {
    public static void main(String[] args) {
        // 匿名内部类
        Function function = new Function<String, Teacher>() {
            @Override
            public Teacher apply(String name) {
                return new Teacher(name);
            }
        };

        // lambda
        Function<String, Teacher> function1 = (a) -> new Teacher(a);

        // 构造器引用
        Function<String, Teacher> function2 = Teacher::new;
    }
}

3.6.3 数组引用

格式: type[] :: new

public static void main(String[] args) {
    // 匿名内部类
    Function function = new Function<Integer, Integer[]>() {
        @Override
        public Integer[] apply(Integer x) {
            return new Integer[x];
        }
    };

    // lambda
    Function<Integer, Integer[]> function1 = a -> new Integer[a];

    // 数组引用
    Function<Integer, Integer[]> function2 = Integer[]::new;
}
  • 9
    点赞
  • 17
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:博客之星2021 设计师:Hiro_C 返回首页
评论 2

打赏作者

怪 咖@

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值