1、什么是函数式接口
只包含一个抽象方法的接口,称为函数式接口,该抽象方法也被称为函数方法。 我们熟知的Comparator和Runnable、Callable就属于函数式接口。
这样的接口这么简单,都不值得在程序中定义,所以,JDK8在 java.util.function
中定义了几个标准的函数式接口,供我们使用。Package java.util.function(opens new window)
可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
2、自定义函数式接口
//@FunctionalInterface标注该接口会被设计成一个函数式接口,否则会编译错误@FunctionalInterfacepublicinterfaceMyFunc<T>{TgetValue(Tt);}publicstaticStringtoUpperString(MyFunc<String>myFunc,Stringstr){returnmyFunc.getValue(str);}publicstaticvoidmain(String[]args){StringnewStr=toUpperString((str)->str.toUpperCase(),"abc");System.out.println(newStr);}
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
函数接口为lambda表达式和方法引用提供目标类型
3、内置四大核心函数式接口)3. Java 内置四大核心函数式接口
/**Java8内置的四大核心函数式接口*Consumer<T>:消费型接口voidaccept(Tt);*Supplier<T>:供给型接口Tget();*Function<T,R>:函数型接口Rapply(Tt);*Predicate<T>:断言型接口booleantest(Tt);*/publicclassFunctionalInterfaceTest{//Predicate<T>断言型接口:将满足条件的字符串放入集合publicList<String>filterStr(List<String>list,Predicate<String>predicate){List<String>newList=newArrayList<>();for(Strings:list){if(predicate.test(s)){newList.add(s);}}returnnewList;}@TestpublicvoidtestPredicate(){List<String>list=Arrays.asList("hello","java8","function","predicate");List<String>newList=filterStr(list,s->s.length()>5);for(Strings:newList){System.out.println(s);}}//Function<T,R>函数型接口:处理字符串publicStringstrHandler(Stringstr,Function<String,String>function){returnfunction.apply(str);}@TestpublicvoidtestFunction(){Stringstr1=strHandler("测试内置函数式接口",s->s.substring(2));System.out.println(str1);Stringstr2=strHandler("abcdefg",s->s.toUpperCase());System.out.println(str2);}//Supplier<T>供给型接口:产生指定个数的整数,并放入集合publicList<Integer>getNumList(intnum,Supplier<Integer>supplier){List<Integer>list=newArrayList<>();for(inti=0;i<num;i++){Integern=supplier.get();list.add(n);}returnlist;}@TestpublicvoidtestSupplier(){List<Integer>numList=getNumList(10,()->(int)(Math.random()*100));for(Integernum:numList){System.out.println(num);}}//Consumer<T>消费型接口:修改参数publicvoidmodifyValue(Integervalue,Consumer<Integer>consumer){consumer.accept(value);}@TestpublicvoidtestConsumer(){modifyValue(3,s->System.out.println(s*3));}}
Package java.util.function 包下还提供了很多其他的演变方法。
Java类型要么是引用类型(Byte、Integer、Objuct、List),要么是原始类型(int、double、byte、char)。但是泛型只能绑定到引用类型。将原始类型转换为对应的引用类型,叫装箱,相反,将引用类型转换为对应的原始类型,叫拆箱。当然Java提供了自动装箱机制帮我们执行了这一操作。
List<Integer>list=newArrayList();for(inti=0;i<10;i++){list.add(i);//int被装箱为Integer}
但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
以上funciton包中的IntPredicate、DoubleConsumer、LongBinaryOperator、ToDoubleFuncation等就是避免自动装箱的操作。一般,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀。