• 【Java8新特性】函数式接口


    在这里插入图片描述

    1. 介绍

    Java中的函数式接口指的是:有且仅有一个抽象方法的接口。(通常接口上方都会有@FunctionInterface注解,用于在编译期发现错误)

    Java函数式接口的体现就是Lambda表达式,所有函数式接口都是适用于Lambda表达式使用的接口。

    1.1 @FunctionInterface注解

    @FunctionalInterface // 表明该类为函数式接口
    public interface DoubleSupplier {
        double getAsDouble();
    }
    
    • 1
    • 2
    • 3
    • 4

    只要在类上加上@FunctionInterface注解,则告诉了编译器该类为函数式接口,只要不符合函数接口的只有一个抽象方法的规范,就会报错。

    注意:即使不加@FunctionInterface接口,只有一个抽象方法的类,也能算是函数是接口,只是编译器不知道。

    1.2 函数式接口的调用

    public class TestFunInterface2 {
        //需求:使用Consumer接口作为方法的参数,对字符串实现翻转
        public static void revString(String str, Consumer<String> consumer){
            consumer.accept(str);
        }
    
        public static void main(String[] args) {
            revString("zhangsan",(str)->{
                StringBuilder reverseStr = new StringBuilder(str).reverse();
                System.out.println("翻转后的字符串:"+reverseStr);
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2. 函数式编程

    2.1 Lambda的延迟加载技术

    通过log4j的日志打印功能举例:

    public class Demo01Logger {
        private static void log(int level, String msg) {
            if (level == 1) {
                System.out.println(msg);
            }
         }
        public static void main(String[] args) {
            String msgA = "Hello";
            String msgB = "World";
            String msgC = "Java";
            log(1, msgA + msgB + msgC);// 无论是否满足log级别为1,都会执行字符串拼接,造成性能浪费。
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    经过Lambda表达式的优化后:只有满足日志级别为2,才能进入lambda表达式,进行字符拼接。

    @FunctionalInterface
    public interface MessageBuilder {
        String buildMessage();
    }
    public class Demo02LoggerLambda {
        private static void log(int level, MessageBuilder builder) {
            if (level == 1) {
                 // 实际上利用内部类 延迟的原理,代码不相关 无需进入到启动代理执行
                 System.out.println(builder.buildMessage());
               
            }
        }
        public static void main(String[] args) {
            String msgA = "Hello";
            String msgB = "World";
            String msgC = "Java";
           log(2,()->{
                    System.out.println("lambda 是否执行了");
                    return msgA + msgB + msgC;
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.2 Lambda表达式的使用

    1. Lambda表达式作为参数
    public class Runnable {
        private static void startThread(Runnable task) {
    		new Thread(task).start();
        }
        public static void main(String[] args) {
    		startThread(()> System.out.println("线程任务执行!"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. Lambda表达式作为返回值
    public class lambda_Comparator {
        private static Comparator<String> newComparator1(){
            return new Comparator<String>() {
                @Override
                public int compare(String a, String b) {
                    return b.length()-a.length();    
                }
            };
        }
        public static void main(String[] args) {
            String[] array={"abc","ab","abcd"};
            Arrays.sort(array, newComparator1()); // 方式一:复杂写法
            Arrays.sort(array,(a,b)->b.length()-a.length());//更简单的方式
            System.out.println(Arrays.toString(array));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3. 常用的函数式接口

    3.1 Supplier生产型接口

    java.util.function.Supplier 接口是生产一个数据,其数据类型由泛型T来定。

    Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。

    public class Test_Supplier {
        private static String test_Supplier(Supplier<String> suply) {
            return suply.get(); //供应者接口
        }
        public static void main(String[] args) {
             // Lambda表达式写法:产生的数据作为 sout 作为输出
             System.out.println(test_Supplier(()->"产生数据"));
             
             // 匿名内部类写法:
             System.out.println(String.valueOf(new Supplier<String>() {
                 @Override
                public String get() {
                    return "产生数据";
                }
            }));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.2 Consumer消费型接口

    java.util.function.Consumer 接口是消费一个数据,其数据类型由泛型决定。

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

    public class Test_Comsumer {
        public static void generateX(Consumer<String> consumer) {
            consumer.accept("hello consumer");
        }
        public static void main(String[] args) {
            generateX(s->System.out.println(s));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    默认方法:andThen

    将两个Consumer组合,先消费一条数据,随后再消费一条数据。

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t)> { accept(t); after.accept(t); }; 
        //1:  返回值为Consumer 那么需要 ()-> 表示函数式接口
        //2:  accept(t);为生产一个数据供应给 (T t)中的t
        //3:  after.accept(t);为利用这个t再次生成新的函数式接口 实现类始于builder的设计模式
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    例如:

    public class TestFunInterface3 {
        public static void method(String s, Consumer<String> con1,Consumer<String> con2){
            //con1.accept(s);
            //con2.accept(s);
            con1.andThen(con2).accept(s); //谁在前面谁先消费
        }
    
        public static void main(String[] args) {
           method("zhangsan",(str)->{
               System.out.println(str.toUpperCase());
           },(str)->{
               System.out.println(str.toLowerCase());
           });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.3 Predicate条件判断接口

    java.util.function.Predicate 用于对某种类型的数据进行判断,并得到一个boolean值结果。(即是生产者,又是消费者)

    抽象方法: boolean test(T t) 用于条件判断的场景:

    默认方法: and(与)、or(或)、nagte(取反)

    既然是条件判断,就会存在与、或、非三种常见的逻辑关系。

    其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,类始于 Consumer接口 andThen()函数 其他三个雷同

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other); 
        return (t)> test(t) && other.test(t);
    }
    
    • 1
    • 2
    • 3
    • 4

    静态方法: isEquals

    static <T> Predicate<T> isEqual(Object targetRef) {
    	return (null == targetRef)
    	         ? Objects::isNull
    	         : object -> targetRef.equals(object);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    举例:

    public class Use_Predicate {
        // 判断字符串是否存在o,即是生产者 又是消费者接口
        private static void method_test(Predicate<String> predicate) {
             boolean b = predicate.test("OOM SOF");
             System.out.println(b);
        }
        // 判断字符串是否同时存在o和h 
        private static void method_and(Predicate<String> predicate1,Predicate<String> predicate2) {
            boolean b = predicate1.and(predicate2).test("OOM SOF");
            System.out.println(b);
        }
        // 判断字符串是否存在o或者h 
        private static void method_or(Predicate<String> predicate1,Predicate<String> predicate2) {
            boolean b = predicate1.or(predicate2).test("OOM SOF");
            System.out.println(b);
        }
        // 判断字符串是否存在o,结果取反
        private static void method_negate(Predicate<String> predicate) {
             boolean b = predicate.negate().test("OOM SOF");
             System.out.println(b);
        }
        public static void main(String[] args) {
            method_test((s)->s.contains("O"));
            method_and(s->s.contains("O"), s->s.contains("h"));
            method_or(s->s.contains("O"), s->s.contains("h"));
            method_negate(s->s.contains("O"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3.4 Function普通函数接口

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

    简单来说就是:将 T 转为 R 返回

    	// 将数字转换为String类型
    	private static void numberToString(Function<Number, String> function) {
    		String apply = function.apply(12);
    		System.out.println("转换结果:"+apply);
    	}
    	public static void main(String[] args) {
    		numberToString((s)->String.valueOf(s));
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    默认方法:andThen compose()

    区别: 执行顺序不同,andThen是调用者先执行,传入的function后执行;compose是传入的function先执行,调用者后执行。

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    	Objects.requireNonNull(after);
    	// 先执行调用者,再执行after的apply方法
    	return (T t) -> after.apply(apply(t));
    }  
    // 这里的V 一个是作为输入值 一个是作为输出值  按照调用的顺序的不同 对于 T V 做输入 输出的顺序也不同 注意看
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    	Objects.requireNonNull(before);
    	// 先执行before的apply方法,后执行调用者apply方法
    	return (V v) -> apply(before.apply(v));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意调用的先后顺序:

    // 静态方法
    private static void method_andThen(Function<Integer, Integer> f1, Function<Integer, Integer> f2) {
        // andThen:先执行f1,再执行f2
        Integer apply = f1.andThen(f2).apply(2);
        System.out.println(apply);
    }
    
    private static void method_compose(Function<Integer, Integer> f1, Function<Integer, Integer> f2) {
        // compose:先执行f2,再执行f1
        Integer apply = f1.compose(f2).apply(2);
        System.out.println(apply);
    }
    
    public static void main(String[] args) {
        method_andThen(s -> s + 1, s -> s = s * 2);//6
        method_compose(s -> s + 1, s -> s = s * s);//5
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.5 Operator 同类型相互转换接口

    个人理解: 例如对字符串操作,返回字符串类型。像Stream流中的map方法,但是限定了返回值和参数相同。

    BinaryOperatorandthen() 方法不支持两个函数链接操作 也就是不需要再次 BinaryOperator 因为源代码规定不允许使用两次输入。

    public static void main(String[] args) {
        // 单个同类型操作
        UnaryOperator<String> u_str=(s)->s.split(" ")[0];
        UnaryOperator<String> u_str1=(s)->s.concat(" ok");
    
        String apply = u_str.andThen(u_str1).apply("lambda Ok");
        String apply1 = u_str.compose(u_str1).apply("lambda Ok");
        System.out.println(apply);
        System.out.println(apply1);
    
        // 两个同类型操作
        BinaryOperator<Integer> way_add=(k, v)->k+v;
        BinaryOperator<Integer> way_mul=(k,v)->k*v;
        // 注意不能连写!
        Integer res1 = way_add.apply(1,2);
        Integer res2 = way_mul.apply(1,2);
        System.out.println(res1);
        System.out.println(res2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    Mariadb数据库
    基于RedHat8部署ES+Kibana8.5集群
    Linux权限
    后端传递数据给前端做导出Excel的Controller类
    Go语言excelize包-02-工作表操作
    一起来探究@Schedule定时任务在分布式产生的问题
    vue3 vue-router4 No match found for location with path “/home“
    入门cv必读的10篇baseline论文
    英语六级day-2
    Python三 —— Python迭代器、生成器、装饰器
  • 原文地址:https://blog.csdn.net/qq_33399435/article/details/127617548