您现在的位置是:首页 >技术教程 >Lambda表达式和函数式接口详解网站首页技术教程

Lambda表达式和函数式接口详解

凤梨壳 2023-05-26 20:00:04
简介Lambda表达式和函数式接口详解

一,前言:三大编程范式

编程语法发展了几十年,各种编程语言百花齐放,如常年霸榜的C,Python,Java。经历如此漫长的演进,编程界也逐步衍生出了三大编程范式。

那么什么是编程范式呢?

编程范式是指软件工程中的一类典型的编程风格,是设计程序结构所采用的设计风格。目前主流的编程范式有:结构化编程、函数式编程、面向对象编程等。不同的编程范式有不同的特点和适用场景。

结构化编程

即面向过程编程,是一种编程范式,是一种自顶向下的编程思想,它采用子程序、程序码区块、for循环以及while循环等结构来组织代码。结构化的程序是以一些简单、有层次的程序流程架构所组成,可分为顺序(sequence)、选择(selection)及循环(repetition)。C语言就是一种典型的结构化编程语言。

结构化语言比非结构化语言更易于程序设计,它完全符合人的思维习惯,过程即流程,就是指做事的步骤:先干什么,在干什么,后干什么,基于该思想编写程序就好比在设计一条流水线。在Java中,main方法就是这么一条流水线。

面向对象编程

面向对象编程(Object-Oriented Programming,简称OOP)是一种主流的编程范式,它以对象作为程序的基本单元,将数据和操作数据的方法组成对象,以此来封装一个或多个功能。面向对象编程中的对象是指一个类的实例,它可以拥有属性和方法。面向对象编程的核心在于抽象,提供清晰的对象边界。结合封装、集成、多态特性,降低了代码的耦合度,提升了系统的可维护性。

在面向对象的程序设计中,要记住一点,就是万物皆对象。它也是尽可能模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程。面向对象编程语言的代表作就是C++和Java。

函数式编程

函数式编程也是一种编程范式,它将计算视为数学函数的求值,通过应用和组合函数来构建程序。函数式编程的核心思想是将计算过程尽量分解成一系列可复用的函数,每个函数都接受一个或多个输入参数,返回一个或多个输出结果。在函数式编程中,函数被视为第一等公民。所谓”第一等公民“,指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

面向对象式编程是用名词抽象世界,而函数式编程是用动词抽象世界。典型的函数式编程语言就是Scala,笔者也是在学习spark过程中接触到的这门语言,里面的最基本的特色就是各种Lambda表达式,还有各种语法糖简写,当时就感觉特别的新奇。函数式编程在使用的时候的特点就是,你已经不知道数据是从哪里来了,每一个函数都是为了用小函数组织成更大的函数,函数的参数也是函数,函数返回的也是函数,最后得到一个超级牛逼的函数,就等着别人用他来写一个main函数把数据灌进去了。

二,Lambda真知灼见

Lambda表达式就是一个匿名函数 ,且可以作为一个函数的参数去传递。本质上Lambda表达式是匿名内部类的一种改写形式,可以看做一种接口类型,所以才可以作为参数、变量去传递。

Lambda表达式的语法形式:( 参数列表 ) -> { 方法体 }
简写逻辑:如果参数只有一个,可以省略“()”,如果方法体只有一行代码,可以省略“{}”。

Tip:参数列表中的参数是不需要声明类型的,会自动匹配接口抽象方法中的形参列表。

PS:匿名内部类能改写成lambda表达式是有条件的:实现的接口必须是一个函数式接口,一般带有一个注解标记@FunctionalInterface

1,匿名内部类

顾名思义,内部类就是在一个类的内部定义的类,而匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。使用匿名内部类有个前提条件:必须继承一个抽象类或实现一个接口,所以匿名内部类本质上是一个对象,是实现了该接口或继承了该抽象类的子类对象。

匿名内部类语法格式:

