- 函数式接口定义
- 函数描述符
- 函数式接口、函数描述符和Lambda表达式关系
- Java8中新增的函数式接口
- Lambda表达式异常处理
一、函数式接口
1.1定义
函数式接口,是指仅仅包含一个抽象方法的接口
函数式接口中,唯一抽象方法的定义:
1、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
2、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。如 equals、hashcode方法
1.2@FunctionalInterface
1、该注解只能标记在"有且仅有一个抽象方法"的接口上。
2、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。
加上该注解能够更好地让编译器进行检查。
如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
1.3例子
@FunctionalInterface
public interface Comparator<T> {
// 唯一的抽象方法
int compare(T o1, T o2);
//显式声明覆盖Object的equals()方法
boolean equals(Object obj);
//default方法
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
//static方法
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
}
二、函数描述符
2.1定义
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名,我们将这种抽象方法称之为函数描述符
2.2例子
() -> void 代表了参数列表为空,且无返回值的函数,而这正是Runnable接口所代表的
public void process(Runnable r) {
r.run();
}
Lambda表达式:
process(() -> System.out.println("此时,lambda表达式的函数签名就是Runnable接口的函数式接口"))
三、函数式接口、函数描述符和Lambda表达式关系
3.1概念
只有需要函数式接口的时候,才可以传递Lambda表达式!
Lambda表达式仅可用于上下文是函数式接口的情况!
Java语言设计者,规定了这种Lambda表达式的传递方式。
3.2例子
以下哪些是使用Lambda表达式的有效方式?
1) execexute(() -> {});
public void execute(Runnable r) {
r.run();
}
2) public Callable<String> fetch() {
return () -> "Tricky example";
}
3)Predicate<Apple> p = (Apple a) -> a.getWeight();
答案:1)和2)有效
1) Lambda() -> {} 具有签名 () -> void,这和Runnable中的抽象方法run的签名相匹配
2) fetch方法返回的类型是Callable<String> Callable<String>基本上就定义了一个方法:
签名是 () -> String, 其中T被String替代了
Lambda () -> "..." 的签名是 () -> String
3)Lambda表达式 (Apple a) -> a.getWeight() 的签名:(Apple) -> Integer
Predicate<Apple> 中签名为: (Apple) -> boolean
两者签名不一致
四、Java8中新增的函数式接口
已有的函数式接口:Comparable、Runnable、Callable
4.1 Predicate
java.util.fuction.Predicate<T> 定义了一个 test 的抽象方法,接收泛型对象,返回boolean类型
函数签名:(T)-> boolean
如果涉及使用关于T的boolean类型表打式时,就可以使用函数式接口Predicate<T>
/**
*这里我们来重新实现以下行为参数里面举例的用苹果的颜色来筛选苹果需求
*/
#函数式接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for ( T t : list ) {
if(predicate.test(t)) result.add(t);
}
return result;
}
public static void main(String[] args){
List<Apple> apples = Arrays.asList(new Apple("red",120),new Apple("green",150));
#使用Lambda表达式进行行为参数传递,传递的是具有函数符号(方法签名)的 (T) -> boolean
#的Predicate函数式接口
List<Apple> result = filter(apples,(a) -> "red".equals(a.getColor()));
}
4.2 Consumer
java.util.function.Consumer<T>定义了一个 accept 的抽象方法,它接受泛型T的对象,没有返回(void)
函数签名:(T)-> void
如果需要访问类型为T的对象,并对其进行某些操作,就可以使用Consumer
#函数式接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
public static <T> void consumerTest(List<T> list, Consumer<T> consumer) {
for (T t : list ) {
consumer.accept(t);
}
}
public static void main(String[] args){
consumerTest(new ArrayList<Apple>(),(a) -> System.out.println(a.getColor()));
}
4.3 Function
java.util.Function.Function<T,R> 定义了一个接收类型为T,返回类型为R的 apply抽象方法。
函数签名:(T) -> R
如果需要将输入对象的信息,映射到输出对象,就可以使用这个函数式接口Function<T,R>
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
public static <T, R> List<R> functionTest(List<T> list, Function<T, R> function) {
List<R> result = new ArrayList<>();
for (T t : list) {
R r = function.apply(t);
result.add(r);
}
return result;
}
#将Apple的颜色属性输出出来
public static void main(String[] args){
List<Apple> apples = Arrays.asList(new Apple("red",130),new Apple("green",150));
List<String> strings = functionTest(apples, a -> a.getColor());
}
4.4.原始类型特化
以上是泛型函数式接口,还有一些函数式接口专门为了某些类型而设计,这里我们引入原始类型特化的概念。
Java的数据类型要么是引用类型(如:Byte、Integr、Object、List),,要么是原始类型(基本类型)(byte、int、double、char)。而泛型只能绑定到引用类型,这是泛型内部实现决定的。因此,java中引入原始类型和引用类型转化的机制,也就是
装箱和拆箱。
装箱和拆箱解决了这个问题,但是在性能方面付出了一些代价。装箱的本质是将原始值包裹起来,装箱后需要的内存更多;而
拆箱需要额外的内存来所搜获取原始值
因此Java8为函数式接口根据原始值制定了专门的函数式接口,从而避免自动装箱和拆箱
I.一般来说,针对专门的输入参数类型的函数式接口,名称都要加上原始类型前缀
如:DoublePredicate、IntConsumer
II.针对输出类型的函数式接口,名称再在原始类型前缀的前面加To
如:ToIntFunction<T>
# 注意: 这里入参是int类型 而不是泛型
@FunctionalInterface
public interface IntPredicate {
boolean test(int value);
}
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
IntPredicate intPredicate = (int a) -> a > 2; #无需自动装箱、拆箱
Predicate<Integer> predicate = (Integer a) -> a > 2; #需要自动装箱、拆箱
4.5、常用的函数式接口总结
五、Lambda表达式异常处理
任何Java包中的函数式接口都不能抛出 受检异常(checked exception)
如何处理异常:
1.自定义函数式接口,并抛出异常
2.在Lambda表达式的主体中捕获异常