函数式接口在Java指的是:有且仅有一个抽象方法的接口就称为函数式接口。java
函数式接口,适用于函数式编程的,在Java当中的函数式编程体如今Lambda,因此函数式接口就是用来服务Lambda表达式。只有确保接口当中有且仅有一个抽象方法,Java中的Lambda才能顺利进行推导。mysql
备注:"语法糖"是指使用更加便利方便,可是原理不变的代码语法。就好比遍历集合时使用for-each语法,其实底层使用的是迭代器,这即是"语法糖"。sql
只有确保接口当中有且仅有一个抽象方法便可:编程
修饰符 interface InterfaceName{ // 只能定义一个抽象方法 public abstract 返回值类型 方法名称(参数列表); // 还能够定义其余的非抽象方法 }
示例:数组
public interface FunctionInterfaceOne { public abstract void show01(); public default void show02(){ //..... } // void show03(); 有且仅有一个抽象方法,才称为函数式接口 }
与@Override注解做用相似,Java 8中专门为函数式接口引入的一个新注解@FunctionalInterface
,该注解主要定义在接口上。一旦在接口上使用该注解,编译期将会强制检查该接口是否是一个函数式接口,该接口中是否是有且仅有一个抽象方法,若是不是,编译报错。tomcat
@FunctionalInterface public interface FunctionInterfaceOne { // 定义一个抽象的方法 void method(); //void show(); default void show02(){ } }
对于自定义的函数式接口,通常用于方法的参数和返回值上。ide
可以在兼顾Java的面向对象特性基础上,经过Lambda表达式与后面的方法引用,为开发者打开函数式编程的的大门。函数式编程
有些场景的代码运行执行后,结果不必定会被使用到,从而形成性能的浪费。而Lambda表达式是延迟执行的,正好能够解决此问题,提高性能。函数
代码以下:性能
public static void main(String[] args) { // 定义一些日志信息 String message1 = "执行mysqld.exe操做"; String message2 = "执行java.exe操做"; String message3 = "执行tomcat.exe操做"; // 调用showLog方法,参数是一个函数式接口--BuildLogMessage接口,因此可使用Lambda表达式 /* showLog(2, () -> { // 返回一个拼接好的字符串 return message1 + message2 + message3; });*/ // 简化Lambda表达式 /* 使用Lambda表达式做为参数传递, 只有知足条件,日志的等级小于等于3 才会调用此接口BuildLogMessage当中的方法buildLogMessage 才会进行字符串的拼接动做 若是条件不知足,日志的等级大于3 那么BuildLogMessage接口当中的方法buildLogMessage也不会执行 因此拼接字符串的动做也不会执行 因此不会存在性能上的浪费。 */ showLog(4, () -> { System.out.println("前面的日志等级大于3,此处不执行!"); return message1 + message2 + message3; }); }
备注:实际上使用内部类也能够达到这样的效果,只是将代码操做延迟到另一个对象当中经过调用方法来完成。
后面的代码的执行取决于前面的条件的判断结果。
在Java当中,Lambda表达式是做为匿名内部类的替代品,若是一个方法的参数是一个函数式接口类型,那么可使用Lambda表达式进行替代。
java.lang.Runnable
接口就是一个函数式接口
代码以下:
public class Demo01Lambda { // 定义一个方法,开启线程的方法,方法传入一个函数式接口类型的参数 public static void startThread(Runnable r) { new Thread(r).start(); } public static void main(String[] args) { startThread(() -> { System.out.println("开启一个新线程,线程任务被执行了!"); }); // 优化Lambda startThread(() ->System.out.println("开启一个新线程,线程任务被执行了!"); } }
若是一个方法的返回值类型是一个函数式接口,那么咱们能够直接使用一个Lambda表达式。
java.util.Comparator
接口是一个函数式接口
代码以下:
public class Demo02Lambda { // 定义一个方法,方法的返回值类型是一个函数式接口类型Comparator public static Comparator<String> createComparator() { // 返回值就是一个函数式接口 /*return new Comparator() { @Override public int compare(String o1,String o2){ // 自定义比较的规则 升序/降序 // 字符串的长度 return o1.length()-o2.length();// 升序 } } */ // 使用Lambda 字符串的长度升序 return (o1,o2) -> o1.length() - o2.length(); } public static void main(String[] args) { String[] strs = {"aaa","a","abcdefg","ggggg"}; Arrays.sort(strs, createComparator()); System.out.println(Arrays.toString(strs));// {"a","aaa","ggggg","abcdefg"} } }
JDK提供了大量经常使用的函数式接口,丰富Lambda表达式的使用场景。他们主要在java.util.function
包中被提供。
java.util.function.Supplier<T>
接口,该接口有且仅有一个无参的方法:T get()
。用来获取一个泛型参数指定类型的对象数据。因为该接口是一个函数式接口,因此咱们可使用Lambda表达式来操做它。
Supplier
代码以下:
// 定义一个方法,方法的参数传递一个Supplier<T>接口,泛型指定String,get方法就会返回一个String public static String getString(Supplier<String> sup) { return sup.get(); } // 定义一个方法,方法的参数传递一个Supplier<T>接口,泛型我指定为Integer,get方法就会返回一个int public static int getNum(Supplier<Integer> sup) { return sup.get(); } public static void main(String[] args) { // 调用getString方法,方法的参数传递Supplier<T>是一个函数式接口,那么咱们就可使用Lambda /* String str = getString(() -> { // 生产一个字符串并返回 return "你好Java"; }); System.out.println(str);*/ // 求一个int类型的数组中的最值 int[] arr = {10,20,5,8,3,50}; int maxNum = getNum(() -> { // 求出数组的最大值 int max = arr[0]; for (int i : arr) { // 判断 if (max < i) { max = i; } } return max; }); // 输出最大值 System.out.println(maxNum);// 50 }
java.util.function.Consumer<T>
接口恰好和Supplier接口相反,它不是用来生产一个数据,而是消费一个数据。
数据的类型由泛型来指定。
意思就是消费一个指定泛型的数据。
代码以下:
// 定义一个方法,方法的参数传递一个Consumer<String>接口,传递一个字符串变量 public static void consumer(String str, Consumer<String> con) { // 使用消费型接口对象,消费传递的字符串值。 con.accept(str); } public static void main(String[] args) { // 来调用消费方法consumer,Consumer<String>接口是一个函数式接口类型,因此可使用Lambda表达式 consumer("abcdefg", name -> { // 把里面的abcdefg字符串改成大写输出 消费的规则自定义 String str = name.toUpperCase(); String s = new StringBuffer(str).reverse().toString(); System.out.println(s);// GFEDCBA }); }
若是一个方法的参数和返回值全都是Consumer类型,那么就能够实现这样的效果:消费数据的时候,首先作一个消费的操做,在作一个消费的操做,实现组合。能够经过Consumer接口当中的默认方法:andThen
来实现。
代码以下:
// 定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型指定为字符串 public static void consumers(String str, Consumer<String> con1,Consumer<String> con2) { /* con1.accept(str); con2.accept(str);*/ // andThen 连续消费 default Consumer<String> andThen // 先执行左边的Consumer--con1的动做,andThen--->再次执行Consumer--con2动做 con1.andThen(con2).accept(str); // 规则 con1链接con2 ,先执行con1消费数据,在执行con2消费数据 } public static void main(String[] args) { // 因为consumers方法的参数Consumer接口是一个函数式接口,可使用Lambda表达式 consumers("Java31-中国最棒-都是业界大佬", (name1)->{ // 消费规则 // 截取传入的字符串 String sub = name1.substring(0, 6); System.out.println(sub); }, (name2) -> { // 定义消费的规则 分红字符串数组展现 String[] strs = name2.split("-"); System.out.println(Arrays.toString(strs));// {“Java31","中国最棒","都是业界大佬"} }); }
经过查看源码得知:andThen方法不容许传入一个null对象不然就会抛出空指针异常。
要想把两次消费的动做链接起来,须要传入两个Consumer接口,经过andThen
方法实现一步一步执行消费动做。
练习:
定义一个字符串数组,存储每个人的信息如:"张三,20,郑州市",存储5我的的信息
使用Consumer接口,按照指定的格式进行打印输出:姓名:张三;年龄:20;地址:郑州市
要求将打印姓名的动做做为第一个Consumer接口的规则
将打印年龄的动做做为第二个Consumer接口的规则
将打印地址的动做做为第三个Consumer接口的规则。
最终将三个Consumer接口按照规定的顺序拼接输出出来。
代码以下:
// 规则 public static void consumers(String[] arr, Consumer<String> con1, Consumer<String> con2, Consumer<String> con3) { // 操做arr数组当中的每个元素 for (String str : arr) { con1.andThen(con2).andThen(con3).accept(str);// 定义了消费的前后的顺序 } } public static void main(String[] args) { // 定义一个字符串数组 String[] arr = {"李四,20,南阳市", "张三,20,郑州市", "小孙,20,开封市", "小丽,20,信阳市", "小赵,20,洛阳市"}; // 调用consumers方法,因为Consumer接口是一个函数式接口,因此可使用Lambda consumers(arr, one -> System.out.print("姓名:" + one.split(",")[0] + ";"), two -> System.out.print("年龄:" + two.split(",")[1] + ";"), three -> System.out.println("地址:" + three.split(",")[2])); }