首页 > 技术文章 > java中的函数式接口

java-quan 2020-09-06 00:30 原文

是什么??

有且只有一个抽象方法的接口

场景:

适用于函数式编程场景(使用lambda表达式编程)的接口,函数式接口可以适用于lambda使用的接口。

只有确保接口中有且只有一个抽象方法,java中的lambda才能顺利推到

 

格式

/**
 * 函数式接口:有且之哟一个抽象方法的接口
 *          接口中可以包含其他的方法,包括默认方法,静态方法,私有方法
 *
 * @FunctionalInterface
 * 作用:可以检测接口是否是一个函数式接口
 *      是:编译成功
 *      否:编译失败(接口中没有抽象方法,抽象方法的个数大于1)
 */

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

//    void  method2();
}

 

函数式接口的使用:

/**
 * 函数式接口的使用:可以作为方法的参数和返回值类型
 */

Lambda作为参数

例如:

public class Demo {
    public static  void show(MyInterface myInterface){
        myInterface.method();
    }

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

        //调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
        show(new MyInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口的抽象方法");
            }
        });

        //调用show方法,方法的参数是一个函数式接口,所以,我们可以使用lambda表达式
        show(()->{
            System.out.println("使用lambda表达式重写接口中的抽象方法");
        });

        //简化lambda表达式
        show(()-> System.out.println("简化lambda表达式"));
    }
注意:使用匿名内部类回生成.class文件,但是lambda表达式不会,减少JVM的加载

结果:

 

 

 

函数式编程

 

Lambda的延迟执行减少性能浪费:

 

例子:

/**
 * 日志案例
 * 发现下面代码存在性能浪费
 *
 * 调用showLog方法的过程:
 *      先将msg1 msg2 msg3拼接好之后,调用showLog方法。
 *      但是如果level不等于1,那就不执行了。
 *      所以拼接字符串的过程就浪费了
 */

 

public class Demo01Logger {
    public static void showLog(int level,String msg){
        if (level == 1){
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        String msg1 = "AAA";
        String msg2 = "BBB";
        String msg3 = "CCC";

        showLog(1,msg1+msg2+msg3);
    }
}

 

 

利用lambda表达式优化:

1先定义一个函数式接口

@FunctionalInterface
public interface MessageBuilder {
    //定义拼接消息的抽象方法,返回拼接完成的消息
    public abstract String builderMsg();
}

 

/**
 * lambda优化
 *
 * lambda延迟加载:
 * lambda使用前提:必须存在函数式接口:
 */
public class Demo02Logger {
    //显示日志方法,
    public static void showLog(int level,MessageBuilder messageBuilder){
        if (level == 1){
            System.out.println(messageBuilder.builderMsg());
        }
    }

    public static void main(String[] args) {
        String msg1 = "AAA";
        String msg2 = "BBB";
        String msg3 = "CCC";
        //调用showLog方法,参数MessageBuilder式一个函数式接口,可以传递Lambda表达式
        showLog(1,()->{
            //返回一个拼接好的字符串
            System.out.println("有没有拼接");
            return msg1+msg2+msg3;
        });
    }
}

可以通过改变日志级别,看看输出存不存在"有没有拼接"

/**
 *使用lambda表达式作为参数传递,仅仅式吧参数传递到showLog方法中
 * 只有满足条件,日志的等级式1
 *      才会调用接口MessageBuilder中的方法builderMessage
 *      进行字符串拼接
 * 如果不满足
 *      接口MessageBuilder中的方法builderMessage不会执行
 *      不会导致性能浪费
 */

 

 

Lambda作为参数和返回值

 

public class Demo03Runable {
    //定义一个方法startThread 方法参数使用函数式接口Runnable
    public static void startThread(Runnable runnable){
        new Thread(runnable).start();
    }

    public static void main(String[] args) {
        //匿名内部类实现
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程启动了");
            }
        });

        //因为Runnable接口是一个函数式接口,里面只有一个抽象方法run方法,可以使用lambda表达式
        startThread(()->{
            System.out.println(Thread.currentThread().getName()+"lambda线程启动了");
        });

        startThread(()->
            System.out.println(Thread.currentThread().getName()+"优化lambda线程启动了"));
        }

}

 

 

 

Lambda作为返回值

