- 方法引用的出现原因在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢?答案肯定是没有必要那我们又是如何使用已经存在的方案的呢?这就是我们要讲解的方法引用,我们是通过方法引用来使用已经存在的方案
概述:
方法引用就是把已经有的方法拿过来用,当做函数式接口中的抽象方法的方法体
以后会在MybatisPlus这个框架中大量使用方法引用
如下过程就是方法引用:
要求:对Integer数组arr进行降序排序:
这时可以用方法引用 ,当做函数式接口中的抽象方法的方法体
public class Test01 {
public static void main(String[] args) {
Integer []arr={4,2,5,7,8};
//1.普通表达式
/* Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});*/
//2.lambda表达式
/*Arrays.sort(arr, ((o1, o2) -> o2-o1));*/
//3.方法引用:
//表示引用Test01类里面的subtraction
//把这个方法当做抽象方法的方法体
Arrays.sort(arr,Test01::subtraction);
//打印
System.out.println(Arrays.toString(arr));//[8, 7, 5, 4, 2]
}
//可以是Java己经写好的,也可以是一些第三方的工具类
public static int subtraction(Integer num1,Integer num2){
return num2-num1;
}
}
::是方法引用符
## 使用方法引用必须满足下面四点:
- 引用处必须是函数式接口
- 如上:Compartor是函数式接口
- 被引用的方法必须已经存在
- 如上:subtraction已手动创建
- 被引用的方法的形参和返回值要和抽象方法保持一致
- 被引用方法的功能必须满足需求
方法引用的分类:
引用静态方法:
格式:类名::静态方法名
如:Integer::parseInt
我们上面那个的例子其实也是引用静态方法
练习:
public class Test02 {
public static void main(String[] args) {
//引用静态方法
ArrayList<String>list=new ArrayList<>();
Collections.addAll(list,"1","2","3","4","5");
//类型转换可以用刚学的stream流中的中间方法map
//1.普通表达式
/*List<Integer> newList = list.stream()
.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
}).collect(Collectors.toList());*/
//2.方法引用
List<Integer> newList = list.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
//获取0索引看看可以运算吗
int num = newList.get(0) + 1;
System.out.println(num);//2,可以运算说明成功转为Integer类型了
}
}
可以看看上面的方法引用是否满足四个要求:
- Function是函数式接口
- 被引用的方法是java已存在的
- 被引用的parseInt方法的形参和返回值和抽象方法一致
- 被引用的方法的功能必须满足需求
引用成员方法
引用其他类的成员方法:
格式: 对象::成员方法
练习:
public class Test03 {
public static void main(String[] args) {
ArrayList<String>list=new ArrayList<>();
Collections.addAll(list,"张三","钱七","王五","张无忌","李四");
//要求:过滤数据(只要以张开头,而且名字是3个字的)
/*//普通写法
list.stream()
.filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张")&&s.length()==3;
}
});*/
//方法引用
//创建对象:
StringOperation so=new StringOperation();
list.stream()
.filter(so::method)
.forEach(s -> System.out.println(s));//张无忌
}
}
//其他类:
class StringOperation{
//成员方法
public boolean method(String s) {
return s.startsWith("张")&&s.length()==3;
}
}
引用本类的成员方法(引用处不能是静态)
格式:this::方法名
还是用上面这个例子:
此时将其他类的成员方法放到本类中,
public class Test03 {
public static void main(String[] args) {
ArrayList<String>list=new ArrayList<>();
Collections.addAll(list,"张三","钱七","王五","张无忌","李四");
//过滤数据(只要以张开头,而且名字是3个字的)
/*//普通写法
list.stream()
.filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张")&&s.length()==3;
}
});*/
//方法引用
//创建对象:
Test03 t=new Test03();
list.stream()
.filter(t::method)
.forEach(s -> System.out.println(s));//张无忌
}
//已放入本类
public boolean method(String s) {
return s.startsWith("张")&&s.length()==3;
}
}
这里同样满足四个要求:
- 引用处必须是函数时接口
- 被引用的方法已存在
- 被引用的方法的形参和返回值和抽象方法一样
- 功能满足需求
疑问:
不是说引用本类成员方法的格式是 this::方法名 吗
是的,但是本例子中的引用处是main方法,是静态的,而以前说过静态内没有this关键字,所以还是要创建本类对象再去用 对象名::方法名
引用父类的成员方法(引用处不能是静态)
格式:super::方法名
举例:
定义一个接口:Greetable
public interface Greetable {
void greet();//问候的抽象方法
}
Person类
public class Person {
//问候方法
public void sayHello(){
System.out.println("Hello! 我是人。");
}
}
子类Teacher
public class Teacher extends Person {
//重写父类方法
@Override
public void sayHello() {
System.out.println("Hello!,我是老师");
}
//定义一个方法,参数传递Gerrtable接口
public void method(Greetable g) {
g.greet();
}
public void show() {
//调用method,
//以前:使用匿名内部类的写法,调用父类的打招呼方法
method(new Greetable() {
@Override
public void greet() {
Teacher.super.sayHello();
}
});
//现在:方法引用
method(super::sayHello);
}
}
测试类:
class Test {
public static void main(String[] args) {
Teacher t = new Teacher();
t.show();
}
}
Hello! 我是人。
Hello! 我是人。
引用构造方法
格式:** 类名**::new
如:Student::new
练习:
Student类:
public class Student {
//属性
private String name;
private int age;
//构造
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// ...set、get、toString
}
测试类:
public class Test04 {
public static void main(String[] args) {
//创建集合
ArrayList<String> list = new ArrayList<>();
//添加元素
Collections.addAll(list, "张三,23", "李四,24", "王五,25");
//普通写法
/* List<Student> newList = list.stream().map(new Function<String, Student>() {
@Override
public Student apply(String s) {
return new Student(s.split(",")[0], Integer.parseInt(s.split(",")[1]));
}
}).collect(Collectors.toList());*/
//方法引用
list.stream().map(Student::new);
}
}
当我们在测试类中写到上面这一步时,new会报错,也就是说肯定是违背了方法引用的规则
可以发现抽象方法中只有一个形参,
而Student的构造方法只有无参或两个参,
这时我们就可以手动在Student中写一个只有一个形参的构造方法
如:
Student
public class Student {
//属性
private String name;
private int age;
//构造
public Student() {
}
//其中str就表示流中的每个数据
//调用构造方法时不用考虑返回值,因为该引用构造后一定会返回是否和抽象方法一致即可
public Student(String str){
this.name=str.split(",")[0];
this.age=Integer.parseInt(str.split(",")[1]);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// ...set、get、toString
}
public class Test04 {
public static void main(String[] args) {
//创建集合
ArrayList<String> list = new ArrayList<>();
//添加元素
Collections.addAll(list, "张三,23", "李四,24", "王五,25");
//普通写法
/* List<Student> newList = list.stream()
.map(new Function<String, Student>() {
@Override
public Student apply(String s) {
return new Student(s.split(",")[0], Integer.parseInt(s.split(",")[1]));
}
}).collect(Collectors.toList());*/
//方法引用
List<Student> newList = list.stream()
.map(Student::new)
.collect(Collectors.toList());
System.out.println(newList);
//[Student{name = 张三, age = 23}, Student{name = 李四, age = 24}, Student{name = 王五, age = 25}]
}
}
最后来看看Collectors的toList方法底层逻辑:
原来toList方法底层也是使用方法引用创建的ArrayList的对象
**其他方法引用的使用:
*使用类名引用成员方法(特殊):
格式:类名::成员方法
如:String::substring
练习:
//创建集合
ArrayList<String>list=new ArrayList<>();
Collections.addAll(list,"a","b");
//普通写法
//String->String,可以使用map方法
/*list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(s -> System.out.println(s));*/
//方法引用:
list.stream()
.map(String::toUpperCase)
.forEach(s -> System.out.println(s));
使用类名引用成员方法的特殊规则:
- 函数式接口
- 被引用方法已存在
- *被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参一致,返回值要一致
- 第一个参数:表示被引用方法的调定者,决定了可以引用哪些类中的方法
- 在Stream流当中,第一个参数一般都表示流里面的每一个数据。假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类的方法
- 第二个参数到最后一个参数:跟抽象方法的形参保持一致,如果抽象方法没有第二个参数,说明引用的方法需要是无参的成员方法。
- 第一个参数:表示被引用方法的调定者,决定了可以引用哪些类中的方法
- 被引用方法的功能需要满足当前的需求
如:
上面的抽象方法apply的形参是String类型,且只有一个,决定了只能引用String
类中的无参方法,还要满足需求,就只有toUpperCase这个方法
这种使用类名引用的局限性:
不能引用所有类中的成员方法。
是跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法。
引用数组的构造方法
格式:数据类型[ ] : :new
如:int [ ] : : new
练习:
ArrayList<Integer>list=new ArrayList<>();
Collections.addAll(list,1,2,3);
//普通表示
/*Integer[] arr = list.stream().toArray(value -> new Integer[value]);
System.out.println(Arrays.toString(arr));*/
//方法引用
Integer[] arr = list.stream().toArray(Integer[]::new);
System.out.println(Arrays.toString(arr));//[1, 2, 3]
总结: