目录
一、函数式接口的概念与定义
1.1函数式接口
有且仅有一个抽象方法的接口
适用于函数编程场景的接口。
1.2函数式编程
是指用lambda或者方法引用进行编程
1.3 定义一个函数式接口
/*
* 有且仅有一个抽象方法的接口称作为函数式接口
* 当然接口中可以有其他方法(默认,静态,私有)
*/
public interface MyFunctionInter {
public abstract void method();}
1.4 @FunctionalInterface注解
检测接口是否是一个函数式接口,是,编译成功,否,编译失败(接口中没有抽象方法,抽象方法的个数大于一个)
@FunctionalInterface
public interface MyFunctionInter {
public abstract void method();
// void method1();}
1.5 Lambda的表达式例子
- 空括号(),没有参数,返回值为void
Runnable noArguments = () -> System.out.println("hello");
- 包含一个参数,可省略参数的括号和参数类型(因为有类型推断,根据上下文自动推断出类型),返回值为void
ActionListener oneArguments = event -> System.out.println("button");
- Lambda表达式主体不仅可以是一个表达式,也可以是一段代码块,使用大括号将代码括起来,跟普通方法遵循的规则一致,可以返回或者抛出异常来推出
Runnable multiStatement = () -> {
System.out.println("hello");
System.out.println("world");
};
- 可以有多个参数
BinaryOperator< Long> add = (x,y) -> x + y;
- 可以和显示声明类型
BinaryOperator< Long> add = (Long x,Long y) -> x + y;
1.6 Lambda引用值,而不是变量
1.6.1 匿名内部类
没有名字的内部类,因为没有名字,所以匿名内部类只能使用一次。
前提条件:必须继承父类或实现一个接口
abstract class Person{
public abstract void eat();
}
public class Demo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Person person = new Person() {
public void eat(){
System.out.println("eat haha");
}
};
person.eat();}
}
1.6.2 lambda表达式引用的是值而不是变量
匿名内部类,需要引用它所在方法里的变量时,需要将变量声明为 final
Lambda表达式不要求必须是final 变量 但是,该变量在既成事实上必须是final
事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值,而不是变量 跟匿名内部类类似
String hello = "HelloWord";
Stream.of(1,2,3).forEach(s -> System.out.println(hello + s));
给hello重新赋值
运行结果:
二、函数式接口的使用
函数式接口一般作为方法的参数和返回值类型使用
1.1作为方法的参数使用
首先要创建一个MyFunctionInter接口的实现类MyFunctionInterImpl
public class Demo {
// 定义一个方法,参数使用函数式接口MyFunctionInter
public static void show(MyFunctionInter myInter) {
// 调用MyFunctionInter中的method方法
myInter.method();
}
public static void main(String[] args) {
// 调用show()方法,方法的参数是一个接口,所以可以传入接口的实现类对象
show(new MyFunctionInterImpl());
// 调用show()方法,方法的参数是一个接口,所以可以传入接口的匿名内部类
show(new MyFunctionInterImpl() {
@Override
public void method() {
System.out.println("使用匿名内部类,重写接口中的抽象方法");
}
// 调用show方法,方法的参数是一个函数式接口,所以可以传入一个lambda表达式
});
show(() -> {
System.out.println("使用lambda表达式重写接口中的抽象方法");
});
}}
运行输出结果
1.1.1 简化lambda表达式
如果只有一条语句,可以省略“{}”和“;”
// 简化lambda表达式
show(() -> System.out.println("使用lambda表达式重写接口中的抽象方法"));
三、函数式编程
3.1lambda的延迟执行
有些场景的代码执行后,结果不一定被使用,而lambda表达式是延迟执行的,可以提升性能
3.1.1性能浪费的日志案例
public class DemoLogger {
// 定义一个根据日志的级别显示日志信息的方法
public static void showLog(int level,String message) {
// 根据日志的等级进行判断,如果级别是一,则显示日志信息
if(level == 1) {
System.out.println(message);
}
}
public static void main(String[] args) {
// 定义三个日志信息
String msg1 = "hello";
String msg2 = "world";
String msg3 = "java";
//调用showlog的方法
showLog(2, msg1+msg2+msg3);
}}
当传入level为2时,就不会显示信息
发现以上代码存在性能浪费问题
调用showLog方法,传入的第二个参数是拼接好的字符串
先把字符串拼接好,然后调用showLog方法
showLog方法如果传递的参数等级不是1,
那么就不会使用拼接后的字符串,导致性能浪费
3.1.2使用lambda进行日志优化
lambda特点;延迟加载,
lambda使用前提:必须存在函数式接口(LogMessage)
(1)创建一个函数式接口
@FunctionalInterface
public interface LogMessage {
// 定义一个拼接消息的抽象方法,返回拼接的消息
public abstract String builderMessage();
}
(2)使用函数式接口传递lambda参数,输出日志信息
public class LambdaLog {
// 定义一个显示日志的方法,方法的参数传递日志的等级和LogMessage接口
public static void show(int level,LogMessage logMessage){
// 对日志等级进行判断,如果是1级调用LogMessage中的builderMessage方法
if(level == 1) {
System.out.println(logMessage.builderMessage());;
}
}
public static void main(String[] args) {
// 定义三个日志信息
String msg1 = "hello";
String msg2 = "world";
String msg3 = "java";
// 调用show方法,LogMessage是个函数式接口,所以,可以传lambda表达式
show(2, () ->{
return msg1+msg2+msg3;
});
}}
发现运行结果是一样的,那么函数式接口有什么好处呢
使用lambda表达式作为参数传递,仅仅是把参数传递给show方法中,只有曼珠条件(level==1 ),才会调用LogMessage接口中的builderMessage,只有调用方法后,才会进行字符串拼接,如果条件不满足(level !==1),builderMessage方法不会执行,所以就不会执行拼接字符串代码,就不会存在性能浪费
测试:
在使用show()中加入一条输出语句
show(2, () ->{
System.out.println("等级为1执行");
return msg1+msg2+msg3;
});
等级为2,查看结果:没有任何输出,即没有执行builderMessage方法,就是没有拼接字符串
等级为1,查看结果:,执行builderMessage方法,有拼接字符串
3.2 使用lambda作为参数和返回值案例
3.2.1 函数式接口作为参数
* java.lang.Runnable接口是一个函数式接口
* 假设有startThread方法使用该接口作为参数,那么就可以使用lambda进行传参
* 这种情况和Thread类的构造方法参数为Runnable没有本质别
public class DemoRunable {
// 定义一个方法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() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName() + "线程启动了");
}
});// 调用startThread方法,方法的参数是一个函数式接口,所以可以传递lambda表达式
// run方法没有参数
startThread(() -> {
System.out.println(Thread.currentThread().getName() + "线程启动了");
});// 优化lambda表达式
startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));
}}
运行结果:
3.2.2 函数式接口作为返回值类型
* 如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个表达式
* 当需要通过一个方法获取一个java.util.Comparator接口类型的对象作为排序器,就可以调该方法获取
public class Democompare {
// 定义一个方法,方法的返回值类型使用函数式接口Comparator
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();
// };
// 优化
return (String o1,String o2) -> o2.length() - o1.length();
}
public static void main(String[] args) {
// 创建一个字符串数组
String[] arr = {"bbb","c","aaaaaaa","dddd"};
// 输出排序前的数组
System.out.println(Arrays.toString(arr));
// 调用Arrays中的sort方法,对字符串数组进行排序
// 第一个参数为数组,第二各参数为Comparator接口的实现类,回了一个lambda
Arrays.sort(arr,getComparator());
// 输出排序后的结果
System.out.println(Arrays.toString(arr));}
}
运行结果:
四、常用函数式接口
java.docs
https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
4.1 Supplier接口
java.util.function.Supplier<T>接口包含一个无参的方法:T.get().用来获取泛型参数指定类型的对象数据。
Supplier<T>接口称之为生产型接口,指定接口的泛型<T>是什么类型,那么接口中的get方法就会产生什么类型的数据。
public class DemoSupplier {
// 定义一个方法,方法参数传递Supplier接口,泛型执行String,get方法就返回一个string
public static String getString(Supplier<String> supplier) {
return supplier.get();
}
public static void main(String[] args) {
// 调用getString方法,方法的参数是supplier接口,锁一个以传递lambda表达式
String s = getString(() -> {
return "老郭";
});
System.out.println(s);
// 优化
String s1 = getString(() -> "小郭");
System.out.println(s1);
}
}
当只有一个return 那么做优化的时候return {} ;都可以忽略掉
4.1.2 练习题:求数组元素最大值
注意:接口的泛型使用java.lang.Integer
public class SupplierTest {
// 定义一个方法,获取int型数组中元素最大值,泛型使用Integer,方法传递参数是supplier接口
public static int getMax(Supplier<Integer> sup) {
return sup.get();
}public static void main(String[] args) {
int[] arr = { 100, 40, 0, -50, 99 };
int maxValue = getMax(() -> {
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;}
}
return max;
});
System.out.println(maxValue);
}}
4.2 Consumer接口
java.util.function.Consumer<T>接口则正好与Supplier接口相反
他不是产生一个数据,而是消费一个数据,其数据类型由泛型决定
Consumer接口包含抽象方法void accept(T t),意为消费一个指定泛型的数据
4.2.1 accept方法
Consumer接口是一个消费型接口,泛型指定什么类型,就可以使用accept方法消费什么类型的数据,具体怎么消费使用,需要自定义
/*
* 定义一个方法,方法参数传递一个字符串的姓名和Consumer接口,泛型使用string
*/public class DemoConsumer {
public static void method(String name,Consumer<String> con) {
con.accept(name);
}
public static void main(String[] args) {
method("老郭",(String name) ->{
System.out.println(name);
// 字符串反转 链式调用
String reName = new StringBuffer(name).reverse().toString();
System.out.println(reName);
});
}}
4.2.2 andThen 默认方法
需要两个Cnsumer接口,把两个Consumer接口组合到一起,在对数据进行消费
例如:
Consumer<String> con1
Consumer<String> con2
String s = "hello"
con1.accept(s)
con2.accept(s)
连接两个Consumer接口,在进行消费,谁写前面,谁先消费
con1.addThen(con2).accept(s)
public class DemoAndThen {
public static void method(String s,Consumer<String> con1,Consumer<String> con2) {
con1.andThen(con2).accept(s);
}
public static void mian(String[] args) {
method("hello", (t) ->{
System.out.println(t.toUpperCase());
}, (t) -> {
System.out.println(t.toLowerCase());
});
}}
4.2.3练习:格式化打印信息
public class ConsumerTest {
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 = {"Lily,18","Bom,20","linda,12"};
printInfo(arr, (message) -> {
String name = message.split(",")[0];
System.out.print("姓名" + name);
}, (message) -> {
String age = message.split(",")[1];
System.out.println(".年龄" + age);
});
}}
运行结果如下
4.3 Predicate接口
4.3.1 boolean test(T t)方法
java.util.function.Predicate<T>接口通过其boolean test(T t)对指定数据类型数据进行判断,返回true或false
public class DemoPredicate {
// 定义一个方法,参数传递一个string字符串和Predicate接口,泛型使用String,使用test对字符串进行判断
public static Boolean method(String s,Predicate<String> pre) {
return pre.test(s);
}
public static void main(String[] args) {
// 定义一个字符串
String string = "asdfgh";
// 调用method方法,传递参数和lambda表达式
// Boolean res = method(string, (String str) -> {
// return str.length() > 5;
// });
// 优化
Boolean res = method(string, str -> str.length() > 5);
System.out.println(res);
}}
4.3.2 and默认方法
表示并且关系,也可以用于连接两个判断条件
方法内部的两个判断条件也是使用&&运算符连接起来的
//判断字符串长度大于5且包含a
public class AndTest {
public static Boolean method(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 = "asdfgh";
boolean bol = method(s, (String str) ->{
return str.length() > 5;
}, (String str) -> {
return str.contains("a");
});
System.out.println(bol);
}}
4.3.3or默认方法
表示或者关系,也可以用于连接两个判断条件
方法内部的两个判断条件也是使用||运算符连接起来的
用法同and()
4.3.4 negate默认方法
表示取反,用“!”
return pre.negate(0.test(s)
4.3.5练习:集合筛选练习
/*
* 通过Predicate接口将符合要求的字符串你筛选到集合ArrayList中
*/
public class PredicateTest {
public static ArrayList<String> filter(String[] arr,Predicate<String> pre1,Predicate<String> pre2){
// 定义一个ArrayList集合,存储过滤后的信息
ArrayList<String> list = new ArrayList<String>();
for (String s : arr) {
// 使用predicate接口中的方法test对获取到的字符串进行判断
boolean b = pre1.and(pre2).test(s);
if(b) {
// 条件成立,把信息存储到ArrayList中
list.add(s);
}
}
return list;
}
public static void main(String[] args) {
String[] arr = {"Lily,12","Bom,20","linda,12"};
ArrayList<String> res = filter(arr, (String s) -> {
return s.split(",")[0].length() > 3;
}, (String s) ->{
return s.split(",")[1].equals("12");
});
System.out.println(res);
}
}
4.4 Function接口
java.util.function.Function<T R>接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
4.4.1 抽象方法Apply
R apply(T t).根据类型T的参数获取类型R的结果
//将String类型换为R类型
public class DemoApply {
public static void method(String s,Function<String, Integer> fun) {
Integer in = fun.apply(s);
System.out.println(in);
}
public static void mian(String[] args) {
// 定义一个字符串类型的整数
String s = "123";
// 调用method方法,传递字符串类型的整数,和lambda表达式
method(s,str -> Integer.parseInt(str));
}
}
4.4.2 默认的方法:andThen
/*
*把String类型转换为Integer类型,转换后加10
*增加后的Integer类型数据转换为String类型
*转换了两次
*第一次;
*String类型转换为Integer
*可以使用Function<String,Integer> fun1
*第二次转换Integer转换为String
*Function<Integer,String> fun2
*可以使用andThen把两次转换组合到一起
*
*/public class DemoTest {
public static void method(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";
method(s, str -> Integer.parseInt(s) + 10,
i -> i + "");
}
}
4.4.3练习;函数模型拼接
/*
* 获取字符串数字年龄部分
* Function<String,String> fun1
* 将得到的字符串转换成int类型
* Function<String,Integer> fun2
* 将int类型加100
* Function<Integer,Integer> fun3
*/public class DemoTest {
public static int method(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 = "gsy,18";
int num = method(str, s -> s.split(",")[1],
s-> Integer.parseInt(s),
i ->i + 100);
System.out.println(num);
}}