一、函数式接口
1.1概念:
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
1.2格式:
修饰符 interface 接口名称{
public abstract 返回值类型 方法名称(可选参数类型);
//其他非抽象方法内容
}
其中 public abstract 可以省略
1.3@FunctionalInterface注解
与@Override注解的作用类似,@FunctionalInterface注解可以用于接口的定义上。
@FunctionalInterface
public abstract MyFunctionalInerface{
void method();
}
一旦使用该注解就会强制检测是否确实有一个抽象方法。
1.4自定义函数式接口
使用上面写到的MyFunctionalInerface函数式接口
public class DemoFunctionalInerface{
private static void doSomething(MyFunctionalInerface inter){
inter.method();
}
public static void main(String[] args){
doSomething(()->System.out.println("Lambda执行"));
}
}
二、函数式编程
2.1Lambda延迟执行
有一些场景代码执行后,结构不一定被调用,从而造成性能浪费。二lambda表达式是延时执行的,正好作为解决方案,提升性能。
性能浪费的日志案例
public class Logger{
public static void log(int level,String msg){
if(level ==1){
System.out.println(msg);
}
}
public static void main(String[] args){
long l = System.currentTimeMillis();
String str1 = "Hello";
String str2 = "World";
String str3 = "Java";
log(i.str1+str2+str3);
long l1 = System.currentTimeMillis();
System.out.println(l1-l);// level==1:33、25、31 level==2:24、25、23
}
}
这一段代码的问题:无论级别是否满足,作为log方法的第二个参数,三个字符出一定会首先拼接并传入方法内,然后才会进行级别判断,如果级别不符合,则字符串白拼接了,存在性能浪费。
Lambda更优写法
@FunctionalInterface
public interface MyFunctionalInterface {
public abstract String method();
}
public class fun {
public static void showLogger(int level,MyFunctionalInterface lam){
if (level==1) System.out.println(lam.method());
}
public static void main(String[] args) {
long l = System.currentTimeMillis();
String str1 = "Hello";
String str2 = "World";
String str3 = "Java";
showLogger(1,() -> str1+str2+str3);
long l1 = System.currentTimeMillis();
System.out.println(l1-l);//level==1:26、26、23 level==2:2、1、4
}
}
从上面可以看出Lambda是延迟执行的,不符合级别的情况下,不会执行,不会产生性能浪费。
2.2使用Lambda作为方法的参数和返回值
作为参数:
/*
例如java.lang.Runnable接口就是一个函数式接口,
假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别。
*/
public class Demo01Runnable {
//定义一个方法startThread,方法的参数使用函数式接口Runnable
public static void startThread(Runnable run){
//开启多线程
new Thread(run).start();
}
public static void main(String[] args) {
//调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
}
});
//调用startThread方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
startThread(()->{
System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
});
//优化Lambda表达式
startThread(()->System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了"));
}
}
作为方法的返回值
/*
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。
当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调该方法获取。
*/
public class DomeComparator {
public static Comparator<String> getComparator(){
/*return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length()-o1.length();
}
};*/
/*return (String o1,String o2) ->{
return o2.length()-o1.length();
};*/
return (o1, o2) -> o2.length()-o1.length();
}
public static void main(String[] args) {
String[] strings = {"aaa","bb","c"};
System.out.println(Arrays.toString(strings));
Arrays.sort(strings,getComparator());//(o1, o2) -> o2.length()-o1.length()
System.out.println(Arrays.toString(strings));
}
}
三、常用函数式接口
3.1Supplier接口
java.util.function.Supplier<T>接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。
Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据.
public class DomeSupplier{
public static String getString(Supplier<String> stringSupplier){
return stringSupplier.get();
}
public static void main(String[] args) {
String str1="Hello";
String str2="world";
System.out.println(getString(()->str1+str2));
}
}
求数组元素最大数
/*
练习:求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。
提示:接口的泛型请使用java.lang.Integer类。
*/
public class DomeSupplier02 {
public static int getInteger(Supplier<Integer> supplier){
return supplier.get();
}
public static void main(String[] args) {
int[] arr={12,35,-46,1,22,5};
System.out.println("数组中最大值:"+getInteger(()->{
int max=arr[0];
/*for (int i=1;i<arr.length;i++){
if (arr[i]>max){
max=arr[i];
}
}*/
for (int i:arr) if (i>max) max=i;
return max;
}));
}
}
3.2Consumer接口
抽象方法accept
/*
java.util.function.Consumer<T>接口则正好与Supplier接口相反,
它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。
Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
至于具体怎么消费(使用),需要自定义(输出,计算....)
*/
public class DomeConsumer {
public static void method(String str,Consumer<String> con){
con.accept(str);
}
public static void main(String[] args) {
method("小明",(String name)-> {
System.out.println(name);
System.out.println(new StringBuffer(name).reverse().toString());
});
}
}
默认方法andThen
/*
Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
例如:
Consumer<String> con1
Consumer<String> con2
String s = "hello";
con1.accept(s);
con2.accept(s);
连接两个Consumer接口 再进行消费
con1.andThen(con2).accept(s); 谁写前边谁先消费
*/
public class DomeConsumerandThen {
public static void method(String str, Consumer<String> con1,Consumer<String> con2){
con1.andThen(con2).accept(str);
}
public static void main(String[] args) {
method("Hello",
(str)-> System.out.println(str.toUpperCase())
,
(str)-> System.out.println(str.toLowerCase())
);
}
}
练习:格式信息打印
/*
练习:
字符串数组当中存有多条信息,请按照格式“姓名:XX。性别:XX。”的格式将信息打印出来。
要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,
将打印性别的动作作为第二个Consumer接口的Lambda实例,
将两个Consumer接口按照顺序“拼接”到一起。
*/
public class DomeConsumer02 {
public static void method(String[] arr, Consumer<String> con1,Consumer<String> con2){
for (String s : arr) con1.andThen(con2).accept(s);
}
public static void main(String[] args) {
String[] arr = {"小红,女","小明,男","小王,男"};
/*method(arr,
(str)-> {
String name = str.split(",")[0];
System.out.print("姓名:"+name+" ");
}
,
(str)->{
String age = str.split(",")[1];
System.out.println("性别:"+age);
}
);*/
method(arr,
(str)-> System.out.print("姓名:"+str.split(",")[0]+" ")
,
(str)-> System.out.println("性别:"+str.split(",")[1])
);
}
}
3.3Predicate接口
抽象方法test
/*
java.util.function.Predicate<T>接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean值
Predicate接口中包含一个抽象方法:
boolean test(T t):用来对指定数据类型数据进行判断的方法
结果:
符合条件,返回true
不符合条件,返回false
*/
public class DomePredicate {
public static boolean isString(String s, Predicate<String> pre){
return pre.test(s);
}
public static void main(String[] args) {
// System.out.println(isString("abcde",(s)-> s.length()>5));
System.out.println(isString("abcde", s -> s.length()>5));
}
}
默认方法and
/*
逻辑表达式:可以连接多个判断的条件
&&:与运算符,有false则false
||:或运算符,有true则true
!:非(取反)运算符,非真则假,非假则真
需求:判断一个字符串,有两个判断的条件
1.判断字符串的长度是否大于5
2.判断字符串中是否包含a
两个条件必须同时满足,我们就可以使用&&运算符连接两个条件
Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> this.test(t) && other.test(t);
}
方法内部的两个判断条件,也是使用&&运算符连接起来的
*/
public class Predicate_and {
public static boolean isString(String s, Predicate<String> pre1,Predicate<String> pre2){
return pre1.and(pre2).test(s);
}
public static void main(String[] args) {
System.out.println(isString("abcde",
s-> s.length()>4
,
s-> s.contains("a")
));
}
}
默认方法or
/*
需求:判断一个字符串,有两个判断的条件
1.判断字符串的长度是否大于5
2.判断字符串中是否包含a
满足一个条件即可,我们就可以使用||运算符连接两个条件
Predicate接口中有一个方法or,表示或者关系,也可以用于连接两个判断条件
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
方法内部的两个判断条件,也是使用||运算符连接起来的
*/
public class Predicate_or {
public static boolean isString(String s, Predicate<String> pre1,Predicate<String> pre2){
return pre1.or(pre2).test(s);
}
public static void main(String[] args) {
System.out.println(isString("abcde",
s-> s.length()>5
,
s-> s.contains("a")
));
}
}
默认方法negate
/*
需求:判断一个字符串长度是否大于5
如果字符串的长度大于5,那返回false
如果字符串的长度不大于5,那么返回true
所以我们可以使用取反符号!对判断的结果进行取反
Predicate接口中有一个方法negate,也表示取反的意思
default Predicate<T> negate() {
return (t) -> !test(t);
}
*/
public class Predicate_negate {
public static boolean isString(String s, Predicate<String> pre){
// return !pre.test(s);
return pre.negate().test(s);
}
public static void main(String[] args) {
System.out.println(isString("abcde",s-> s.length()>5));
}
}
练习:结合信息筛选
/*
练习:集合信息筛选
数组当中有多条“姓名+性别”的信息如下,
String[] array = {"小明,男","小红,女","古力娜扎,女","马儿扎哈,男"};
请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,
需要同时满足两个条件:
1. 必须为女生;
2. 姓名为4个字。
分析:
1.有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
2.必须同时满足两个条件,所以可以使用and方法连接两个判断条件
*/
public class Predicate_DomeText {
public static ArrayList<String> Test(String[] arr, Predicate<String> pre1,Predicate<String> pre2){
ArrayList<String> arrayList = new ArrayList<>();
// for (String s : arr) {
// boolean test = pre1.and(pre2).test(s);
// if (test) arrayList.add(s);
// }
for (String s : arr) if (pre1.and(pre2).test(s)) arrayList.add(s);
return arrayList;
}
public static void main(String[] args) {
String[] arr ={"小明,男","小红,女","古力娜扎,女","马儿扎哈,男"};
// for (String s : Test(arr,
// s -> s.split(",")[1].equals("女")
// ,
// s -> s.split(",")[0].length() == 4
// )) {
// System.out.println(s);
// }
ArrayList<String> arrayList = Test(arr,
s -> s.split(",")[1].equals("女")
,
s -> s.split(",")[0].length() == 4
);
for (String s : arrayList) {
System.out.println(s);
}
}
}
3.4Function接口
抽象方法apply
/*
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
前者称为前置条件,后者称为后置条件。
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用的场景例如:将String类型转换为Integer类型。
*/
public class Function_Dome {
public static void doFunction(String s, Function<String,Integer> function){
System.out.println(function.apply(s));
}
public static void main(String[] args) {
doFunction("18300", s-> Integer.parseInt(s));
}
}
默认方法andThen
/*
Function接口中的默认方法andThen:用来进行组合操作
需求:
把String类型的"123",转换为Inteter类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
分析:
转换了两次
第一次是把String类型转换为了Integer类型
所以我们可以使用Function<String,Integer> fun1
Integer i = fun1.apply("123")+10;
第二次是把Integer类型转换为String类型
所以我们可以使用Function<Integer,String> fun2
String s = fun2.apply(i);
我们可以使用andThen方法,把两次转换组合在一起使用
String s = fun1.andThen(fun2).apply("123");
fun1先调用apply方法,把字符串转换为Integer
fun2再调用apply方法,把Integer转换为字符串
*/
public class Function_andThen {
public static void StringToInteger(String s, Function<String,Integer> function1,Function<Integer,String> function2){
System.out.println(function1.andThen(function2).apply(s));
}
public static void main(String[] args) {
StringToInteger("1200",
s -> Integer.parseInt(s)+10
,
s -> s+""
);
}
}
练习:自定义函数模型拼接
/*
练习:自定义函数模型拼接
题目
请使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
String str = "赵丽颖,20";
分析:
1. 将字符串截取数字年龄部分,得到字符串;
Function<String,String> "赵丽颖,20"->"20"
2. 将上一步的字符串转换成为int类型的数字;
Function<String,Integer> "20"->20
3. 将上一步的int数字累加100,得到结果int数字。
Function<Integer,Integer> 20->120
*/
public class Function_Test {
public static int Test(String s,
Function<String,String> function1,
Function<String,Integer> function2,
Function<Integer,Integer> function3){
return function1.andThen(function2).andThen(function3).apply(s);
}
public static void main(String[] args) {
System.out.println(Test("小红,20",
s-> s.split(",")[1],
si->Integer.parseInt(si),
i-> {
for (int j = 0; j < 100; j++) {
i+=j;
}
return i;
}
));
}
}