public class Demo02Comparator {
    //定义一个方法,方法的返回值位函数式接口Comparator
    public static Comparator<String> getComparator(){
        //方法返回值式一个接口,返回接口的匿名内部类
        return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //安装字符串降序排序
                return o1.length() - o2.length();

            }
        };

    }

    public static Comparator<String> getComparator2() {
        //方法返回值式一个函数式接口,返回接口的lambda表达式
        return (String o1, String o2)->{
            return o1.length() - o2.length();
        };
    }

    //优化表达式
    public static Comparator<String> getComparator3() {
        //方法返回值式一个函数式接口,返回接口的lambda表达式
        return ( o1,  o2)-> o1.length() - o2.length();
    }

    public static void main(String[] args) {
        String[] arr = {"AAAAAAA","BBBB","CCCCCCC"};
        System.out.println(Arrays.toString(arr));
        //对数组进行拍寻
        Arrays.sort(arr,getComparator());
        System.out.println(Arrays.toString(arr));
    }

}

 

常用的函数式接口:

Supplier接口:

java.util.function.Supplier<T> 接口包含一个无参方法:T get() 

作用:用来获取一个泛型参数指定类型的对象数据。

对应的lambda表达式需要提供一个符合泛型类型的对象数据

/**
 * 常用函数式接口
 *  java.util.function.Supplier<T> 接口被称为生产型接口。
 * Supplier指定什么类型的泛型就返回什么类型的数据
 */

 

public class Demo01Supplier {
    //方法参数传递Supplier接口,泛型String,get返回一个String
    public static String getString(Supplier<String> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        //调用getString方法。使用lambda表达大师
        String s = getString(()->{
            return "生成字符串";
        });

        //优化表达式
        String s1 = getString(()->"优化生成字符串");

        System.out.println(s);
        System.out.println(s1);
    }
}

结果:

生成字符串
优化生成字符串

 

简单练习

/**
 * 求数组最大值
 *      使用Supplier接口作为参数类型,通过lambda表达式求出int数组的最大值
 */

答案:

public class Demo02Test {
    //获取数组元素的最大值,参数位supplier接口,泛型Integer
    public static Integer getMax(Supplier<Integer> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        //定义数组
        int[] arrint = {111,222,444,333};
        int maxinarr =getMax(()->{
           int max = arrint[0];
            for (int i : arrint) {
                if(i>max){
                    max = i;
                }
            }
            return max;
        });

        System.out.println(maxinarr);
    }

}

 

 

Consumer

/**
 * java.util.function.Consumer<T> 消费一个数据,数据类型又泛型决定
 * 消费型接口,使用型接口
 * accept
 *
 */

 

public class Demo03Consumer {
    public static void method(String name, Consumer<String> consumer){
        consumer.accept(name);
    }

    public static void main(String[] args) {
        //因为accept有参数,所以lambda表达式需要加入参数。
        method("quanzhiqiang",(String name)->{
            System.out.println(name);

            String reName = new StringBuffer(name).reverse().toString();
            System.out.println(reName);
        });


    }
}

结果:

quanzhiqiang
gnaiqihznauq

 

Consumer中的默认方法 andThen

/**
 * Consumer接口默认方法andThen
 * 作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,
 * 在对数据进行消费
 *
 * Consumer<String> co1
 * Consumer<String> co2
 * String str1 = "done"
 * co1.accept(str1)
 * co2.accept(str1)
 * 可以简洁点:
 * co1.andThen(con2).accept(s)
 *
 * 连接两个Consumer接口,再进行消费,谁写前面谁先消费
 */

 

public class Demo4Consumer {
    public static void method(String str , Consumer<String> co1,Consumer<String> co2){
        co1.accept(str);
        co2.accept(str);
    }

    public static void method1(String str , Consumer<String> co1,Consumer<String> co2){
      co1.andThen(co2).accept(str);//执行co1再执行co2,比上面的要简洁一些
    }

    public static void main(String[] args) {
        method1("quan",
                (t)->{//lambda表达式参数的类型,可以省略
                    System.out.println(t.toUpperCase());
                },
                (t)->{
                    System.out.println(t.toLowerCase());
                });


    }

}
/**
 * QUAN
 * quan
 * 结果
 */

 

练习:

/**
 * 字符串数组中存在多条信息,按照格式姓名:xx 性别:Xx格式打印出来
 * 答应名名字位第一个Consumer接口的lambda实例
 * 打印性别式第二个
 * 然后拼接起来
 */

public class Demo5Consmer {

    public static void pringinfo(String[] arr , Consumer<String> c1,Consumer<String> c2){
        for (String s : arr) {
           c1.andThen(c2).accept(s);
        }
    }
    public static void main(String[] args) {
        String[] arr = {"AA,N","BB,M","CC,N"};
        pringinfo(arr,
                ( msg)->{
//            对字符串进行分割,取出姓名
            String name = msg.split(",")[0];
                    System.out.print("姓名:"+name);
                },
                (msg)->{
//            对字符串进行分割,取出性别
            String sex = msg.split(",")[1];
                    System.out.println("性别:"+sex);
                });

    }
}

 

 

Predicate接口

 

/**
 * java.util.function.Predicate<T>接口
 * 作用:对某种数据类型的数据进行判断,结果返回一个boolean值
 *     接口有一个抽象方法:
 *     boolean test(T t):用来对指定数据类型数据进行判断
 *
 *
 */

 

public class PredicateTest {
    //定义方法,参数传递一个字符串,Predicate接口
    public static boolean checkString(String s, Predicate<String> pred){
        return  pred.test(s);
    }

    public static void main(String[] args) {
        String  s ="ABCD";
        System.out.println(checkString(s,(ss)->{
            if (ss.length()>5){
                return true;
            }else {
                return false;
            }
        })
        );
    }
}

 

接口中的默认方法,and  or negate:

/**
 * 判断一个字符串长度大于5 且字符串是否包含a
 */
public class PredicateT {
    public static boolean checkString(String s, Predicate<String> p1, Predicate<String> p2){
//        return  p1.test(s) && p2.test(s);
        return p1.and(p2).test(s);//等价于上面
//        return p1.or(p2).test(s);////        return p1.negate().test(s)//negate非
    }

    public static void main(String[] args) {
        String s = "anbced";
        boolean re =checkString(s,
                (ss)->{
            return ss.length()>5;
                },
                (ss)->{
            return ss.contains("a");
                });
        System.out.println(re);
    }
}

 

练习:

/**
 * 将数组里面的名字等于4位的且是女生的用arraylist保存起来
 */
public class PredicateTest2 {
    private static void xuanze(String[] arr, Predicate<String> pre1, Predicate<String> pre2){
        List<String> stringList =new ArrayList<>();
        for (String s : arr) {
            if(pre1.and(pre2).test(s)){
                stringList.add(s);
            }
        }
        System.out.println(stringList);
    }

    public static void main(String[] args) {
        String[] arr = {"1111,N","222,M","44444","N"};
        xuanze(arr,
                (s)->{
                 return  s.split(",")[0].length()==4;
                },
                (s)->{
                return s.split(",")[1].equals("N");
                });
    }
}

 

Function接口

/**
 * java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
 *                          前者位前置条件,后者位后置条件
 *  接口里面的抽象方法: R apply<T t>,根据类型T参数获取类型R参数的结果。
 *          了例如:String类型转换位Integer类型
 */

 

public class FunctionDemo {
    public static void exchange(String s, Function<String,Integer> function){
        Integer i = function.apply(s);
//        int i1 = function.apply(s);自动拆箱,将Integer类型自动拆成Int类型
        System.out.println(i);
    }

    public static void main(String[] args) {
        String si = "123";
        exchange(si,(ss)->{
           return Integer.parseInt(ss);
        });
//优化
//        exchange(si,(ss)-> Integer.parseInt(ss)
//        );
    }
}

 

默认方法andThen

 

/**
 * Function接口的默认方法andThen:用来进行组合操作
 * 需求:String类型的123 转换为Integer类型,把转换结果加10
 * 把增加之后的Integer类型的数据转换为String类型
 *
 */
public class FunctionDemo2_andThen {

    private static void change(String s,Function<String,Integer> f1,Function<Integer,String> f2){
       String ss =  f1.andThen(f2).apply(s);
        System.out.println(ss);
    }

    public static void main(String[] args) {
        String s = "123";
        change(s,
                (s1)->{
            //把字符串转整数
                    return Integer.parseInt(s1)+10;
                },
                (i)->{
            return i+"";
                });
    }
}

 

推荐阅读