Bootstrap

Lambda表达式,函数式接口和方法引用

JDK8新特性

1.lambda表达式

概念:

  • Lambda表达式: 特殊的匿名内部类,语法更简洁
  • Lambda表达式允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递。

语法:

<函数时接口><变量名>=(参数1, 参数2...)->{
	//方法体
}

注意: 函数式接口: 接口中只有一个抽象方法。

(参数1,参数2):抽象方法的参数

->: 分隔符

{}:表示抽象方法的实现

例子:

public class Test {
    public static void main(String[] args) {
        MyRunnable my=new MyRunnable();
        Thread t1=new Thread(my);
        t1.start();

        //匿名内部类
        Runna匿名内部类ble r=new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类任务接口");
            }
        };
        Thread t2=new Thread(r);
        t2.start();
    }
}

class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("自定义任务接口");
    }
}

分析代码:

  • Thread 类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心
  • 为了指定run的方法体,不得不需要Runnable接口的实现类
  • 为了省去定义一个Runnable实现类的麻烦,不得不使用匿名内部类
  • 必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在。

遇到这样的情况可以使用lambda表达式完成上面的要求

前提:必须是函数式接口

//lambda表达式
Runnable r1=()->{
	System.out.println("lambda表达式");
};
Thread t3=new Thread(r1);
t3.start();

1.1 无参返回值

public class Test02 {
    public static void main(String[] args) {
        //匿名内部类
        Swimmable swimmable=new Swimmable() {
            @Override
            public void swimming() {
                System.out.println("使用匿名内部类");
            }
        };
        fun(swimmable);

        //lambda表达式
//        Swimmable s= () -> System.out.println("使用lambda表达式");
        fun(() -> System.out.println("使用lambda表达式"));
    }

    public static void fun(Swimmable s){
        s.swimming();
    }
}
interface Swimmable{
    public void swimming();
}

1.2 有参数有返回值的Lambda

下面举例演示java.util.Comparator接口的使用场景代码,其中的抽象方法定义为:

  • public abstract int compare(T o1, T o2);

当需要对一个对象集合进行排序时,Collections.sort方法需要一个Comparator 接口实例来指定排

序的规则。

