四大函数式接口及lambda表达式的应用原理


前言

本文介绍了函数式接口及lambda的使用,及四大函数式接口(消费型、供给型、函数型、断言型)的源码剖析。


一、函数式接口和Lambda的使用

想要了解lambda的使用,我们先来认识下什么是函数式接口。

1、函数式接口

⑴ 、定义

函数式接口有且仅有一个抽象方法的接口。简单来说,就是该接口只提供了一个方法供实现类实现,其他的方法都是defaultstatic修饰的方法。

@FunctionalInterface:编译期用来检验该接口是否是满足函数式接口条件,该注解仅仅是用来检查是否满足函数式接口的条件,可以不写,建议加上。

⑵ 、jdk中的实例

多线程封装线程任务的Rannable接口:

@FunctionalInterface
public interface Runnable {
	//仅有一个抽象方法
    public abstract void run();
}

比较器Comparator:

@FunctionalInterface
public interface Comparator<T> {

	//供实现的抽象方法
    int compare(T o1, T o2);
   
    boolean equals(Object obj);

	//已经实现了的默认方法
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
	//.......省略其他的default方法

对于上面的例子,Comparator是函数式接口,我们知道函数式接口的定义是:一个函数式接口有且只有一个抽象方法。由于默认方法已经有了实现,所以它们不是抽象方法,但是对于equals方法呢?他并没有实现,这里是因为equals是Object类中的方法,所有类都默认继承至Object类,接口中该方法equals已经从Object类中获得了实现,所以因为重写了Object类中任意一个public方法的方法并不算接口中的抽象方法。

还有其他等等:java.util.concurrent.Callable,java.security.PrivilegedAction,java.io.FileFilter…

2、lambda表达式

⑴ 、定义

Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda使用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
lambada 表达式实质上是一个匿名方法,但该方法并非独立执行,而是用于实现由函数式接口定义的唯一抽象方法

⑵、演示

拿上面介绍的函数式接口Rannable来介绍,这里不用将Rannable当做线程任务,先仅仅将其当做一个普通的接口来看:

在以往,我们写代码的时候,假如需要创建Rannable接口对象,需要定义一个实现类,实现类重写抽象方法,简单点采用直接new匿名内部类如下:

    @Test
    public void test() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类方式");
            }
        };
        r.run();
    }

现在我们引入了lambda表达式,Rannable是函数式接口,仅有一个实现方法run,下面写法我们可以理解成一个匿名方法:(行参)-> {方法体},接口中仅有一个需要实现的方法,不需要指定。

    @Test
    public void test() {
        Runnable r = ()-> System.out.println("lambda表达式");
        r.run();
    }

lambda在写法上大体有如下几种类型:

 	//无形参,无返回值
 	Runnable a = ()-> System.out.println("111");

    //2个形参数,返回值为int  方法体包含多行语句,需要使用{}
    //抽象方法定义:int compare(T o1, T o2);
    Comparator<String> b = (o1, o2) -> {
         System.out.println(o1);
         System.out.println(o2);
         return o1.length() - o2.length();
     };

    //1个形参,返回值为boolean  可以省略行参括号,一行代码可以省略大括号与return
    //抽象方法定义:boolean test(T t);
    Predicate<String> c = str -> str.length() > 0;

    //方法体如果写{},需要显示写return
    Predicate<String> d = str -> {return str.length() > 0;};
场景举例
返回boolean类型str -> str.isEmpty() 或 String::isEmpty
创建对象()-> new Demo()
消费对象a -> { sout( a.getName() ) }
计算a,b -> a+ b
比较a,b -> a == b

二、内置函数式接口

在jdk8之后,官方定义了一些常用的函数式接口,如果以后需要使用类似的接口,直接使用即可,不需要再单独定义,共分以下四大类。

1、消费型Consumer

接口期望执行带有副作用的操作(即它可能会改变输入参数的内部状态)

⑴ 、演示

包含一个抽象方法accept和一个default方法,
andThen方法返回一个组合过的consumer实例,它的accept方法是先调用this这个consumer的accept方法,然后调用after的accept方法(…之后执行)。
在这里插入图片描述

例:

@Test
public void consumerTest(){
    Consumer<String> consumer = str-> System.out.println(str+"11");
    consumer.accept("abc");

    consumer.andThen(s -> System.out.println(s+="222"))
            .accept("def");
}

	输出:
	abc11
	def11
	def222

⑵、java中的应用实例

ArrayList中的forEach方法:在这里插入图片描述

 	ArrayList<String> al = new ArrayList<>();
  	al.add("11111");
    al.add("22222");
    al.add("33333");
    al.forEach(s->{
        String str = s.isEmpty() ? "" : s.substring(0,1);
        System.out.println(str);
        });
	}

⑶、拓展

拓展接口方法
IntConsumervoid accept(int x)
DoubleConsumervoid accept(double x)
LongConsumervoid accept(long x)
BiConsumer<T, U>void accept(T t, U u)        接收2个泛型参数
ObjIntConsumer<T>void accept(T t, int value)         无andThen方法
ObjDoubleConsumer<T>void accept(T t, double value)        无andThen方法
ObjLongConsumer<T>void accept(T t, long value)        无andThen方法

2、供给型Supplier

Supplier本质就是一个容器,可以用来存储数据(或者是产生数据的规则),然后可以供其他方法使用的这么一个接口

⑴ 、演示

它不包含任何静态或默认方法,只有一个抽象方法 T get()
在这里插入图片描述
例:

	//如取随机数
 	@Test
    public void supplierTest(){
        //()->Math.random();
        Supplier<Double> supplier = Math::random;

        System.out.println(supplier.get());
    }

⑵、java中的应用实例

延迟执行
java.util.logging.Logger日志类中:
先来看info输出日志两个重载方法:
在这里插入图片描述

/**
 * 日志测试类
 */
public class LoggerTest {

    private Logger logger = Logger.getLogger(this.getClass().getName());

    private String info = "";

    public String getInfo() {
        return info += "info执行了";
    }

    @Override
    public String toString() {
        return "LoggerTest{" +
                "info='" + info + '\'' +
                '}';
    }

       @Test
    public void testString() {

        LoggerTest loggerTest = new LoggerTest();

        logger.config(loggerTest.getInfo());

        System.out.println(loggerTest);//LoggerTest{info='info执行了'}
    }

    @Test
    public void testSupplier() {

        LoggerTest loggerTest = new LoggerTest();

        logger.config(loggerTest::getInfo);

        System.out.println(loggerTest); //LoggerTest{info=''}
    }
}

测试说明:新建一个日志测试类,默认的Logger的level为800,可打印info信息,config打印方法的level为700,两个test方法中的日志将不会打印出来,该类有个成员变量info,当调用getInfo方法时,info对象会加上值,test方法中两个打印方式,第一个用的是
String为形参的config打印,第二个用的是Supplier为形参的config打印,通过输出我们可以发现:

  • 无论是否显示消息,都会执行getInfo方法,构建info参数
  • 仅当日志级别显示消息时,才会执行getInfo方法,构建参数

采用相同类型的 Supplier 替换参数的技术称为延迟执行(deferred execution),可以在任何对象创建成本较高的上下文中使用。

再来看标准库中的java.util.Optional中的orElseGet方法:
在这里插入图片描述
orElseGet相比于orELse来说,仅在Optional没值的时候,执行返回Supplier接口实现中的方法,返回值。

⑶、拓展接口

拓展接口方法
IntSupplierint getAsInt()
DoubleSupplierdouble getAsDouble()
…Long Boolean等

3、函数型Function

它可以将 T 类型的泛型输入参数转换为 R 类型的泛型输出值,T和R可以相同

⑴ 、演示

包含一个抽象方法apply,2个defalut方法及一个静态方法,apply不多说,待实现的抽象方法,入参T返回R。
addThen返回一个组合过的Function,先调用this的apply方法,将其返回值作为after的apply的入参,after可以理解为this之后执行。
compose与addThen相反,先调用before的apply,得到一个返回值,将这个值作为this的apply的入参,before可以理解为this之前执行。
在这里插入图片描述
例:

    @Test
    public void FunctionTest() {

        //T为String R为int, 定义一个实现方法返回字符长度
        Function<String, Integer> fun = String::length;
        System.out.println(fun.apply("123"));   //3

        //andThen使用----
        Function<Integer, Integer> fun1 = a -> a + 100;
        //after.apply(apply(t)):先调用fun的apply获取长度为5,然后调用fun1的accply,+100
        Integer apply = fun.andThen(fun1).apply("11111");
        System.out.println(apply);  //105

        //compose使用----
        Function<Integer, String> fun2 = a -> a + "";
        //apply(before.apply(v)):先调用fun2的apply,输入1101获取字符类型“1101”,然后调用fun的apply,输出长度
        System.out.println(fun.compose(fun2).apply(1101));	 //4

    }

identity()方法:
返回一个输入和输出一样的表达式,t -> t

	/**
	 * 如下转换成map集合,key为String字符,value为字符长度
	 */
    @Test
    public void FunctionTest() {
        Stream<String> stream = Stream.of("11", "222", "3", "444");
        //toMap方法形参为两个Function接口,其中key实现方法t->t即入参t,返回也是t;value的实现方法取字符长度
        //可以写成Collectors.toMap(Function.identity(), String::length)
        Map<String, Integer> map = stream.collect(Collectors.toMap(t->t, String::length));
        System.out.println(map); //{11=2, 222=3, 3=1, 444=3}
    }

上面的t->t可以写成Function.identity()

⑵、java中的应用实例

java.util.stream.Stream流中的map方法如下:

在这里插入图片描述

	/**
	 * 字符串映射到他们的字符长度
	 */
    @Test
    public void FunctionTest() {
        List<String> list = Arrays.asList("aa", "bbbbb", "c", "dd");
        //list.stream().map(str->str.length()).collect(Collectors.toList());
        List<Integer> collect = list.stream().map(String::length).collect(Collectors.toList());
        System.out.println(collect);  //[2, 5, 1, 2]
    }    

⑶、拓展接口

拓展接口方法
IntFunction<R>R apply(int value)
… Double,Long等
ToIntFunction<T>int applyAsInt(T value)
… Double,Long等
DoubleToIntFunctionint applyAsInt(double value)
…Double,Long等
BiFunction<T, U, R>R apply(T t, U u)

4、 断言型Predicate

主要用于筛选数据,常用在:给定一个包含若干项的流,Stream 接口的 filter 方法传入 Predicate 并返回一个新的流,它仅包含满足给定谓词的项。

⑴ 、演示

抽象方法test:入参为泛型T,返回值为boolean,主要用来判断参数是否符合某种规则;
and方法:入参为Predicate对象,返回一个组合过的Predicate对象,同时满足this和other的test,相当于逻辑与&&,有短路特性
or方法:用法同and,相当于逻辑或 || ,存在短路特性
negate方法:用法同and,相当于逻辑非 !。
isEquers方法:功能类似于equals,不同的是,该方法先判断入参对象是否为空,为空返回实现方法为this == null即(obj->obj==null)的断言对象,否则返回实现方法为object -> targetRef.equals(object)的Predicate对象。
在这里插入图片描述

例:

    @Test
    public void predicateTest(){
        Predicate<String> predicate = str -> str.startsWith("a");
        System.out.println(predicate.test("aaa"));  // true
        System.out.println(predicate.test("baa"));  // false
    }

结合stream流,综合使用上面的Predicate中的方法:
Stream的filter过滤方法接受一个断言型参数,根据Predicate实现对象的test方法的boolean类型的返回值来判断元素是否符合条件Stream<T> filter(Predicate<? super T> predicate)

public class PredicateTest {

    public static List<String> MICRO_SERVICE = Arrays.asList("nacos","authority","gateway","ribbon",
            "feign","hystrix","e-commerce","gate");

