- 常用函数式接口
有且只有一个抽象方法的接口
可以用@FunctionInterface来检测一下这个接口是不是函数式接口
使用:一般作为方法参数和返回值类型
Lambda表达式,来作为函数式接口参数的调用就非常简洁
在这里,我们用lambda表达式来替代一个匿名内部类,这样文件在编译的时候,就不会多生成一个不必要的类,减少内存开销
这里,我始终是认为,匿名内部类有点太麻烦,写法太繁琐
- 函数式编程
日志:快速定位问题,对项目进行监控与优化
Lambda特点:延迟加载
Lambda使用前提:必须存在函数式接口
首先来看一个性能浪费的例子:
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
上面就比如我们有三段log消息要进行拼接传给msg参数,那么这种情况就是,不管怎么样,字符串消息都会先执行拼接,然后取判断相应的等级是不是1,在去决定把它打印出来
如果用lambda就可以很好的解决这种问题,我们让一个函数式接口做参数
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
上面就会在level == 1的时候,然后调用我们采用lambda实现的方法
我们不采用lambda表达式的时候,我们采用匿名内部类的一个处理方法:
/**
* 对于日志字符串拼接性能浪费的分析与改进
*/
public class Demo11 {
public static void main(String[] args) {
String msg1 = "信息1";
String msg2 = "信息2";
//采用匿名内部类处理,然后函数内部去调用一个这样的接口方法
log(1,new BuildInfo(){
@Override
public String ShowInfo() {
return msg1 + msg2;
}
});
}
public static void log(int level,BuildInfo build) {
if(level == 1) {
System.out.println("日志信息:" + build.ShowInfo());
}
}
}
@FunctionalInterface
interface BuildInfo {
String ShowInfo();
}
上面的操作,就是感觉非常繁琐
//lambda:一个非常核心的东西:1.函数式接口一个存在
// 2.我们要要接口方法主体->接口中返回什么内容你就必须给我做出来
log(1, () -> msg1 + msg2);
上面就是lambda一看就是很简单的操作
- 函数式接口作为方法的返回值
public class MyComparator {
//这里就是说返回一个接口的匿名内部类
private static Comparator<String> newComparator() {
return (a, b) ‐> b.length() ‐ a.length();
}
public static void main(String[] args) {
String[] array = { "abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
- 下面说一下常用的函数式接口:
主要是在java.util.function中被提供的函数式接口。
下面说一个Supplier接口
接口指定一个泛型,接口泛型是在我们实现这个接口的时候指定的。
当我们没有在实现接口指定,就在实例化对象的时候指定泛型
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));
}
下面问题:
使用Supplier接口作为方法的参数类型,通过lambda表达式来求出int数组中的最大值
这里还是来说一个问题,看看下面的代码,为什么num11会给我们报错说这个变量已经存在
因为num11在用表达式的时候,又给我们重新定义了一遍。
下面看看具体代码:
public class Demo12 {
public static void main(String[] args) {
Test2 t2 = getTest2();
int res = t2.add(1,2);
System.out.println(res);
}
//函数式接口来做函数参数
public static void getTest1(Test1 test1) {
test1.say();
}
//函数式接口作为返回值
public static Test2 getTest2() {
return ((num1, num2) ->{ return num1 + num2;});
}
}
//函数式接口1
@FunctionalInterface
interface Test1 {
void say();
}
//函数式接口2
@FunctionalInterface
interface Test2 {
int add(int num1,int num2);
}
/**
* 利用supplier函数式接口,求数组中元素最大值
*/
public class Test18 {
public static void main(String[] args) {
int[] arr = {3, 6, 5, 9, -1, 4};
int res = getMax(() -> {
//设置数组第一个元素为最大值
int maxValue = arr[0];
for(int data : arr) {
if(data > maxValue) {
maxValue = data;
}
}
return maxValue;
});
System.out.println("最大值是:" + res);
}
public static int getMax(Supplier<Integer> supplier) {
return supplier.get();
}
}
- Consumer
这个接口就是说:也只有一个函数式的接口
我们上面就可以很明显的看到,有一个接收泛型数据类型的方法,无返回值。
还有一个andThe的一个默认方法,返回的是一个带泛型的Consumer接口
下面就是一个简单的运行结果:
public static void main(String[] args) {
//调用这个方法
method(25,(Integer age) -> System.out.println("我的年龄:" + age));
}
//定义接收consumer接口的一个方法
public static void method(int age, Consumer<Integer> consumer) {
consumer.accept(age);
}
注意上面就是说用到了一个泛型的数据类型,传入数据类型的时候注意传入的是一个泛型数据
正是因为这个函数式接口给了我们一个泛型数据类型的参数,我们就可以以各种形式来进行这个数据的消费输出。
比如我们传入一个字符串,可以把字符串进行反转之后输出
String name = new StringBuffer(name).reverse().toString();
然后直接对其输出
下面在说一下它的默认方法;andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,然后对数据进行消费,接收Consumer接口,然后返回Consumer接口
Consumer<String> con1
Consumer<String> con2
String s= “hello”
con1.accept(s);
Con2.accept(s);
下面用andthen连接两个接口,在进行消费
con1.andThen(con2).accept(s);//本身就返回一个Consumer接口,所以调用accept(s)方法
- 下面说一下andThen方法原理:
下面看JDK源代码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
return (T t) ‐> { accept(t); after.accept(t); }; 这句话给剖析一下:
自己返回一个lambda表达式,其实也就是实现了内部的一个accept(T t)方法,然后又去调用accept(T t)就可以传入一个值进来
/**
* 练习格式化打印信息
* String[] array = { "张三,女", "王五,男", "李六,不男不女" };
* 以姓名为第一接口
* 以性别为第二接口
*/
- 下面我们看一下Predicate函数式接口
里面有个and方法,JDK源代码如下:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
//检查字符串是否包含两个指定字符串序列
public class Test21 {
public static void main(String[] args) {
String str = "Most of the rain in Spain falls on the plains";
boolean flag = judge(s -> s.contains("ain"),
s -> s.contains("te"), str);
System.out.println(flag);
}
public static boolean judge(Predicate<String> p1, Predicate<String> p2, String str) {
return p1.and(p2).test(str);
}
- return p1.and(p2).test(str);
上面这段话来给剖析一下,这里返回的肯定是一个boolean类型的值。
首先p1.and(p2)->会返回一个Predicate接口对象,而这个对象内部是用lambda表达式来实现的。就是如下:
return (t) ‐> test(t) && other.test(t);
加粗的部分其实就是实现了内部的函数式接口,但是整体是返回一个对象。
- 下面说一下:or方法,既然有and(&&),就会有or
下面看一下JDK源代码:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}
就是有一个条件满足就成立,与and用法一致
- 下面看一下negate,这个就是逻辑非(!)
还是去看一下JDK源代码:
default Predicate<T> negate() {
return (t) ‐> !test(t);
}
其实非常好理解,test(t)函数给我们测出来的结果如果为真,就把它变成假,如果为假,就把它变成真。
最后看一道实测题:
/**
* 通过Predicate接口中and方法,进行对数据的判断,然后放到集合中
* 集合信息的筛选,放到集合中的信息要求:
* 1.必须男生
* 2.姓名三个字
*/
public class Demo16 {
public static void main(String[] args) {
String[] arr = {"周杰伦, 男", "李晟,男", "赵丽颖,女", "刘子成,男"};
//调用judge,过滤在这过滤
List<String> list = judge(s -> s.split(",")[0].length() == 3,
s -> s.contains("男"),
arr);
//把集合中的数据给打印出来
System.out.println(list);
}
//两个条件进行判断,两个接口
public static List<String> judge(Predicate<String> p1, Predicate<String> p2, String[] targetArr) {
//字符串进来之后,我们要分割进行字符串的处理
List<String> list = new ArrayList<String>();
//进来之后,做一个循环
for(String res : targetArr) {
//每次会从数组中拿出一个字符串
//进行两个test拼接
if(p1.and(p2).test(res)) {
//符合条件,数据添加
list.add(res);
}
}
//做完返回
return list;
}
}
- 这里说说,function包中的Function接口
Interface Function<T,R>
根据一个数据类型来得到另外一个数据类型
R apply(T t)
这个函数式接口就是比如通过t,我们来得到r
前者T,就是一个前置条件,后者r就是我们想要的结果
比如我们把int类型转换成String类型
- 下面看里面一个andThen连接的方法
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
- 下面实现对一个字符串实现多个函数的操作
String str = “周杰伦,35”;
1. 将字符串截取数字年龄部分,得到字符串;
2. 将上一步的字符串转换成为int类型的数字;
3. 将上一步的int数字累加10,并将结果变为String类型。
核心思想就是:每一步的操作结果都给下一步,作为一个前置条件
public class Demo01Function {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*String str = "周杰伦,35";
String res = formatMethod1(str);
System.out.println("结果是:" + res);*/
String str = "周杰伦,35";
String res = formatMethod1_1(str,(str1) -> {
String[] arr = str1.split(",");
return Integer.parseInt(arr[1]);
},(num) -> {
return Integer.toString(num + 10);
});
System.out.println(res);
/*String str = "123";
String res = formatMethod2(str, (str1) -> Integer.parseInt(str1),
(num) -> Integer.toString(num + 10));
System.out.println("结果:" + res);*/
}
//第一种方法
public static String formatMethod1(String str) {
String[] arr = str.split(",");
int age = Integer.parseInt(arr[1]);
age = age + 100;
String res = Integer.toString(age);
return res;
}
//第二种方法
public static String formatMethod1_1(String str,Function<String,Integer> fun1,Function<Integer,String> fun2) {
return fun1.andThen(fun2).apply(str);
}
/*
* 需求:
把String类型的"123",转换为Inteter类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
* */
public static String formatMethod2(String str,Function<String,Integer> fun1,Function<Integer,String> fun2) {
return fun1.andThen(fun2).apply(str);
}
}