public class Test03 {
    public static void main(String[] args) {
        List<Person> personList=new ArrayList<>();
        personList.add(new Person("A",15));
        personList.add(new Person("B",17));
        personList.add(new Person("C",16));

        /*Comparator<Person> comparator=new Comparator<Person>() {
            @Override
            //int:0表示新的和原来的相同;1:o2比o1大;-1:o2比o1小
            public int compare(Person o1, Person o2) {
                return o2.getAge()-o1.getAge();
            }
        };*/

        //lambda表达式
        Comparator<Person> comparator=(Person o1, Person o2)->{
            return o2.getAge()-o1.getAge();
        };
        Collections.sort(personList,comparator);
        for (Person person : personList) {
            System.out.println(person);
        }
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person{
    private String name;
    private int age;
}

1.3详细介绍lambda表达式

  • Lambda引入了新的操作符:->(箭头操作符),->将表达式分成两部分
    • 左侧:(参数1,参数2…)表示参数列表
    • 右侧:{}内部是方法体
  • 注意事项
    • 形参列表的数据类型会自动推断
    • 如果形参列表为空,只需保留()
    • 如果形参只有1个,()可以省略,只需要参数的名称即可
    • 如果执行语句只有一句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有一句
    • Lambda不会生成一个单独的内部类文件

2. 函数式接口

  • 如果一个接口只有一个抽象方法,则该接口称之为函数式接口,函数式接口可以使用Lambda表达式,Lambda表达式会被匹配到这个抽象方法上。
  • **@Functionallnterface ** 注解检测接口是否符合函数式接口。

内置函数式接口的由来

public class Test {
    public static void main(String[] args) {
        Operator o=arr -> {
          int sum=0;
            for (int i : arr) {
                sum+=i;
            }
            System.out.println("数组的和为:"+sum);
        };
        fun(o);
    }

    public static void fun(Operator operator){
        int[] arr={2,3,4,5,8,11};
        operator.getSum(arr);
    }
}
@FunctionalInterface
interface Operator{
    //求数组的和
    public abstract void getSum(int[] arr);
}

分析

我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方
法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大
量常用的函数式接口。

常见的函数式接口

都在java.util.function包下

请添加图片描述

4.1 Consumer

有参,无返回值 
public class Test {
    public static void main(String[] args) {
        Consumer<Double> c= t->{
            System.out.println("吃饭花了:"+t);
        };
        fun01(c,55);
    }

    //调用某个方法时,该方法需要的参数为接口类型,这时应该使用lambda
    public static void fun01(Consumer<Double> consumer,double money){
        consumer.accept(money);
    }
}

4.2 Supplier供给型函数式接口

无参,有返回值

T get();
public class Test02 {
    public static void main(String[] args) {
//        Supplier<Integer> supplier= () -> new Random().nextInt(10);
        
        fun(() -> new Random().nextInt(10));
    }

    public static void fun(Supplier<Integer> supplier){
        Integer result = supplier.get();
        System.out.println("内容为:"+result);
    }
}

4.3 Function<T,R> 函数型函数式接口

T:参数类型的泛型

R:函数返回结果的泛型

有参,有返回值

例子:传入一个字符串把小写转换为大写

public class Test03 {
    public static void main(String[] args) {

        fun((t)-> t.toUpperCase(),"hello world");
    }

    public static void fun(Function<String,String> function,String msg){
        String s = function.apply(msg);
        System.out.println("结果为:"+s);
    }
}

4.4 Predicate

T:参数的泛型

boolean test<T,t>

当传入一个参数时,需要对该参数进行判断时,则需要这种函数。

public class Test04 {
    public static void main(String[] args) {

        fun(t-> t.length()>3?true:false,"迪丽热巴");
    }

    public static void fun(Predicate<String> predicate,String name){
        boolean b = predicate.test(name);
        System.out.println("该名称的长度是否长啊:"+b);
    }
}

5. 方法引用

方法引用的使用场景

我们用Lambda表达式来实现匿名方法。但有些情况下,我们用Lambda表达式仅仅是调用一些已经
存在的方法,除了调用方法外,没有其他任何多余的动作
,在这种情况下,我们倾向于通过方法名来
调用它,而Lambda表达式可以帮助我们实现这一要求,它使得Lambda在调用那些已经拥有方法名
的方法的代码更简洁、更容易理解。方法引用可以理解为Lambda表达式的另外一种表现形式。

5.1 lambda表达式的冗余

public class Test01 {
    public static void main(String[] args) {
        Consumer<Integer[]> consumer=arr->{
            int sum=0;
            for (int i : arr) {
                sum+=i;
            }
            System.out.println("数组的和为:"+sum);
        };
        fun(consumer);
    }

    public static void fun(Consumer<Integer[]> consumer){
        Integer[] arr={1,2,3,4,5,6};
        consumer.accept(arr);
    }

    public static void sum(Integer[] arr){
        int sum=0;
        for (int i : arr) {
            sum+=i;
        }
        System.out.println("数组的和为:"+sum);
    }
}

分析:

如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接"引用"过去就好了: --方法引用

public class Test01 {
    public static void main(String[] args) {        
        Consumer<Integer[]> c=Test01::sum;
        fun(c);
    }

    public static void fun(Consumer<Integer[]> consumer){
        Integer[] arr={1,2,3,4,5,6};
        consumer.accept(arr);
    }

    public static void sum(Integer[] arr){
        int sum=0;
        for (int i : arr) {
            sum+=i;
        }
        System.out.println("数组的和为:"+sum);
    }
}

请注意其中的双冒号 :: 写法,这被称为“方法引用”,是一种新的语法。

5.2 什么是方法引用

方法引用通过方法的名字来指向一个方法。 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 方法引用使用一对冒号 :: 。

方法引用的分类

类型语法对应的Lambda表达式
静态方法引用类名::staticMethod(args)->类名.staticMethod(args)
实例方法引用inst::instMethod(args) -> inst.instMethod(args)
对象方法引用类名::instMethod(inst,args) -> inst.instMethod(args)
构建方法引用类名::new(args) -> new 类名(args)
public class Test {
    public static void main(String[] args) {
        //void accept(T t);
//        Consumer<String> consumer=t->{
//            PrintStream out =System.out;
//            out.println(t);
//        };
        //方法引用:如果Lambda表达方法体中只是调用一个特定得已经存在得方法,则可以使用方法引用
        //对象::实例方法
//        Consumer<String> consumer2=System.out::println;
//        consumer2.accept("你是XXX?");

        //类::静态方法  int compare(T o1,T o2);
//        Comparator<Integer> comparator=(o1, o2) -> Integer.compare(o1,o2);
//        Comparator<Integer> comparator=Integer::compare;
//        int compare = comparator.compare(18, 18);
//        System.out.println(compare);

        //类名::实例方法  R apply(T t);
//        Function<Emp,String> function=e->{
//            return e.getName();
//        };
//        Function<Emp,String> function=Emp::getName;
//        System.out.println(function.apply(new Emp("张三")));

        //类名::new
//        Supplier<Emp> supplier=new supplier<Emp>() {
//            @Override
//            public Emp get() {
//                return new Emp("刘德华");
//            }
//        };
        Supplier<Emp> supplier=Emp::new; //必须该类中存在无参构造函数。
        System.out.println(supplier.get());
    }
}
;