    /**
     * boolean test(T t)
     * test方法 主要判断参数是否符合规则
     */
    @Test
    public void testTest(){
        //定义规则为字符长度大于5
        MICRO_SERVICE.stream()
                .filter(str->str.length() > 5)
                .forEach(str -> System.out.print(str + ", "));
        //输出:authority, gateway, ribbon, hystrix, e-commerce,
    }

    /**
     * Predicate<T> and(Predicate<? super T> other)
     * and方法 等同于逻辑&&,存在短路特性
     */
    @Test
    public void andTest(){
        Predicate<String> p1 = str -> str.length() > 5;
        Predicate<String> p2 = str -> str.startsWith("g");
        MICRO_SERVICE.stream()
                .filter(p1.and(p2))
                .forEach(str -> System.out.print(str + ", "));
        //输出:gateway,
    }

    /**
     * Predicate<T> or(Predicate<? super T> other)
     * or方法 等同于逻辑 || ,存在短路特性,多个条件满足一个即可
     */
    @Test
    public void orTest(){
        Predicate<String> p1 = str -> str.length() > 5;
        Predicate<String> p2 = str -> str.startsWith("g");
        MICRO_SERVICE.stream()
                .filter(p1.or(p2))
                .forEach(str -> System.out.print(str + ", "));
        //输出:authority, gateway, ribbon, hystrix, e-commerce, gate,
    }

    /**
     * Predicate<T> negate()
     * 等同于逻辑 !
     */
    @Test
    public void negateTest(){
        Predicate<String> p2 = str -> str.startsWith("g");
        MICRO_SERVICE.stream()
                .filter(p2.negate())
                .forEach(str -> System.out.print(str + ", "));
        //输出:nacos, authority, ribbon, feign, hystrix, e-commerce,
    }

    /**
     * Predicate<T> isEqual(Object targetRef)
     * 类似于equals,区别在于:先判断对象是否为null ,不为null再使用equals进行比较
     */
    @Test
    public void isEqualsTest(){
        String string = "gate";
//        String string = null;
        Predicate<String> p = str -> Predicate.isEqual(string).test(str);
        MICRO_SERVICE.stream()
                .filter(p)
                .forEach(str -> System.out.print(str + ", "));
        //输出:gate,
    }
}

⑵、java中的应用实例

如上述的Stream中的filter方法,Optional的filter,Collection的removeIf,Stream的allMatch等等
Optional中的filter:
在这里插入图片描述

⑶、拓展接口

拓展接口方法
IntPredicateboolean test(int value)
… Double,Long等

参考文章
https://blog.csdn.net/xushiyu1996818/article/details/92787371
https://geek-docs.com/java/java-examples/java-consumer-interface.html

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数式接口是指只有一个抽象方法的接口。它们可以被用作lambda表达式的目标类型,使得我们能够以更简洁的方式定义和使用匿名函数。 Lambda表达式是一种匿名函数,它可以作为参数传递给方法或存储在变量中。它提供了一种更简洁、更灵活的方式来编写代码。在Java 8之前,我们使用匿名内部类来实现类似的功能。但是,使用Lambda表达式可以更直观地表达代码的意图,使代码更易读和易于维护。 Lambda表达式可以在很多场景中使用,其中包括事件处理和迭代列表。在事件处理方面,Lambda表达式可以用来替代传统的匿名内部类,使代码更简洁。例如,使用Lambda表达式可以更好地处理Swing API中的事件监听代码。 在迭代列表方面,Lambda表达式可以用来替代传统的for循环,使代码更简洁和可读性更高。例如,使用Lambda表达式可以更方便地对列表进行迭代,可以使用forEach方法来实现。 总的来说,函数式接口Lambda表达式Java 8中引入的功能,它们可以使代码更简洁、更灵活,并提供了更好的方法来处理事件和迭代列表。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java Lambda表达式函数式接口实例分析](https://download.csdn.net/download/weixin_38688145/12746209)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Lambda表达式函数式接口详解](https://blog.csdn.net/weixin_43552143/article/details/122364188)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值