接口中有一种特殊的接口,这类接口中只有一个抽象方法。我们把这种接口称为函数式接口(FunctionalInterface) 可以使用 @FunctionalInterface 标记函数接口 函数接口表示某个功能(能力)
函数式接口:有且只有一个抽象方法的接口,称之为函数式接口
当然接口中可以包含其他的方法(默认,静态,私有)
package com.finterface;
/*@FunctionalInterface注解:
作用:是检测接口是否是一个函数式接口
注意:如果接口中没有抽象方法或者抽象方法的个数多余1个,该注解则会报错
* */
@FunctionalInterface
public interface MyFunctionalInterface {
//定义一个抽象方法
public abstract void method(); // 同样public abstract可以省略。。
}
由于接口当中的抽象方法 public abstract
是可以省略的,所以定义一个函数式接口很简单:
@FunctionalInterface
public interface MyFunctionalInterface {
//定义一个抽象方法
void method();
}
注意:这里我省略了上面4.1中的接口的实现类MyFunctionalInterfaceImpl
package com.finterface;
/*函数式接口的使用:一般可以作为方法的参数和返回值类型*/
public class Demo {
//定义一个方法,参数使用函数式接口MyFunctionalInterface
public static void show(MyFunctionalInterface inter){
inter.method();
}
public static void main(String[] args) {
//调用show方法,方法参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionalInterfaceImpl());
//调用show方法,方法参数是一个接口,所以我们可以传递接口的匿名内部类
show(new MyFunctionalInterface() {
@Override
public void method() {
System.out.println("使用匿名内部类重写接口中的抽象方法");
}
});
//调用show方法,方法参数是一个函数式接口,所以我们可以使用lambda表达式
show(()->{
System.out.println("使用lambda表达式重写接口中的抽象方法");
});
//如果上面的这个代码中有且仅有这一条语句,那么里面{}括号可以省略,而且输出语句后面的;也可省略
show(()->
System.out.println("简化后的lambda表达式。")
);
}
}
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式,是延迟执行的,这正好可以作为解决方案,提升性能。
性能浪费的日志案例:
注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,满足条件的情况下进行打印输出:
package com.Lambda;
/*日志案例
* 通过测试发现以下代码存在一些性能浪费问题:
* 通过调用show方法,传递的第二个参数是一个拼接后的字符串,但是如果show方法中传递的日志等级不是1级
* 那么就不会打印输出拼接后的字符串,所以就感觉字符串白拼接了,存在了浪费。*/
public class DemoLogger {
//定义一个根据日志的等级,来显示日志信息的方法
public static void show(int level,String massage){
//对日志等级进行判断
if (level == 1) {
System.out.println(massage);
}
}
public static void main(String[] args) {
//定义三个日志信息
String msg1 = "hello ";
String msg2 = "world ";
String msg3 = "java";
show(1,msg1+msg2+msg3);
}
}
下面我们就来使用lambda来优化上面的日志案例:
//定义一个函数式接口
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
package com.Lambda;
/*使用lambda表达式进行优化日志案例:
* lambda的特点:延迟加载
* lambda的使用前提:必须存在函数式接口*/
public class DemoLambda {
//定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口中的builderMessage方法
public static void showLogger(int level,MessageBuilder builder){
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msg1 = "hello ";
String msg2 = "world ";
String msg3 = "java";
//调用showLogger方法,参数MessageBuilder是一个函数式接口,所以可以传递lambda表达式
showLogger(1,()->{
return msg1+msg2+msg3;
});
/*使用lambda表达式作为参数传递,仅仅是把参数传递到showLogger方法中
* 只有满足条件,才会调用MessageBuilder中的方法buildMessage(),才会进行字符串的拼接
* 如果条件不满足,则MessageBuilder接口中的方法buildMessage()不会执行
* 所以拼接的字符串也不会执行,所以不存在性能的浪费。*/
}
}
作为方法的参数就和上面讲过的一样。
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式,当需要通过一个方法来获取一个java.util.Comparator
接口类型的对象作为排序器时,就可以调用该方法获取:
package com.Lambda;
import java.util.Arrays;
import java.util.Comparator;
public class DemoComparator {
//定义一个方法,方法的返回值类型使用函数式接口Comparator
public static Comparator<String> getComparator() {
//方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
/*return new Comparator() {
@Override
public int compare(String o1, String o2) {
//按照字符串的降序排序
return o2.length()-o1.length();
}
};*/
//对上面代码的优化:方法的返回值类型是一个函数式接口,所以我们可以返回一个lambda表达式
/*return (String o1,String o2)->{
return o2.length()-o1.length();
};*/
//继续优化lambda表达式
return ((o1, o2) -> o2.length()-o1.length());
}
public static void main(String[] args) {
//创建一个字符串数组
String[] arr = {"aaaa","bb","cccccccc","dedsass"};
//输出排序前的数组
System.out.println(Arrays.toString(arr));//[aaaa, bb, cccccccc, dedsass]
//调用Arrays数组中的sort方法,对字符串数组进行排序
Arrays.sort(arr,getComparator())z;
//输出排序后的数组
System.out.println(Arrays.toString(arr));//[cccccccc, dedsass, aaaa, bb]
}
}
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function
包中被提供。
java.util.function.Supplier
接口仅包含一个无参的方法:T get()
.用来获取一个泛型参数指定类型的对象数据。
import java.util.function.Supplier;
/*常用函数式接口
* java.util.function.Supplier接口仅包含一个无参构造方法get()
* Supplier接口称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据*/
public class DemoSupplier {
//定义一个方法,方法的参数传递Supplier接口,泛型执行String,get方法就会返回一个String类型的
public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
//调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递lambda表达式
String s = getString(()->{
//生产一个字符串,并返回
return "张三";
});
System.out.println(s);
//优化lambda表达式:
String s2 = getString(()->"张三");
}
}
java.util.function.Consumer
接口则正好与supplier接口相反,他不是产生一个数据,而是消费一个数据,其数据类型由泛型决定。
Consumer接口中包含抽象方法 void accept(T t)
,意为消费一个指定泛型的数据。
Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据。
import java.util.function.Consumer;
public class DemoConsumer {
/*定义一个方法:
* 方法的参数传递Consumer接口,泛型使用String
* 可以使用Consumer接口消费字符串的姓名*/
public static void method(String name, Consumer<String> con){
con.accept(name);
}
public static void main(String[] args) {
//调用method方法,传递字符串姓名,方法的拎一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
method("王可六",(String name)->{
//对传递的字符串进行消费
//消费方式:直接输出字符串
System.out.println(name);//王可六
//补充消费方式:把字符串进行反转输出(了解)
String reName = new StringBuffer(name).reverse().toString();
System.out.println(reName);//六可王
});
}
}
如果一个方法的参数和返回值类型都是Consumer
类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen。
备注:
java.util.Objects
的requireNonNull
静态方法将会在参数为null时主动抛出NullPointerException
异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen
的语义正是一步接一步操作。例如两个步骤组合的情况:
package com.Lambda;
import java.util.function.Consumer;
/*
* Consumer接口的默认方法andThen
* 作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费
* 例如:
* Consumer con1
* Consumer con2
* con1.accept(s)
* con2.accept(s)
* 连接两个Consumer接口:
* con1.andThen(con2).accept(s)---->谁写前边,谁就先进行消费*/
public class DemoAndThen {
//定义一个方法,方法的参数传递一个字符串和两个Consumer接口
public static void method(String s, Consumer<String> con1,Consumer<String> con2){
// con1.accept(s);
// con2.accept(s);
//使用andThen方法,把两个Consumer接口连接到一起,然后再消费数据
con1.andThen(con2).accept(s);
}
public static void main(String[] args) {
method("hello",(t)->{
//消费方式:把字符串转换为大写
System.out.println(t.toUpperCase());//HELLO
},(t)->{
System.out.println(t.toLowerCase());//hello
});
}
}
有时候我们需要对某种类型的数据进行判断,从而达到一个Boolean值结果,这时可以使用 java.util.function.Predicate
接口。
Predicate
接口中包含一个抽象方法:boolean test(T t)
.用于条件判断的场景:
package com.Lambda;
import java.util.function.Predicate;
/*boolean test(T t):用来对指定数据类型进行判断的方法
* 结果:符合条件返回true,否则返回false*/
public class DemoPredicate {
/*定义一个方法:
* 参数传递一个String类型的字符串,传递一个predicate接口,泛型使用String
* 使用predicate中的方法test对字符串进行判断,并把判断的结果返回*/
public static Boolean checkString(String s, Predicate<String> pre){
return pre.test(s);
}
public static void main(String[] args) {
String s = "asdfqwq";
boolean b = checkString(s,(String str)->{
//对传递的字符串进行判断,判断字符串的长度是否大于5,并把判断结果返回。
return str.length()>5;
});
//优化lambda表达式
boolean b2 = checkString(s,str->str.length()>5);
System.out.println(b2);//true
}
}
既然是条件判断,就会存在与或非三种常见的逻辑关系。其中将两个Predicate
条件使用“与”逻辑连接起来实现“并且”的效果,可以使用default方法and
。
如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:
package com.Lambda;
import java.util.function.Predicate;
public class DemoPredicate_and {
/*定义一个方法,方法的参数,传递一个字符串;传递两个Predicate接口:
* 一个用于判断字符串的长度是否大于5
* 一个用于判断字符串中是否包含a
* 两个条件必须同时满足,所以用与来判断。*/
public static boolean checkString(String s, Predicate<String> pre1,Predicate<String> pre2){
//return pre1.test(s)&& pre2.test(s);
return pre1.and(pre2).test(s);//等同于上面的一行代码
}
public static void main(String[] args) {
String s = "abcdef";
boolean b = checkString(s,(String str)->{
//判断字符串长度是否大于5
return str.length()>5;
},(String str)->{
//判断字符串中是否包含a
return str.contains("a");
});
System.out.println(b);//true
}
}
与and的“与”类似,默认方法or实现逻辑关系中的“或”。
比如希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or"名称即可,满足一个条件即可。其他不变:
public class DemoPredicate_or {
public static boolean checkString(String s, Predicate<String> pre1,Predicate<String> pre2){
//return pre1.test(s)|| pre2.test(s);
return pre1.or(pre2).test(s);
}
public static void main(String[] args) {
String s = "Wang";
boolean b = checkString(s,(String str)->{
return str.contains("A");
},(String str)->{
return str.contains("L");
});
System.out.println(b);//false
}
}
“与”“或”已经了解了,剩下的“非”(取反),默认方法 negate
从实现中容易看出,它是执行了test方法之后,对结果Boolean值进行了“!”取反而已,一定要在 test
方法调用之前调用 negate
方法,正如and和or方法一样:
/*需求:判断一个字符串的长度是否大于5
* 如果长度大于5,则false
* 如果长度不大于5,则true*/
public class DemoPredicate_negate {
public static boolean checkString(String s, Predicate<String> pre){
//return !pre.test(s);
return pre.negate().test(s);
}
public static void main(String[] args) {
String s = "Wang";
boolean b = checkString(s,(String str)->{
return str.length()>5;
});
System.out.println(b);//true
}
}
java.util.function.Function
接口用来根据一个类型得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
Function
接口中最主要的抽象方法为:R apply(T t)
,根据类型T的参数获取类型R的结果。
使用的场景例如:将 String
类型转换为 Integer
类型。
/*将 `String`类型转换为 `Integer`类型。*/
public class DemoFunctionApply {
/*定义一个方法:
* 方法的参数传递一个字符串类型的整数,一个Function接口,泛型使用
使用Function接口中的方法apply,把字符串类型的整数,转换成Integer类型的整数*/
public static void change(String s, Function<String,Integer> fun){
//Integer num = fun.apply(s);//这句代码也是可以的
int num = fun.apply(s);//这句代码使用了自动拆箱
System.out.println(num);
}
public static void main(String[] args) {
String s = "1234";
change(s,(String str)->{
//把字符串类型的整数,转换成Integer类型的整数返回
return Integer.parseInt(str);
});
//优化lambda
change(s,str->Integer.parseInt(str));
}
}
/*需求:
* 把字符串类型的“123”转换为Integer类型,结果+10
* 再把增加之后的Integer类型的数据,转换为String类型的
* 分析:共转换了两次
* 第一次是把字符串类型的“123”转换为Integer类型,所以使用Function fun1
Integer i = fun1.apply("123")+10;
第二次是把Integer类型的数据,转换为String类型,可以使用Function fun2
* String str = fun2.apply(i);
*我们可使用andThen方法,把两簇转换组合在一起
* String s = fun1.andThen(fun2).apply("123");
* fun1先调用apply方法,把字符串转换为Integer
* fun2再调用apply方法,把Integer转换为字符串。*/
public class DemoFunctionAndThen {
public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){
String str = fun1.andThen(fun2).apply(s);
System.out.println(str);
}
public static void main(String[] args){
String s = "123";
change(s,(String str)->{
return Integer.parseInt(str)+10;
},(Integer i)->{
return i+"";
});
//优化lambda表达式
change(s,str->Integer.parseInt(str)+10,i->i+"");
}
}
public class DemoStream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("张三丰");
list.add("张无忌");
//要求:先对list集合中的元素进行过滤,找出姓张的人,
//然后对新得到的姓张的集合中的元素进行过滤,找出姓名长度为3的人
//最后遍历最终的集合
list.stream().filter(name->name.startsWith("张"))
.filter(name->name.length()>2)
.forEach(name-> System.out.println(name));//注意forEach也是函数式接口
}
/*这里就是使用了stream流的方式遍历集合,对集合中的数据进行过滤
* 而不再使用之前的那种采用三次forEach循环来过滤了。*/
}
和以前collection操作不同,Stream操作还有两个基础的特征:
当使用流的时候,通常包括三个基本步骤:获取一个数据源(source)->数据转换->执行操作获取想要的结果,每次转换,原有Stream对象不改变,而是返回一个新的Stream对象(可以有多次转换)。