1.函数式接口概述
函数式接口在java中指的是,有且仅有一个抽象方法的接口(但可以有其他静态方法、默认方法、私有方法等)。函数式接口适用于函数式编程场景的接口,Java中就是Lambda。
tips:(1)”语法糖“,指使用更加方便,原理不变的代码语法;(2)@Override注解的作用,是检查方法是否为重写方法,是则编译成功,否则编译失败。 (3)同样,可以在函数式接口前,添加一个@FunctionalInterface注解,其作用是可以检测接口是否是一个函数式接口,是的话就编译成功,否则编译失败。
函数式接口的格式
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
//其他非抽象方法内容
}
1.1 函数式接口使用
/*
函数式接口的使用:一般可以作为方法的参数和返回值类型
*/
public class Demo {
//定义一个方法,参数使用函数式接口MyFunctionalInterface
public static void show(MyFunctionalInterface myInter) {
myInter.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表达式重写接口中的抽象方法");
});
//简化lambda表达式
show(()->System.out.println("使用Lambda表达式重写接口中的抽象方法"));
}
public static void main(String[] args) {
sho
}
}
注意,匿名内部类创建时,编译时会在当前目创建一个XXX$1.class对应匿名内部类中的内容和方法;而如果使用Lambda表达式时,则不会在编译时创建这么一个文件,因此效率要高一些。
2.函数式编程
2.1 案例一:日志案例
public class Demo {
public static void showLog(int level, String message) {
if(level == 1) {
System.out.println(messgae);
}
}
public static void main(String[] args) {
String msg1 = "Hello";
String msg2 = "World";
showLog(1,msg1+msg2);
}
}
上面的代码,存在性能浪费的问题。showLog方法中,传递的第二个参数String message,是一个拼接后的字符串,如果此时,level等级不为1,那么不会执行showLog中的主要内容,因此String就浪费了。
上面这种浪费,是可以用Lambda表达式优化的。可优化的原因,是因为Lambda表示式有延迟加载的特点。优化代码如下。
//先定义一个函数式接口
@FunctionalInterface
public interface MessageBuilder {
public abstract String builderMessage();
}
//
public class Demo {
//优化在于,第二个参数改为函数式接口
public static void showLog(int level, MessageBuilder mb) {
if(level == 1) {
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args) {
String msg1 = "Hello";
String msg2 = "World";
//调用showLog方法,参数MessageBuilder是一个函数式接口,可以选择传递Lambda表达式
showLog(1,()->{
System.out.println("不满足条件不执行");
return msg1+msg2;
});
/*
使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中;
只有满足level=1, 才会调用MessageBuidder中的方法,否则不执行。
所以不存在性能浪费。
*/
}
}
2.2 案例二:函数式接口作为方法的参数
例如java.lang.Runnable接口就是一个函数式接口,假设一个startThread方法使用该接口作为参数,那么可以使用lambda表达式进行传参。这种情况实际上和Thread类的构造方法参数为Runnable,没有本制区别。
public class Demo {
public static void startThread(Runnable run) {
new Thread(run).start();
}
public static void main(String[] args) }
startThread(new Runnable() {
@Override
public void run() {
System.out.println("测试");
}
});
//Lambda表达式
startThread(()->{
System.out.println("测试");
});
//优化Lambda
startThread(()->System.out.println("测试"));
}
}
2.3 案例三:函数式接口作为返回值类型
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator接口类型的对象,作为排序器的参数时,就可以调用此方法获取。
public class Demo {
public static Comparator<String> getComparator() {
return new Comparator<String>() {
@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 = {"aaa","v"};
//作为排序器的参数时,就可以调用此方法获取
Arrays.sort(arr,getComparator());
}
}
3. 常用的函数式接口
JDK提供了大量常用的函数式接口,以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。
3.1 Supplier接口
Supplier接口,被称为生产型接口,指定接口的泛型是什么类型,那么接口中get方法就会产生什么类型数据。基本使用如下:
public class Demo {
//定义一个方法,方法的参数传递Supplier<T>接口,泛型指定
//String,get方法就会返回一个String
public static String getString(Supplier<String> sup) {
return sup.get();
}
public static void main(String[] args) {
String s = getString(() -> {
return "Lucy";
});
//优化lambda
String s2 = getString(()->"Lucy");
}
}
练习:求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer类
public class Demo {
//定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口
public static int getMax(Supplier<Integer> sup) {
return sup.get();
}
public static void main(String[] args) {
int[] arr = {100,0,20,2};
//调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
int max = getMax(()->{
int max = arr[0];
for(int i :arr) {
if(i>max) {
max = i;
}
}
return max;
}
}
}
3.2 Consumer接口
与Supplier接口相反,这是消费一个数据。Consumer接口中包含抽象方法void accept(T t),消费一个指定泛型的数据,至于具体怎么消费使用,需要自定义(输出,计算)。
Consumer中的accept没有返回值,有返回值的是Supplier中的get方法。
public class Demo {
public static void method(String name, Consumer<String> con) {
con.accept(name);
}
public static void main(String[] args) {
method("赵丽颖",(String name) -> {
String reName = new StringBuffer(name).reverse().toString();
Sytem.out.println(reName);
}
}
}
Consumer接口中,还有一个默认方法,andThen。如果一个方法的参数和返回值全是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再进行一个操作,实现组合(实际中,是对要进行操作拆分的操作,使用andThen这一个功能)。
例如:
Consumer<String> con1;
Consumer<String> con2;
String s = "hello";
con1.accept(s);
con2.accept(2);
//上面代码,可以用andThen方法简化
con1.andThen(con2).accept(s); //谁在前,谁先消费
实例:
public class Demo{
public static void method(String s, Consumer<String> con1, Consumer<String> con2) {
con1.accept(s);
con2.accept(2);
//或者用下面的andThen
con1.andThen(con2).accept(s);
}
public static void main(String[] args) {
method("Hello",
(t) - > {
System.out.println(t.toUpperCase());
},
(t) -> {
System.out.println(t.toLowerCase());
}
);
}
}
下面是一个练习,内容是格式化打印信息,要求打印姓名的动作作为i的一个Consumer接口的Lambda实例,打印性别作为第二个,然后将两个Consumer接口拼接在一起。
public class Demo {
public static void printInfo(String arr, Consumer<String> con1, Consumer<String> con2) {
for(String message:arr) {
con1.andThen(con2).accept(message);
}
}
public static void main(String[] args) {
String[] arr = {"Lucy, W", "Jordan,M"};
printInfo(arr,(message) -> {
String name = message.split(",")[0];
System.out.println("姓名:"+name);
}, (message) ->{
String age = message.split(",")[1];
System.out.println(". 年龄:" + age);
});
}
}
3.3 Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值得结果,就需要用Predicate接口。
Predicate接口中包含一个抽象方法,boolean test(T t),用于条件判断场景。
public class Demo {
public static boolean checkString(String s, Predicate<String> pre) {
return pre.test(s);
}
public static void main(String[] args) {
String s = "abcd";
boolean b = checkString(s, (String str) -> {
return str.length()>5;
});
//lambda优化简写
boolean b = checkString(s,str->str.length()>5);
}
}
注意,这里再记录一下lambda表达式省略得条件。可以省略的内容:
1)(参数列表):括号中,参数列表的数据类型,可以省略不写
2)(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略;
3)(一些代码):如果{}中的代码只有一行 ,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{},则return和分号必须一起省略。
Predicate接口中,还有一个默认方法and,它得目的是将两个Predicate条件使用与逻辑连接起来,实现并且得效果。
/*
需求:判断一个字符串,有两个判断得条件:
1.判断字符串得长度是否大于5;
2.判断字符串是否包含a;
*/
public class Demo {
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) - > {
return str.length() > 5;
}, (String str) -> {
return str.contains("a");
});
System.out.println(b);
}
}
Predicate接口中,还有两个默认方法or和negate。
or表示或的关系;negate表示取反(非)的关系。
//基本内容和and方法得示例代码相似
pre1.or(pre2).test(s);
//与return !pre.test(s);相同
return pre.negate().test(s);
综合练习,集合信息的筛选,通过Predicate接口的拼装,将符合要求的字符串筛选到集合ArrayList中,需要满足是女生,姓名为4个字。
//有两个判断条件,所以需要连个Predicate接口
public class Demo {
public static ArrayList<String> filter(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
ArrayList<String> list = new ArrayList<>();
for(String s : arr) {
boolean b = pre1.and(pre2).test(s);
if(b) {
list.add(s);
}
}
return list;
}
public static void main(String[] args) {
String[] array = {"Lucy,W", "Jordan,M"};
ArrayList<String> list = filter(array,(String s) -> {
return s.split(",")[1].equals("W");
}, (String s) -> {
return s.split(",")[0].length() == 4;
});
}
}
3.4 Function接口
java.util.function.Function<T,R>接口,用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者为后置条件,即一个转换。
//Function接口中,最主要的抽象方法为R apply(T t),根据类
//型T的参数,获取类型R的结果,例如将String类型转换为Integer类型。
public class Demo{
public static void change(String s, Function<String,Integer> fun) {
Integer in = fun.apply(s);
int in = fun.apply(s); //自动拆箱 Integer->int
System.out.println(in);
}
public static void main(String[] args) {
String s = "1234";
change(s, (String str) -> {
return Integer.parseInt(str);
});
//简化lambda
change(s,str -> Integer.ParseInt(str));
}
}
Function接口中有一个默认andThen方法,用来进行组合操作。例如我们要把String类型转为Integer类型,然后把转换后的结果+10,之后再把新的Integer类型,转为String。
public class Demo {
public static void change(String s, Function<String,Integer> fun1, Function<Integer, String> fun2) {
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
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+"");
}
}
综合案例,自定义函数模型拼接。将字符串年龄部分,得到字符串,将字符串转换为int类型,再将int的数字夹100,得到结果。因此需要三个Function接口。
public class Demo {
public static int change(String s, Function<String,String> fun1, Function<String, Integer> fun2, Function<Integer,Integer> fun3) {
return fun1.andThen(fun2).andThen(fun3).apply(s);
}
public static void main(String[] args) {
String str = "Lucy,22";
change(str,(String s) -> {
return s.split(",")[1];
}, (String s)->{
return Integer.parseInt(s);
} ,(Integer i) -> {
return i+100;
});
System.out.println(num);
}
}