new `抽象类名|接口名`() {
        实现方法;
    };

2,Functional接口

在这里插入图片描述

Functional接口,即函数式接口。目前各大文章上给出定义是:有且仅有一个抽象方法的接口。这个定义准确来说是有误的。准确的定义应该是:有且仅有一个待实现的抽象方法的接口

就比如上图所示的接口Comparator<T>,这是java提供的比较器接口,是一个Functional接口。细心的同学肯定发现了,这个接口中不仅只有一个抽象方法compare,还有一个equals方法,所以它有两个抽象方法,这就推翻了上述 “有且仅有一个抽象方法” 的定义 。但是equals方法并不需要自己实现,这是因为每个类都默认继承了Object类,在Object中已经提供了对equals方法的实现。

所以,Function接口准确的定义应该是:有且仅有一个待实现的抽象方法的接口。每一个该类型的lambda表达式都会被匹配到这个抽象方法。如果想给接口添加额外的功能,你可以给你的函数式接口添加默认方法。

我们可以将lambda表达式当作实现这个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface注解即可,编译器如果发现你标注了这个注解的接口多于一个待实现的抽象方法的时候会给出如下提示。
“Multiple non-overriding abstract methods found in interface java8.lambda.Adder”

函数式接口示例:

@FunctionalInterface
interface Action<T,F>{
    T run(F f);
}

3,Lambda改写案例

我们通过一个排序的例子来认识下,如何将一个啰嗦的匿名内部类改写成一个清爽的Lambda表达式。

public class LambdaDemo1 {
    public static void main(String[] args) {
        //假如一个list中的元素要排序
        List<String> list = Arrays.asList("hello","tom","apple","bbc");
        //之前的排序是这样写的
        Collections.sort(list, new Comparator<String>(){
            @Override
            public int compare(String o1, String o2) {
                return -o1.compareTo(o2);
            }
        });
        System.out.println(list.toString());
    }
}

使用Lambda改写后

public class LambdaDemo1 {
    public static void main(String[] args) {
        //假如一个list中的元素要排序
        List<String> list = Arrays.asList("hello","tom","apple","bbc");
        //使用Lambda表达式,如果是单条语句,可以省略大括号
        Collections.sort(list, (o1, o2) -> -o1.compareTo(o2));
        System.out.println(list.toString());
    }
}

当然,目前的IDEA非常的智能,将鼠标移动到匿名内部类上面会提示你是否改写成lambda表达式,这个功能可以说非常得便捷实用。

上面我们用的是java内置的Functional接口,接下来我们再自己手动实现一个Functional接口来体验下lambda的用法。

自定义Functional接口

@FunctionalInterface
public interface Adder {
    int add(int a,int b);
}

匿名内部类实现一个加法运算

public class AdderTest {
    public static void main(String[] args) {
        Adder adder = new Adder() {
            @Override
            public int add(int a, int b) {
                return a + b;
            }
        };

        System.out.println(adder.add(1,2));
    }
}

改写成Lambda形式

public class AdderTest {
    public static void main(String[] args) {
        Adder adder = (a, b) -> a + b;

        System.out.println(adder.add(1,2));
    }
}

三,Lambda语法糖

若Lambda体中的内容有方法已经实现了,即有现成的方法,它的参数个数、类型及返回值类型和函数式接口中的抽象方法完全一致,那么可以使用方法引用 ,也可以理解为方法引用是Lambda表达式的另外一种表现形式,并且其语法比lambda表达式更加简单。Java 8 允许你使用::关键字来引用方法。

1,静态方法引用

形式:类名::方法名

@FunctionalInterface
public interface Action {
    public String run(int Integer);
}
public class LambdaTest {
    public static void main(String[] args) {
        Action action=Integer::toString;
        System.out.println(action.run(9));
    }
}

2,实例方法引用

形式:实例名::方法名

public class LambdaTest01 {
    public String test(int i){
        return "i="+i;
    }
    public static void main(String[] args) {
        LambdaTest01 t = new LambdaTest01();
        Action action=t::test;
        System.out.println(action.run(10));
    }
}
//特别的:System.out::println
public class LambdaTest03 {
    public static void main(String[] args) {
        Consumer<Integer> con = System.out::println;
        con.accept(200);
    }
}

3,构造方法引用

形式:类名::new | 数组类型[]::new

//定义一个实体类People
public class People {
    private String name;
    public People() {

    }
    public People(String name) {
        this.name = name;
    }
    public void say(){
        System.out.println("我的名字是:"+name);
    }
}
//定义函数式接口CreatePeople
@FunctionalInterface
public interface CreatePeople {
    public People create(String name);
}
public class LambdaTest04 {
    public static void main(String[] args) {
        //会调用相应参数的构造器
        CreatePeople createPeople=People::new;
        People people = createPeople.create("kangkang");
        people.say();
    }
}
//数组引用案例
public class LambdaTest06 {
    public static void main(String[] args) {
        Function<Integer, String[]> fun1 = (x) -> new String[x];
        Function<Integer, String[]> fun2 = String[]::new;
        String[] array = fun2.apply(10);
    }
}

四,四大通用函数式接口

附表:

接口名抽象方法特点
Predicateboolean test(T t)传入T,返回布尔
SupplierT get()无中生有
Consumervoid accept(T t)有中生无
Function<T, R>R apply(T t)变T为R

1,Predicate

断言,进行条件判断的接口

有参,有返回值,传入一个T,返回一个boolean

示例:

public class PredicateTest {
    public static void main(String[] args) {
        List<String> lists = Arrays.asList("Java", "html5","JavaScript", "C++", "hibernate", "PHP");
        //判断列表长度是否大于10
        Predicate<List> p1= list -> list.size()>10;
        boolean ans = p1.test(lists);
        System.out.println(ans);

        //判断列表是否包含"Java"
        Predicate<List> p2= list -> list.contains("Java");
        boolean ans2 = p2.test(lists);
        System.out.println(ans2);
    }
}

2,Supplier

生产者接口,创造、生产、无中生有

无参,有返回值T

示例:

public class SupplierTest {
    public static void main(String[] args) {
        //生成一个八位的随机字符串
        Supplier<String> supplier = ()->{
            String base = "abcdefghijklmnopqrstuvwxyz0123456789";
            Random random = new Random();
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < 8; i++) {
                //生成[0,base.length)之间的随机数
                int number = random.nextInt(base.length());
                sb.append(base.charAt(number));
            }
            return sb.toString();
        };
        String ans = supplier.get();
        System.out.println(ans);
    }
}

3,Consumer

消费型接口

消费一个T,不返回任何值

示例:

public class ConsumerTest {
    public static void main(String[] args) {
        Consumer<String>  consumer= System.out::print;
        consumer.accept("打印字符串");
    }
}

4,Function<T, R>

转换接口

有参,有返回值,给定一个T,将之转换为R,并返回。

接口附带默认方法:

"compose(Function)"方法表示在某个方法之前执行,返回的仍是一个Function类型

"andThen(Function)"方法表示在某个方法之后执行,返回的仍是一个Function类型

示例:

public class FunctionTest {
    public static void main(String[] args) {
        //传入一个String类型名称,返回一个people对象
        Function<String,People> func= People::new;
        People p1=func.apply("kangkang");

        //需求改变:创建对象前给name加一个姓氏前缀,创建对象后获取姓名长度
        Function<String,String> before = s ->"wang-"+s;
        Function<People,Integer> after= s -> s.getName().length();
        //这段代码需要好好理解,逻辑是先对T应用before规则,再对R应用after规则
        Integer len = func.compose(before).andThen(after).apply("kangkang");
        System.out.println("姓名长度:"+len);
    }
}

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。