[Java] 函数式接口

36 篇文章 0 订阅

第一章 函数式接口

1.1 概念

函数式接口在java中是指: 有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,java中的Lambda才能顺利地进行推导。

备注: “语法糖”是指使用更加方便,但是原理不变的代码语法。 例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。 从应用层面来讲,java中的Lambda可以被当作是匿名内部类的“语法糖”,但是二者在原理上是不一样的(匿名内部类会在编译的时候生成新的class文件,但是lambda不会)。

1.2 格式

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称{
    public abstract 返回值类型 方法名称(可选参数信息); //前面的修饰符可以省略
    //其他非抽象方法的内容(默认,静态,私有)
}

1.3 @FunctionalInterface注解

@override注解的作用类似,Java8中专门为函数式接口引入了一个新的注解@FunctionalInterface.该注解可用于一个接口的定义上:

@FunctionalInterface
public interface YourFather {
    void method();//定义了一个抽象方法
    
}

作用: 他会自动检测你这个接口是否符合函数式接口的定义,如果不符合(接口中没有抽象方法或者多余一个),那么这个注解就会报错

1.4 自定义函数式接口与使用

自定义函数式接口

@FunctionalInterface
public interface YourFather {
    void method();//定义了一个抽象方法

}

接口的实现类

public class YourFatherC implements YourFather {
    @Override
    public void method() {
        System.out.println("我是你爹");
    }
}

使用

  1. 可以作为方法的参数
public class Main {
    //作为参数
    public static void show(YourFather yourFather){
        yourFather.method();
    }

    public static void main(String[] args) {
        //调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
        show(new YourFatherC());

        //匿名内部类
        show(new YourFather() {
            @Override
            public void method() {
                System.out.println("我是你爹");
            }
        });

        //可以用Lambda
        show(()-> System.out.println("我是你爹"));
    }
}

第二章 函数式编程

2.1 Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成行能的浪费.而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能

性能浪费的日志案例

注: 日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化.

一种典型的场景就是对参数进行有条件的使用,例如对日志消息进行拼接之后,在满足条件的情况下进行打印输出:

package demo09;


//日志案例
public class Main {
    //定义一个根据日志的级别显示日志信息的方法
    public static void showLog(int level, String message) {
        //对日志的等级进行判断
        if (level == 1) {
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        //我们在主方法里面定义三个日志信息
        String msg1 = "我是你爹";
        String msg2 = "World";
        String msg3 = "Java";

        //调用showLo方法,传递我们这写参数
        showLog(2, msg1 + msg2 + msg3);
    }
}

我们会发现有一些行能浪费的问题

  1. 他要先把第二个参数先拼接好,在进行等级的判断
  2. 如果level不符合,那么这个拼接就浪费了

所以.我们使用Lambda表达式对这个案例进行优化(优化前提: 必须有函数式接口)

//函数式接口

package demo09;

@FunctionalInterface
public interface MessageBuilder {
    //定义一个拼接字符串的方法
    String stringBuilder();
}
//Main函数

package demo09;
//Lambda特点: 延迟加载

//使用前提:必须存在函数式接口

public class Lambda {
    //定义一个显示日志的方法
    public static void showLog(int level, MessageBuilder mb) {
        //对日志的等级进行判断
        if (level == 1) {
            System.out.println(mb.stringBuilder());
        }
    }

    public static void main(String[] args) {
        //我们在主方法里面定义三个日志信息
        String msg1 = "我是你爹";
        String msg2 = "World";
        String msg3 = "Java";

        //调用showLo方法,传递我们这写参数
        showLog(2, () -> (msg1 + msg2 + msg3));


    }
}

使用了Lambda之后,Lambda表达式作为参数传递,仅仅是把参数传递到了showLog方法中.

  • 当满足条件的时候,才会调用Lambda里面重写的方法进行字符串的拼接
  • 要是条件不满足,那么重写的方法就不会执行,不会造成性能的浪费

2.2 使用Lambda作为参数和返回值

如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品.如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数.

上一个小节我就使用了这个方法,看看就好

类似的,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式. 当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调用该方法获取.

package demo09;


import java.util.Arrays;
import java.util.Comparator;

public class Lambda {
    private static Comparator<String> newComparator() {
        return (a,b) -> b.length() - a.length();//升序排列,返回值是int
    }

    public static void main(String[] args) {
        String[] arr = {"你爹","你爸爸","你爷爷"};
        System.out.println(Arrays.toString(arr));

        Arrays.sort(arr,newComparator());

        System.out.println(Arrays.toString(arr));
    }
}

第三章 常用的函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,他们主要在java.util.function包中被提供.

3.1 Supplier接口

java.util.function.Supplier<T>接口仅包含一个无参的方法T get()用来获取一个泛型参数指定类型的对象数据,由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据

package demo09;

// Supplier<T>接口被称之为生产型接口,指定接口的泛型式什么类型,那么接口中的get()方法就会返回什么类型

import java.util.function.Supplier;

public class Lambda {
    public static void method(Supplier<String> sup){
        System.out.println(sup.get());//get的返回值类型就是随着方法的参数里面的那个接口参数的泛型写的是什么决定的
    }

    public static void main(String[] args) {
        method(() -> "我是你爹");
    }
}

3.2 练习: 求数组元素的最大值

题目:

使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值. 提示: 接口的泛型请使用java.lang.Integer

解答

package demo09;

// Supplier<T>接口被称之为生产型接口,指定接口的泛型式什么类型,那么接口中的get()方法就会返回什么类型

import java.util.function.Supplier;

public class Lambda {
    public static int getMax(int[] arr,Supplier<Integer> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        int[] arr = {123,444,2,66,8565};

        int maxNum = getMax(arr,() -> {
            int max = arr[0];
            for (int i : arr) {
                if (max < i){
                    max = i;
                }
            }
            return max;
        });

        System.out.println(maxNum);
    }
}

3.3 Consumer接口

java.util.function.Consumer<T>接口正好与Supplier接口相反,它不是产生一个数据,而是消费一个数据,数据类型由泛型决定

具体怎么消费,需要自定义(输出,计算等等)

抽象方法: accept

Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据.基本使用如:

package demo09;


import java.util.function.Consumer;

public class Lambda {
    private static void method(String name, Consumer<String> con){
        con.accept(name);

    }

    public static void main(String[] args) {
        method("香真屎",(name) -> {
            String newName = new StringBuilder(name).reverse().toString();
            System.out.println(newName);
        });//输出打印结果: 屎真香
    }
}

默认方法: andThen

如果一个方法的参数和返回值都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合.

而这个方法就是Consumer接口中的default方法andThen.下面是JDK源码

default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };//这个是accept方法的lambda格式
    }

注意: Objects.requireNonNull()方法是检测参数是不是null,如果是的话,就会主动抛出NullPointerException异常.这就省去了写抛出空指针异常的麻烦

想要实现组合,需要两个或多个Lambda表达式即可, andThen的语义正是一步接一步的操作. 例如两个步骤组合的情况

package demo09;


import java.util.function.Consumer;

public class Lambda {
    private static void method(String name, Consumer<String> con1, Consumer<String> con2) {
        //       con1.accept(name);
		//       con2.accept(name);
        con1.andThen(con2).accept(name);//这句话的意思就是上面两句话
    }
    public static void main(String[] args) {
        method("Hello World" , (name) -> {
            System.out.println(name.toLowerCase());
        }, (name) -> System.out.println(name.toUpperCase()));
    }
}

3.4 格式化打印信息

题目

下面的字符串数组中存有多条信息,请按照"姓名: XX 性别: XX"的格式将信息打印出来.要求将打印姓名的动作作为第一个consumer接口的Lambda实例,将打印性别额动作作为第二个Consumer接口的Lambda实例,将两个接口按照顺序拼接到一起

public static void main (String[] args) {
    String[] array = {"迪丽热巴,女","古力娜扎,女","马儿扎哈,男"};
}

解答

package demo09;


import java.util.function.Consumer;

public class Lambda {
    private  static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2){
        for (String s : arr) {
            con1.andThen(con2).accept(s);
        }
    }

    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马儿扎哈,男"};

        printInfo(array,(s) -> {
            String name = s.split(",")[0];
            System.out.println("姓名: " + name);
        },(s) ->{
            String sex = s.split(",")[1];
            System.out.println("性别: " + sex);
        });//注意,这里利用到了Lambda的延迟执行方法,首先他在printInfo里面先执行了for语句,然后再执行这个重写的accept()方法,所以说,lambda这里写一个s代表一个字符串是可以的,编译器识别的出来
    }
//运行结果
//    姓名: 迪丽热巴
//    性别: 女
//    姓名: 古力娜扎
//    性别: 女
//    姓名: 马儿扎哈
//    性别: 男
}

3.5 Predicate接口

有时候我们需要对某种数据的类型进行判断,从而得到一个boolean值得结果.这时可以使用java.util,function.predicate<T>接口

抽象方法: test

predicate接口中包含的一个抽象方法boolean test(T t)用于条件判断的场景

package demo09;


import java.util.function.Predicate;

public class Lambda {

    private static boolean isLong(String length,Predicate<String> pre){
        return pre.test(length);
    }

