在了解了Lambda表达式后,发现它适用于函数式接口,Java8内置了许多函数式接口,而为了使Lambda表达式在某些情况下能够更为精简,就出现了方法引用这一新特性,接下来分别看看这两个新特性
四大内置核心函数式接口:
所谓函数式接口,指的是接口中只含有一个抽象方法的接口,用@FunctionalInterface注解可以对该接口进行检验,为了配合lambda表达式使用,java8内置了四大核心函数式接口,分别是:
Consumer:消费型接口
正如其名,消费,该接口中有一个不带返回值带一个参数的方法accept
void accept(T t)
Supplier:供给型接口
供给,该接口中有一个带返回值不带参数的方法get
T get();
Function<T,R>:函数型接口
其中接口中的泛型T代表参数,R代表返回值,该接口有一个带返回值并且带一个参数的方法apply
R apply(T t);
Predicate:断言型接口
该接口有一个返回值为boolean类型且带一个参数的方法test
boolean test(T t);
除这四大接口外,还有许多由其衍生出来接口,比如BiPredicate<T, U>(返回值为boolean且有两个参数)、BiFunction<T, U, R>(返回值为R且有两个参数)等等
那么说完核心概念,我们分别看一看结合lambda表达式,这四个函数型接口能够做一些什么
例子一:消费型接口,这里就以消费为例,happy()为消费的方法,参数为消费的金额money以及用于消费的函数型接口Consumer:
@Test
public void test1(){
happy(10000,(m)-> System.out.println("消费了"+m+"元"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
可以看到lambda表达式能够作为参数传入方法中。
例子二:供给型接口
//功能:产生指定个数的整数并且存入集合中
@Test
public void test2(){
List<Integer> nums=getNumList(10,()->(int)(Math.random()*100));
for (Integer integer:nums) {
System.out.println(integer);
}
}
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list=new ArrayList<>();
for (int i=0;i<num;i++){
Integer n=sup.get();
list.add(n);
}
return list;
}
例子三:函数型接口
//功能:处理字符串
@Test
public void test3(){
String newStr=strHandler("\t\t hello world",(str)->str.trim());
System.out.println(newStr);
String str2=strHandler("hello world",(str)->str.substring(0,3));
System.out.println(str2);
}
public String strHandler(String str,Function<String,String> fun){
return fun.apply(str);
}
例子四:断言型接口
@Test
public void test4(){
List<String> list= Arrays.asList("Hello","World","Lambda","OK");
List<String> strList=filterStr(list,(s)->s.length()>3);
for (String str : strList) {
System.out.println(str);
}
}
//功能:将满足条件的字符串添加到集合中
public List<String> filterStr(List<String> strings, Predicate<String> pre){
List<String> strings=new ArrayList<>();
for (String str:strings) {
if (pre.test(str)){
list.add(str);
}
}
return list;
}
不难看出,在结合了Lambda表达式以及函数型接口的使用后,我们能够使用更少的代码完成传统代码所能够实现的功能
接下来我们说一说方法引用,所谓方法引用,指的是若Lambda体中有方法已经实现了,那么便可使用,标志符号是“::”,这是Lambda表达式的另一种表现形式,它主要有三种语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
第三种语法格式需在特定的条件下才可使用,我们在接下来的代码里进行演示
首先是对象::实例方法名,我们使用consumer接口示例,比如我们现在要输出一个语句,之前的写法:
@Test
public void test1(){
Consumer<String> consumer=(x) -> System.out.println(x);
consumer.accept("语法格式1");
}
这里我们输出了一句“语法格式1”,由于println方法在lambda体中已经使用过一次,查看System.out的源码:
public final static PrintStream out = null;
发现它其实是打印流PrintStream对象,为了方便理解,我们使用方法引用可以这样写:
@Test
public void test1(){
PrintStream ps=System.out;
Consumer<String> consumer=(x) -> ps.println(x);
consumer.accept("语法格式1");
Consumer<String> con=ps::println;
con.accept("对象::实例方法名");
}
再还原成我们常用的方式:
@Test
public void test1(){
Consumer<String> consumer=(x) -> System.out.println(x);
consumer.accept("语法格式1");
Consumer<String> con=System.out::println;
con.accept("对象::实例方法名");
}
接下来是第二种语法格式类::静态方法名,我们使用comparator接口进行示例,比如我们要比较两个整数:
@Test
public void test2(){
Comparator<Integer> com=(x,y) ->Integer.compare(x,y);
System.out.println(com.compare(2,1));
}
而使用方法引用,我们可以这样写:
@Test
public void test2(){
Comparator<Integer> com=(x,y) ->Integer.compare(x,y);
System.out.println(com.compare(2,1));
Comparator<Integer> com1=Integer::compare;
System.out.println(com1.compare(1,2));
}
接下来是第三种格式类::实例方法名,该格式比较特殊,比如我们现在需要比较两个字符串是否相等,使用我们之前提到的BiPredicate接口:
@Test
public void test3(){
BiPredicate<String,String> bp=(x,y) ->x.equals(y);
}
在这里x是.equals方法的调用者,而y是作为.equals方法的参数,在这种情况下,我们可以这样写:
@Test
public void test3(){
BiPredicate<String,String> bp=(x,y) ->x.equals(y);
BiPredicate<String,String> bp1=String::equals;
}
所以得出结论,这种类::实例方法名的格式的使用条件是,当Lambda参数列表的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,方可使用该方法
而有了方法引用,类似地,我们还可以使用构造器引用,比如我们有一个student对象,其中有有许多构造方法:
public class Student implements Serializable {
Integer id;
String name;
Integer age;
Double height;
public Student() {
}
public Student(Integer id) {
this.id = id;
}
public Student(Integer id, Integer age) {
this.id = id;
this.age = age;
}
public Student(Integer id, String name, Integer age, Double height) {
this.id = id;
this.name = name;
this.age = age;
this.height = height;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
}
例子:使用构造器引用新建一个Student对象,我们可以这样新建:
@Test
public void test5(){
Function<Integer,Student> fun=(x)->new Student(x);
Student student1=fun.apply(101);
System.out.println(JSONObject.toJSON(student1));
}
我们使用只带一个参数id的构造方法新建一个student对象,运行结果如下:
使用构造器引用,如下:
@Test
public void test5(){
Function<Integer,Student> fun=(x)->new Student(x);
Student student1=fun.apply(101);
System.out.println(JSONObject.toJSON(student1));
Function<Integer,Student> fun2=Student::new;
Student student2=fun2.apply(101);
System.out.println(JSONObject.toJSON(student2));
}
运行结果如下:
同样的,我们还可以使用数组引用,格式也是类似,比方我们新建一个String类型的数组并打印其长度:
@Test
//数组引用
public void test7(){
Function<Integer, String[]> fun=(x)->new String[x];
String[] strings=fun.apply(10);
System.out.println(strings.length);
Function<Integer,String[]> fun2=String[]::new;
String[] strings1=fun2.apply(12);
System.out.println(strings1.length);
}
运行结果如下: