初识Lambda函数式编程

2021/7/18 11:06:21

本文主要是介绍初识Lambda函数式编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1、Lambda 入门

1.1、从一个需求例子开始

需求:对一个整型数组进行排序,使用冒泡排序,如何进行优化,如何灵活实现排序规则。

1.2、方案一:在 sort 方法中增加一个参数控制排序规则

/**
     *
     * @param datas 数组
     * @param sortType true 表示升序,false 表示降序
     */
    public static void sort(int[] datas,boolean sortType){
        for (int i = 0; i < datas.length; i++) {
            for (int j = i+1; j < datas.length; j++) {
                if(sortType){
                    //两两元素比较,前一个数大于后一个数则交换位置,最终实现升序
                    if(datas[i]>datas[j]){
                        //交换位置
                        int temp = datas[i];
                        datas[i] = datas[j];
                        datas[j] = temp;
                    }
                }else{
                    //两两元素比较,前一个数小于后一个数则交换位置,最终实现降序
                    if(datas[i]<datas[j]){
                        //交换位置
                        int temp = datas[i];
                        datas[i] = datas[j];
                        datas[j] = temp;
                    }
                }
            }
        }
    }

问题:代码冗余严重!!

1.3、方案二:将元素比较的代码抽取到一个接口中

public interface SortCompare {
 boolean compare(int a,int b);
}
public class SortCompareImpl implements  SortCompare{

    @Override
    public boolean compare(int a, int b) {
        //前一个是否大于后一个数
        return a>b;
    }
}

就可以使用一个方法实现不同的排序规则,方法的代码不再繁琐

//增加一个参数,就是排序比较接口的对象
    public static void sort(int[] datas,SortCompare sortCompare){
        for (int i = 0; i < datas.length; i++) {
            for (int j = i+1; j < datas.length; j++) {
                if(sortCompare.compare(datas[i],datas[j])){
                    //交换位置
                    int temp = datas[i];
                    datas[i] = datas[j];
                    datas[j] = temp;
                }
            }
        }
    }

问题:为了封装一行比较代码就需要定义多个接口实现类,这个过程繁琐的。

1.4、方案 3:匿名内部类实现比较接口

//匿名内部类,实现升序
        sort(datas, new SortCompare() {
            @Override
            public boolean compare(int a, int b) {
                return a>b;
            }
        });

优点:不用再定义独立的类,代码逻辑也很简洁。

问题:封装一行比较的代码,但是需要定义一些无关的代码。

1.5、Lambda表达式实现

sort(datas,(int a,int b)->a<b);//降序

Lambda 是一种匿名内部类的简化形式。

Lamdba 表达式可以使编程过程更高效。

语法: (参数,参数) –> {方法体代码}

1、 参数列表

2、 箭头

3、 方法体代码

2、Lambda语法规则

2.1、Lambda 表达式语法格式

Lambda 是一种匿名内部类的简化形式。

注意:不是所有的匿名内部类可以使用 Lambda 表达式来实现的。

Lambda 表达式只能代替有一个抽 象方法的接口所对应的匿名内部类。

Lamdba 表达式可以使编程过程更高效。

语法: (参数,参数) –> {方法体代码}

1、 参数列表

2、 箭头

3、 方法体代码

2.2、Lambda表达式测试

1、 无参、无返值 2、 有参、有返回值

定义 Fun1、Fun2 两个接口。

 2.3、函数式接口

什么是函数接口?

只有一个抽象方法的接口叫函数式接口。

Lambda 表达式只能赋值给函数式接口的引用变量。

2.4、 简化形式

1、 参数类型可以省略。 注意:如果有多个参数,参数类型要么都省略,要么都不省略

 2、 如果函数体只有一条语句可以省略大括号和分号及 return

3、 如果参数只有一个,可以省略参数列表的小括号

 2.5、函数式编程思想

函数式编程和面向对象编程对比:

1) 核心不同

面向对象编程,它的核心是对象。

函数式编程,它的核心是函数。方法就是函数。

2) 目标不同

面向对象,是需要分析对象的属性,对象的行为。

函数式编程,首先确定要做什么事,也就确定了函数的功能。

 3、java.util.function 函数式接口

 3.1、认识函数式接口

函数式接口,是只有一个抽象方法的接口叫函数式接口。

比较常用的几个接口 

                  接口         抽象方法                说明
Function<T, R>
R apply(T t);
接收一个参数,有返回值,接收参数类型T,返回参数类型R
Consumer<T>
void accept(T t);
接收一个参数,无返回值
Supplier<T>
T get();
没有参数,有返回值
Predicate<T>
boolean test(T t);
接收一个参数,返回布尔值
@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

