文章目录
前言
本文介绍了函数式接口及lambda的使用,及四大函数式接口(消费型、供给型、函数型、断言型)的源码剖析。
一、函数式接口和Lambda的使用
想要了解lambda的使用,我们先来认识下什么是函数式接口。
1、函数式接口
⑴ 、定义
函数式接口:有且仅有一个抽象方法的接口。简单来说,就是该接口只提供了一个方法供实现类实现,其他的方法都是default或static修饰的方法。
@FunctionalInterface:编译期用来检验该接口是否是满足函数式接口条件,该注解仅仅是用来检查是否满足函数式接口的条件,可以不写,建议加上。
⑵ 、jdk中的实例
多线程封装线程任务的Rannable接口:
@FunctionalInterface
public interface Runnable {
//仅有一个抽象方法
public abstract void run();
}
比较器Comparator:
@FunctionalInterface
public interface Comparator<T> {
//供实现的抽象方法
int compare(T o1, T o2);
boolean equals(Object obj);
//已经实现了的默认方法
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
//.......省略其他的default方法
对于上面的例子,Comparator是函数式接口,我们知道函数式接口的定义是:一个函数式接口有且只有一个抽象方法。由于默认方法已经有了实现,所以它们不是抽象方法,但是对于equals方法呢?他并没有实现,这里是因为equals是Object类中的方法,所有类都默认继承至Object类,接口中该方法equals已经从Object类中获得了实现,所以因为重写了Object类中任意一个public方法的方法并不算接口中的抽象方法。
还有其他等等:java.util.concurrent.Callable,java.security.PrivilegedAction,java.io.FileFilter…
2、lambda表达式
⑴ 、定义
Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda使用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
lambada 表达式实质上是一个匿名方法,但该方法并非独立执行,而是用于实现由函数式接口定义的唯一抽象方法
⑵、演示
拿上面介绍的函数式接口Rannable来介绍,这里不用将Rannable当做线程任务,先仅仅将其当做一个普通的接口来看:
在以往,我们写代码的时候,假如需要创建Rannable接口对象,需要定义一个实现类,实现类重写抽象方法,简单点采用直接new匿名内部类如下:
@Test
public void test() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类方式");
}
};
r.run();
}
现在我们引入了lambda表达式,Rannable是函数式接口,仅有一个实现方法run,下面写法我们可以理解成一个匿名方法:(行参)-> {方法体},接口中仅有一个需要实现的方法,不需要指定。
@Test
public void test() {
Runnable r = ()-> System.out.println("lambda表达式");
r.run();
}
lambda在写法上大体有如下几种类型:
//无形参,无返回值
Runnable a = ()-> System.out.println("111");
//2个形参数,返回值为int 方法体包含多行语句,需要使用{}
//抽象方法定义:int compare(T o1, T o2);
Comparator<String> b = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.length() - o2.length();
};
//1个形参,返回值为boolean 可以省略行参括号,一行代码可以省略大括号与return
//抽象方法定义:boolean test(T t);
Predicate<String> c = str -> str.length() > 0;
//方法体如果写{},需要显示写return
Predicate<String> d = str -> {return str.length() > 0;};
场景 | 举例 |
---|---|
返回boolean类型 | str -> str.isEmpty() 或 String::isEmpty |
创建对象 | ()-> new Demo() |
消费对象 | a -> { sout( a.getName() ) } |
计算 | a,b -> a+ b |
比较 | a,b -> a == b |
二、内置函数式接口
在jdk8之后,官方定义了一些常用的函数式接口,如果以后需要使用类似的接口,直接使用即可,不需要再单独定义,共分以下四大类。
- interface Consumer<T>: 消费型接口
void accept(T t);
- interface Supplier<T>:供给型接口
T get();
- interface Function<T, R> :函数型接口
R apply(T t);
- interface Predicate<T>:断言型接口
boolean test(T t);
1、消费型Consumer
接口期望执行带有副作用的操作(即它可能会改变输入参数的内部状态)
⑴ 、演示
包含一个抽象方法accept和一个default方法,
andThen方法返回一个组合过的consumer实例,它的accept方法是先调用this这个consumer的accept方法,然后调用after的accept方法(…之后执行)。
例:
@Test
public void consumerTest(){
Consumer<String> consumer = str-> System.out.println(str+"11");
consumer.accept("abc");
consumer.andThen(s -> System.out.println(s+="222"))
.accept("def");
}
输出:
abc11
def11
def222
⑵、java中的应用实例
ArrayList中的forEach方法:
ArrayList<String> al = new ArrayList<>();
al.add("11111");
al.add("22222");
al.add("33333");
al.forEach(s->{
String str = s.isEmpty() ? "" : s.substring(0,1);
System.out.println(str);
});
}
⑶、拓展
拓展接口 | 方法 |
---|---|
IntConsumer | void accept(int x) |
DoubleConsumer | void accept(double x) |
LongConsumer | void accept(long x) |
BiConsumer<T, U> | void accept(T t, U u) 接收2个泛型参数 |
ObjIntConsumer<T> | void accept(T t, int value) 无andThen方法 |
ObjDoubleConsumer<T> | void accept(T t, double value) 无andThen方法 |
ObjLongConsumer<T> | void accept(T t, long value) 无andThen方法 |
2、供给型Supplier
Supplier本质就是一个容器,可以用来存储数据(或者是产生数据的规则),然后可以供其他方法使用的这么一个接口
⑴ 、演示
它不包含任何静态或默认方法,只有一个抽象方法 T get()
例:
//如取随机数
@Test
public void supplierTest(){
//()->Math.random();
Supplier<Double> supplier = Math::random;
System.out.println(supplier.get());
}
⑵、java中的应用实例
延迟执行
java.util.logging.Logger日志类中:
先来看info输出日志两个重载方法:
/**
* 日志测试类
*/
public class LoggerTest {
private Logger logger = Logger.getLogger(this.getClass().getName());
private String info = "";
public String getInfo() {
return info += "info执行了";
}
@Override
public String toString() {
return "LoggerTest{" +
"info='" + info + '\'' +
'}';
}
@Test
public void testString() {
LoggerTest loggerTest = new LoggerTest();
logger.config(loggerTest.getInfo());
System.out.println(loggerTest);//LoggerTest{info='info执行了'}
}
@Test
public void testSupplier() {
LoggerTest loggerTest = new LoggerTest();
logger.config(loggerTest::getInfo);
System.out.println(loggerTest); //LoggerTest{info=''}
}
}
测试说明:新建一个日志测试类,默认的Logger的level为800,可打印info信息,config打印方法的level为700,两个test方法中的日志将不会打印出来,该类有个成员变量info,当调用getInfo方法时,info对象会加上值,test方法中两个打印方式,第一个用的是
String为形参的config打印,第二个用的是Supplier为形参的config打印,通过输出我们可以发现:
- 无论是否显示消息,都会执行getInfo方法,构建info参数
- 仅当日志级别显示消息时,才会执行getInfo方法,构建参数
采用相同类型的 Supplier 替换参数的技术称为延迟执行(deferred execution),可以在任何对象创建成本较高的上下文中使用。
再来看标准库中的java.util.Optional中的orElseGet方法:
orElseGet相比于orELse来说,仅在Optional没值的时候,执行返回Supplier接口实现中的方法,返回值。
⑶、拓展接口
拓展接口 | 方法 |
---|---|
IntSupplier | int getAsInt() |
DoubleSupplier | double getAsDouble() |
…Long Boolean等 |
3、函数型Function
它可以将 T 类型的泛型输入参数转换为 R 类型的泛型输出值,T和R可以相同
⑴ 、演示
包含一个抽象方法apply,2个defalut方法及一个静态方法,apply不多说,待实现的抽象方法,入参T返回R。
addThen返回一个组合过的Function,先调用this的apply方法,将其返回值作为after的apply的入参,after可以理解为this之后执行。
compose与addThen相反,先调用before的apply,得到一个返回值,将这个值作为this的apply的入参,before可以理解为this之前执行。
例:
@Test
public void FunctionTest() {
//T为String R为int, 定义一个实现方法返回字符长度
Function<String, Integer> fun = String::length;
System.out.println(fun.apply("123")); //3
//andThen使用----
Function<Integer, Integer> fun1 = a -> a + 100;
//after.apply(apply(t)):先调用fun的apply获取长度为5,然后调用fun1的accply,+100
Integer apply = fun.andThen(fun1).apply("11111");
System.out.println(apply); //105
//compose使用----
Function<Integer, String> fun2 = a -> a + "";
//apply(before.apply(v)):先调用fun2的apply,输入1101获取字符类型“1101”,然后调用fun的apply,输出长度
System.out.println(fun.compose(fun2).apply(1101)); //4
}
identity()方法:
返回一个输入和输出一样的表达式,t -> t
/**
* 如下转换成map集合,key为String字符,value为字符长度
*/
@Test
public void FunctionTest() {
Stream<String> stream = Stream.of("11", "222", "3", "444");
//toMap方法形参为两个Function接口,其中key实现方法t->t即入参t,返回也是t;value的实现方法取字符长度
//可以写成Collectors.toMap(Function.identity(), String::length)
Map<String, Integer> map = stream.collect(Collectors.toMap(t->t, String::length));
System.out.println(map); //{11=2, 222=3, 3=1, 444=3}
}
上面的t->t
可以写成Function.identity()
⑵、java中的应用实例
java.util.stream.Stream流中的map方法如下:
/**
* 字符串映射到他们的字符长度
*/
@Test
public void FunctionTest() {
List<String> list = Arrays.asList("aa", "bbbbb", "c", "dd");
//list.stream().map(str->str.length()).collect(Collectors.toList());
List<Integer> collect = list.stream().map(String::length).collect(Collectors.toList());
System.out.println(collect); //[2, 5, 1, 2]
}
⑶、拓展接口
拓展接口 | 方法 |
---|---|
IntFunction<R> | R apply(int value) |
… Double,Long等 | |
ToIntFunction<T> | int applyAsInt(T value) |
… Double,Long等 | |
DoubleToIntFunction | int applyAsInt(double value) |
…Double,Long等 | |
BiFunction<T, U, R> | R apply(T t, U u) |
4、 断言型Predicate
主要用于筛选数据,常用在:给定一个包含若干项的流,Stream 接口的 filter 方法传入 Predicate 并返回一个新的流,它仅包含满足给定谓词的项。
⑴ 、演示
抽象方法test:入参为泛型T,返回值为boolean,主要用来判断参数是否符合某种规则;
and方法:入参为Predicate对象,返回一个组合过的Predicate对象,同时满足this和other的test,相当于逻辑与&&,有短路特性。
or方法:用法同and,相当于逻辑或 || ,存在短路特性。
negate方法:用法同and,相当于逻辑非 !。
isEquers方法:功能类似于equals,不同的是,该方法先判断入参对象是否为空,为空返回实现方法为this == null即(obj->obj==null)的断言对象,否则返回实现方法为object -> targetRef.equals(object)的Predicate对象。
例:
@Test
public void predicateTest(){
Predicate<String> predicate = str -> str.startsWith("a");
System.out.println(predicate.test("aaa")); // true
System.out.println(predicate.test("baa")); // false
}
结合stream流,综合使用上面的Predicate中的方法:
Stream的filter过滤方法接受一个断言型参数,根据Predicate实现对象的test方法的boolean类型的返回值来判断元素是否符合条件Stream<T> filter(Predicate<? super T> predicate)
public class PredicateTest {
public static List<String> MICRO_SERVICE = Arrays.asList("nacos","authority","gateway","ribbon",
"feign","hystrix","e-commerce","gate");
/**
* boolean test(T t)
* test方法 主要判断参数是否符合规则
*/
@Test
public void testTest(){
//定义规则为字符长度大于5
MICRO_SERVICE.stream()
.filter(str->str.length() > 5)
.forEach(str -> System.out.print(str + ", "));
//输出:authority, gateway, ribbon, hystrix, e-commerce,
}
/**
* Predicate<T> and(Predicate<? super T> other)
* and方法 等同于逻辑&&,存在短路特性
*/
@Test
public void andTest(){
Predicate<String> p1 = str -> str.length() > 5;
Predicate<String> p2 = str -> str.startsWith("g");
MICRO_SERVICE.stream()
.filter(p1.and(p2))
.forEach(str -> System.out.print(str + ", "));
//输出:gateway,
}
/**
* Predicate<T> or(Predicate<? super T> other)
* or方法 等同于逻辑 || ,存在短路特性,多个条件满足一个即可
*/
@Test
public void orTest(){
Predicate<String> p1 = str -> str.length() > 5;
Predicate<String> p2 = str -> str.startsWith("g");
MICRO_SERVICE.stream()
.filter(p1.or(p2))
.forEach(str -> System.out.print(str + ", "));
//输出:authority, gateway, ribbon, hystrix, e-commerce, gate,
}
/**
* Predicate<T> negate()
* 等同于逻辑 !
*/
@Test
public void negateTest(){
Predicate<String> p2 = str -> str.startsWith("g");
MICRO_SERVICE.stream()
.filter(p2.negate())
.forEach(str -> System.out.print(str + ", "));
//输出:nacos, authority, ribbon, feign, hystrix, e-commerce,
}
/**
* Predicate<T> isEqual(Object targetRef)
* 类似于equals,区别在于:先判断对象是否为null ,不为null再使用equals进行比较
*/
@Test
public void isEqualsTest(){
String string = "gate";
// String string = null;
Predicate<String> p = str -> Predicate.isEqual(string).test(str);
MICRO_SERVICE.stream()
.filter(p)
.forEach(str -> System.out.print(str + ", "));
//输出:gate,
}
}
⑵、java中的应用实例
如上述的Stream中的filter方法,Optional的filter,Collection的removeIf,Stream的allMatch等等
Optional中的filter:
⑶、拓展接口
拓展接口 | 方法 |
---|---|
IntPredicate | boolean test(int value) |
… Double,Long等 |
参考文章
https://blog.youkuaiyun.com/xushiyu1996818/article/details/92787371
https://geek-docs.com/java/java-examples/java-consumer-interface.html