一.什么是行为参数化呢
说白了就是将一段行为当作参数传入一个方法中呗,那么这段行为是什么呢?
答:那也是一个方法咯。
Java在1.8版本引入了行为参数化的概念,首先,我们先看一小段代码
public class test {
public static void main(String[] args){
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
show(consumer ,"hello world");
}
public static void show(Consumer<String> consumer,String text){
consumer.accept(text);
}
}
输出结果当然是:hello world 啦。
可是你想这有什么呢?不就是写了一个Consumer接口,然后写一个匿名类重写他的accept方法,然后带入Consumer到show方法调用它咩?Java没更新前也可以这样啊,只不过是传参了一个对象,然后在方法内调用了对象的方法。
那么我们再来看看下面这段代码,看看它和上面的代码有什么不同:
public class test {
public static void main(String[] args){
/*Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};*/
show(System.out::println ,"hello world");
}
public static void show(Consumer<String> consumer,String text){
consumer.accept(text);
}
}
注意!代码中3到8行已经注释了哦。
现在看看:show方法接受一个Consumer对象和字符串,然后调用对象的accept方法,这里都没变;可是著方法中调用show方法的时候传递的第一个参数不再是Consumer对象了,而是System.out::println,而这是什么意思呢?
System.out::println 是方法引用,方法引用的格式就是:类名::方法名 或者 对象名::成员方法名
我们都知道,在方法重写和重载中我们是怎么做的呢?在子类中写一个和主类中被重写方法方法名相同、参数类型、顺序相同的方法,那jdk是根据什么来判断是否是重写或者重载呢?没错,就是靠函数签名来判断的,就比如上面代码中的show方法,它的函数签名就是:名为show、接受两个参数并且第一个参数是Consumer类型、第二个参数是String类型的函数,如果返回类型相同,则是重写;否则是重载。Java根据你的方法引用来确定你的方法签名和返回类型,如果和形参中Consumer对象中的方法相同的话,就可以使用。
那么我们回到代码中去,我们在主方法中调用show方法时传入的第一个参数就是一个:无返回值,接受一个泛型参数的方法签名的方法引用。
你也可以传入其他方法进去,只要方法签一致就可以啦。就像下面一样:
public class test<T> {
public static void main(String[] args){
/*Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};*/
test one = new test();
show(one::otherMethod ,"hello world");
}
public void otherMethod(T t){
int i = 0;
System.out.println(i + t.toString());
}
public static void show(Consumer<String> consumer,String text){
consumer.accept(text);
}
}
结果是输出:0hello world
你也可以将show方法的第二个参数换成一个集合,然后在第一个参数中传入其他方法引用来对集合进行操作哦。
在上面我们说到在带哦用show方法时传入的方法引用的方法签名、返回类型要和Consumer对象中方法的一致,那么这个Consumer到底是什么呢?它的内部只有一个方法吗?如果有多个方法的话怎样根据传入的方法引用和他的众多方法比较呢?
答:Consumer其实是一个接口,一个函数式接口,话不多说,直接上源码
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
大家看,是不是感觉有些不对呢?Java接口明明不能所有实现方法的,现在却多了一个default方法被实现,嘿嘿,这是Java8的新特性,这个我会在之后的帖子中继续和大家分享。大家要注意的是@FunctionalInterface这个注解,它表明这个接口是一个函数式接口,函数式接口的定义就是:一个有且只有一个抽象方法,但是可以有多个非抽象方法的接口。非抽象方法就是default方法,可以有多个,但是抽象方法只能有一个,那么上面关于传入方法引用怎样匹配方法签名和返回值的疑问你们理解了吗?
就是说在上面的例子中我在show方法定义的Consumer中只有一个抽象方法accept,他是一个无返回值,接受一个泛型参数的方法,而我在调用show方法的时候也传入了和accept方法返回值一致,参数相同的方法引用,这就是方法重写,在上面的例子中我传入System.out::println时就是无形之中创建了一个匿名类,然后重写了他的accept方法,重写的方法就是System.out.println()方法。神奇咩?嘿嘿...
等等,还没完...Consumer接口提供一个无返回值类型接受一个参数的抽象方法供大家使用,那么如果我有其他需求呢?假如我需要一个有返回值的抽象方法供我使用呢?
别急,Java提供了一系列函数式接口来供我们按照需求使用,如果没有自己需要的接口的话我们也可以自己写一个函数式接口来使用。
函数式接口 | 函数描述符 |
Predicate<T> | T->boolean |
Consumer<T> | T->void |
Function<T,R> | T->R |
Supplier<T> | ()->T |
UnaryOperator<T> | T->T |
BinaryOperator<T> | (T,T)->T |
BiPredicate<L,R> | (L,R)->boolean |
BiConsumer<T,U> | (T,U)->void |
BiFunction<T,U,R> | (T,U)->R |
函数描述符是什么呢?这就要说到Lambda表达式了,在下一篇博客中我会讲解。(本人是小白大学生一枚,如有不对或者不当之处,还请各位前辈指点,嘿嘿)