@FunctionalInterface 它是一个注解,标记在这个接口上就表示该接口是一个函数式接口。

在一个函数式接口中不标记@FunctionalInterface,也是可以的!

3.2、Function 函数式接

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

andThen 方法: 原型:

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

参数:也是一个 Function 函数式接口类型

结果:也是一个 Function 函数式接口类型

内容:return (T t) -> after.apply(apply(t));

//测试andThen方法
    public static void testAndThen(){
        //前边的一个操作
        Function<String,String> before = s1 -> "www."+s1;
        //后边的一个操作
        Function<String,String> after = s1 -> s1+".com";
        //调用before的andThen方法,实现前后执行两个操作
        String pbteach = before.andThen(after).apply("pbteach");
        System.out.println(pbteach);
    }

 3.3、BiFunction 函数式接口

@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

apply 方法有两个参数,一个返回值

public class TestBiFunction {

    public static void main(String[] args) {
        //求两个数的和
        BiFunction<Integer, Integer, Integer> fun1 = (x, y) -> x + y;
        //求两个数的乘积
        BiFunction<Integer, Integer, Integer> fun2 = (x, y) -> x * y;
        //调用方法
        System.out.println(operate(fun1, 1, 2));
        System.out.println(operate(fun2, 1, 2));
        //简化
        System.out.println(operate((x, y) -> x * y, 1, 2));
    }

    //实现两个数运算
    public static int operate(BiFunction<Integer, Integer, Integer> fun, int n1, int n2) {
        return fun.apply(n1, n2);

    }
}

3.4、Consumer 接口

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Consumer 是一个消费接口 ,接收一个数据进行消费。

public class TestConsumer {
    //一个整型数组
    static int[] datas = {1,2,3,4,5,6,7};
    //静态变量
    static int sum = 0;
    public static void main(String[] args) {
        //定义一个Consumer接口对象
        Consumer<Integer> fun1 = n -> System.out.println(n);
        //判断奇偶
        Consumer<Integer> fun2 = n ->{
            if(n % 2 == 0){
                System.out.println(n + "是偶数");
            }else{
                System.out.println(n + "是奇数");
            }
        };
        //求累加和
        Consumer<Integer> fun3 = n -> sum+=n;
       //调用consumer
        consumer(fun1,datas);
        System.out.println("===========判断奇偶=========");
        consumer(fun2,datas);
        System.out.println("===========求累加和=========");
        consumer(fun3,datas);
        System.out.println("sum="+sum);
    }

    //消费方法
    public static void consumer(Consumer<Integer> fun,int[] datas){
        for (int i = 0; i < datas.length; i++) {
             //调用accept方法进行消费,一次消费一个数据
            fun.accept(datas[i]);
        }
    }
}

3.5、Predicate 函数式接口

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t); // 接收一个参数,得到一个布尔类型的结果,用于判断,返回判断结果。

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

  
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
public class TestPredicate {

    //一个整型数组
    static int[] datas = {1,2,3,4,5,6,7};

    public static void main(String[] args) {
        //定义Lambda表达式,实现奇偶判断
        Predicate<Integer> filter1 = n ->{
            if(n % 2 ==0){
                return true;
            }else{
                return false;
            }
        };
        System.out.println("========奇偶判断=======");
        int[] datas1 = doFilter(filter1, datas);
        for (int i = 0; i < datas1.length; i++) {
            System.out.println(datas1[i]);
        }
        System.out.println("========大于5的数=======");
        //定义一个过虑器
        Predicate<Integer> filter2 = n -> n>5;
        int[] datas2 = doFilter(filter2, datas1);
        for (int i = 0; i < datas2.length; i++) {

            System.out.println(datas2[i]);
        }
    }


    /**
     * 对数据进行过虑
     * @param filter 过虑器
     * @param datas 待过虑的数组
     * @return 保留数据的数组
     */
    public static int[] doFilter(Predicate<Integer> filter,int[] datas){

        //新建一个数组,用于存储保留数据
        int[] result = new int[datas.length];
        //保留数组的下标
        int n=0;
        for (int i = 0; i < datas.length; i++) {
            if(filter.test(datas[i])){
                //保留数据
                result[n++] = datas[i];
            }
        }

        //最后只返回一个正好包含保留数据容量的数组
        //参数1:原始数组,参数2:需要保留数组长度
        int[] newResult = Arrays.copyOf(result, n);
        return newResult;
    }

}

3.6、Supplier 函数接口

@FunctionalInterface
public interface Supplier<T> {

    T get();
}

Supplier 接口表示供应,和 Consumer 相反,Supplier 是一个生产接口,T get()没有参数,只有结果。

public class TestSupplier {

    static long sequence = 0;

    //生成不同类型的编号,比如:订单号、课程编号等,采用方法是:随机,顺序编号,时间编号

    //定义随机数的生成器,Math.abs获取绝对值
    static Supplier<Long> randomNum = () -> Math.abs(new Random().nextLong());

    //顺序编号
    static Supplier<Long> sequenceNum = () -> ++sequence;

    //时间编号,获取当前时间纳秒值
    static Supplier<Long> timeNum = () -> System.nanoTime();

    public static void main(String[] args) {

        System.out.println("=========生产随机数========");
        for (int i = 0; i < 10; i++) {
            System.out.println(randomNum.get());
        }
        System.out.println("=========生产顺序编号========");
        for (int i = 0; i < 10; i++) {
            System.out.println(sequenceNum.get());
        }
        System.out.println("=========生产时间编号========");
        for (int i = 0; i < 10; i++) {
            System.out.println(timeNum.get());
        }
    }
    
}

4、方法引用

4.1、方法引用入门

Lambda 表达式要对应一个函数式接口,具体依据函数式接口中的抽象方法来定义

Lambda。使用 Lambda 表达式进行函数式编程,使用 Lambda 表达式所定义是一个函数,在 Java 中方法就是函数。

什么是方法引用?

Java 中的方法可以当作一个引用作为函数来使用,它叫方法引用。方法引用是一种将方法应用于函 数式编程的方法,方法引用可以代替 Lambda。

简写为: 将 Java 中方法作一个引用,代替 Lambda 表达式。

1、 定义一个函数式接口

public interface Fun2 {
 //有参数有返回值
 int handler(int a,int b);
}

2、 使用 Lambda 表达式测试

//使用 Lambda 表达式测试
doFun2((x,y)->{
 int z = x * y;
 return z;
});

依据函数式接口中的抽象方法来定义 Lambda 表达式.

3、使用方法引用代替 Lambda 表达式

1) 定义一个方法

如何定义一个可以代替上边这个 Lambda 表达式的方法呢? 依据函数式接口中的抽象方法来定义 一个可以代替 Lambda 表达式的方法。

public class Utils {
 //实现两个数的乘积
 public static int product(int a,int b){
 return a * b;
 }
}

2) 可以使用方法引用来代替 Lambda
 

//使用方法引用 
doFun2(Utils::product);

Utils::product 表示引用了 Utils 类中的 product 方法,::符号为引用运算符,它所在的表达式被称为 方法引用。

小结: 一个函数式接口,可以使用 Lambda 表达式来实现,也可以使用方法引用实现。 最关键的是知道如何定义一个 Lambda 和方法引用。

一个方法引用所引用的方法必须和函数式接口中的抽象方法的参数、返回值类型一致

4.2、静态方法引用

类型语法对应的lambda表达式
静态方法引用类名::staticMethod(args) -> 类名.staticMethod(args)
特定对象实例方法引用instance::instanceMethod(args) -> instance.instanceMethod(args)
任意对象实例方法引用类名::instanceMethod(instance,args) -> instance.instanceMethod(args)
构造方法引用类名::new(args) -> new 类名(args)

1、 确定函数式接口

@FunctionalInterface
public interface BiFunction<T, U, R> {
 /**
 * Applies this function to the given arguments.
 *
 * @param t the first function argument
 * @param u the second function argument
 * @return the function result
 */
 R apply(T t, U u);
}

2、 使用方法引用

public class TestStaticFun {
    public static void main(String[] args) {
        //使用Lambda表达式实现
        System.out.println(operate((x,y)->x+y,1,2));
        //使用方法引用,求两个数的和
        System.out.println(operate(Integer::sum,1,2));
        //测试当函数式接口的抽象方法的返回值为void时,仍然可以使用一个返回值不为void的方法引用
        Consumer<String> fun  = TestStaticFun::toUpperCase;
        fun.accept("www.pbteach.com");
    }

    //实现两个数的操作
    public static int operate(BiFunction<Integer,Integer,Integer> fun,int n1,int n2){
        return fun.apply(n1,n2);
    }
    /**
     * 将字符串转大写
     * @param s 输入一个字符串
     * @return 转大写后的字符串
     */
    public static String toUpperCase(String s){
        System.out.println("s="+s);
        //将字符串转在大写
        return s.toUpperCase();
    }
}

4.3、抽象方法为 void 的情况

@FunctionalInterface
public interface Consumer<T> {
 void accept(T t);
}

根据前边的知识可知,要想一个方法引用和上边的 Consumer 对应上,这个方法的参数类型和返回 值类型要和函数式接口的抽象方法一致。void accept(T t);

所引用的方法不管是否有返回值都可以和函数式接口对应上。