    public static void main(String[] args) {
        String cjy = "88cm";

        String lxj = "99cm";

        boolean flag1 = isLong(cjy,(length) ->{
            if(Integer.parseInt(length.substring(0,2)) > 15){
                return true;
            }
            return false;
        });

        boolean flag2 = isLong(lxj,(length) ->{
            if(Integer.parseInt(length.substring(0,2)) > 18){
                return true;
            }
            return false;
        });

        System.out.println(flag1? "cjy很长":"cjy很短");
        System.out.println(flag2? "lxj很长":"lxj很短");
    }
}//输出结果都是长

默认方法: and

既然是条件判断,就会存在与或非三种常见的逻辑关系. 其中将两个predicate条件使用"与"逻辑连接起来实现"并且"的效果的时候,可以使用这个方法,其源代码是:

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

示例:

package demo09;


import java.util.function.Predicate;

public class Lambda {

    private static boolean isBothLong(String length, Predicate<String> pre1, Predicate<String> pre2) {
        return pre1.and(pre2).test(length);
    }

    public static void main(String[] args) {
        String cjy = "18cm";

        String lxj = "88cm";

        boolean flag1 = isBothLong(cjy, (length) -> {
            if (Integer.parseInt(length.substring(0, 2)) > 15) {
                return true;
            }
            return false;
        }, (length) -> {
            if (Integer.parseInt(length.substring(0, 2)) > 15) {
                return true;
            }
            return false;
        });


        System.out.println(flag1 ? "cjy和lxj都很长" : "cjy和lxj都很短");


    }//结果就是都很长
}

默认方法: or

源码:

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

示例就不做了,道理和前者一样

默认方法: negate

源码

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

就是一个取反,就不解释了

3.6 集合信息筛选

题目

数组中由多条"姓名+性别"的信息如下,请通过predicate接口的拼装将符合要求的字符筛选到集合ArrayList中,需要同时满足两个条件:

  1. 性别必须是女

  2. 姓名为4个字

    数据:

    public static void main(String[] args) {
            String[] array = {"迪丽热巴,女","古力娜扎,女","马儿扎哈,男"};
            
        }
    

解答

package demo09;


import java.util.ArrayList;
import java.util.function.Predicate;

public class Lambda {
    private static boolean isGirl(String s, Predicate<String> pre) {
        return pre.test(s);
    }

    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马儿扎哈,男"};
        ArrayList<String> arr = new ArrayList<>();

        for (String s : array) {
            boolean flag1 = isGirl(s, (String str) -> str.split(",")[0].length() >= 4 && str.split(",")[1].equals("女"));
            if (flag1)  arr.add(s);
        }
        System.out.println(arr);
    }
//运行结果
    //[迪丽热巴,女, 古力娜扎,女]
}

3.7 Function接口

java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置事件,后者称为后置条件.

抽象方法: apply

Function接口中最主要的抽象方法为: R apply(T t),

举例: 将String 转化为 Integer

package demo09;


import java.util.function.Function;

public class Lambda {
    //把字符串转换为integer
    public static void change(String s, Function<String, Integer> fun) {
        int in = fun.apply(s); //自动拆箱
        System.out.println(in);
    }
    public static void main(String[] args) {
        change("123", (str) -> Integer.parseInt(str));
    }
}

默认方法: andThen

源码:

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

就是链式编程的思想,节省了代码量,但是可读性降低

举例: 先将输入的String转化为Integer,再将这个Integer乘10

package demo09;


import java.util.function.Function;

public class Lambda {
    //把字符串转换为integer
    public static void method(String s, Function<String, Integer> fun1,Function<Integer, Integer> fun2) {
        System.out.println(fun1.andThen(fun2).apply(s));//等效于fun2.apply(fun1.apply());
    }

    public static void main(String[] args) {
        method("123", (str) -> Integer.parseInt(str),(Int) -> Int*10);
    }
}

顺口一提,将Integer快速转换为String的方法就是 Integer + ""

3.8 练习: 自定义函数模型的拼接

题目

请使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:

​ 例子: String str = “赵丽颖,20”;

  1. 将String截取数字年龄部分,得到字符串
  2. 将字符串转换成为int类型的数字
  3. 将上一步的int数字累加100,得到一个String结果

题解

package demo09;


import java.util.function.Function;

public class Lambda {
    //把字符串转换为integer
    public static void method(String s, Function<String, String> fun1, Function<String, Integer> fun2, Function<Integer, String> fun3) {
        System.out.println(fun1.andThen(fun2).andThen(fun3).apply(s));//等效于fun2.apply(fun1.apply());
    }

    public static void main(String[] args) {
        method("赵丽颖,20", (str) -> str.split(",")[1], Integer::parseInt, (num) -> (num + 100) + "");
    }//输出结果120

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值