使用上边的函数式接口来测试,用一个不为 void 返回值的方法引用,和 Consumer 接口对应上。 还是建议:定义一个方法引用,依据函数式接口中的抽象方法来定义,让引用方法的参数和返回值 类型与函数式接口中的抽象方法的参数和返回值一致!!!

4.4、特定实例方法引用

指: 引用某个对象的方法,叫特定实例方法的引用。

//特定实例方法引用,引用某个对象的实例方法
public static void test1(){
 //创建一个对象
 PbStudent s1 = new PbStudent();
 s1.setNickname("攀博课堂");
 //引用 s1 对象的 getNickname 方法,方法引用赋值给 Supplier 函数接口 ,它的 T get();抽象方
法与 getNickname 一致
 Supplier<String> fun1 = s1::getNickname;
 //获取学生昵称,fun1.get()相当于调用了 s1 对象的 getNickname
 System.out.println(fun1.get());
 //引用 s1 对象的 setNickname
 Consumer<String> fun2 = s1::setNickname;
 System.out.println("设置学生昵称");
 fun2.accept("攀博课堂 www.pbteach.com");
 System.out.println("学生新昵称:"+fun1.get());
}

4.5、任意对象实例方法引用

它和特定对象实例方法引用区别:

特定对象实例方法:

引用某个类型的某个对象的实例方法。

例如:s1::getNickname,表示引用 s1 对象的 getNickname 方法。

语法:实例名::实例方法名

任意对象实例方法:

引用某个类型的所有对象的实例方法。

例如:PbStudent::getNickname,表示引用 PbStudent 这个类型的所有对象的 getNickname 方法。

语法:类名::实例方法名

任意对象实例方法引用规则:

1、固定有第一个参数,是实例的类型。

2、 从第二个开始才是实例方法的参数。

//任意实例方法引用
 public static void test2(){
 //引用 PbStudent 类型的任意对象的 setNickname 方法
 BiConsumer<PbStudent, String> setNickname = PbStudent::setNickname;
 //引用 PbStudent 类型的任意对象的 getNickname 方法
 Function<PbStudent,String> getNickname = PbStudent::getNickname;
 //通过 setNickname 方法引用设置昵称
 PbStudent s1 = new PbStudent();
 setNickname.accept(s1,"攀博课堂");
 //通过 getNickname 方法引用获取昵称
 String nickName = getNickname.apply(s1);
 System.out.println(nickName);
// System.out.println(s1.getNickname());
 }

一个案例: 如何使用任意对象实例方法引用,引用下边的 String 类下的 charAt 方法。

实现:获取字符串某个索引位置上的字符。

public char charAt(int index) {
 if ((index < 0) || (index >= value.length)) {
 throw new StringIndexOutOfBoundsException(index);
 }
 return value[index];
}

分析:charAt 方法有一个参数,一个返回值。 如果要使用任意对象实例方法引用,找一个函数式接口,它的抽象方法,参数有两个,必须有返回值。

//测试任意对象实例方法的引用,测试 String 类型下的 charAt 方法
public static void test3(){
//引用 String 类型下的 charAt 方法
 BiFunction<String,Integer,Character> fun = String::charAt;
 System.out.println(fun.apply("www.pbteach.com",4));
 System.out.println(fun.apply("www.pbteach.com",5));
}

4.6、构造方法引用

1) 无参构造方法 语法:类名::new。

引用的无参构造方法,对应哪个函数式接口,这个函数式接口的抽象方法没有参数,必须有一个返 回值。

使用 Supplier 函数式接口。

//无参构造方法引用
public static void test1(){
 //引用 PbStudent 类型的无参构造方法
 Supplier<PbStudent> fun = PbStudent::new;
 //创建学生对象
 PbStudent pbStudent = fun.get();
 System.out.println(pbStudent);
}

2)有参构造方法

语法:类名::new 引用有参构造方法,根据函数式接口确定要引用哪个有参构造方法。

//有参构造方法引用
public static void test2(){
 //引用 PbStudeng 类型下的 pbstudent(String nickname)构造方法。
 Function<String,PbStudent> fun = PbStudent::new;
 //通过方法引用调用有参构造方法
 PbStudent pbStudent = fun.apply("www.pbteach.com");
 System.out.println("昵称:"+pbStudent.getNickname());
 //引用 PbStudeng 类型下的 PbStudent(String id,String nickName)
 BiFunction<String,String,PbStudent> fun2 = PbStudent::new;
 PbStudent pbStudent1 = fun2.apply("100", "攀博课堂");
 System.out.println(pbStudent1.getId()+" "+pbStudent1.getNickname());
}


这篇关于初识Lambda函数式编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程