一、方法引用
1.1、什么是方法引用?
把已经存在的方法拿过来用,当做函数式接口中抽象方法的方法体
1.2、方法引用时要注意什么?
- 需要有函数式接口
- 被引用方法必须已经存在
- 被引用方法的形参和返回值需要跟抽象方法保持一致
- 被引用方法的功能要满足当前的需求
1.3、什么是函数式接口?
我们在学习lambda表达式的时候曾经学过,函数式接口就是有且仅有一个抽象方法的接口,他一把有@FunctionalInterface注解。
1.4、方法引用具体能干什么?
我们首先来看一个例子:
// 将下列数组由小到大排序
Integer arr[] = {1,2,3,4,5,6,7,8};
// 使用匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
// 使用lambda表达式
Arrays.sort(arr,((o1, o2) -> o2-o1));
按照之前所学的排序方法,我们能做到这个样子。那我们接下来来看方法引用。方法引用是把已经存在的方法拿过来用,当做函数式接口中抽象方法的方法体。那么这个例子当中的
就是一个函数式接口,我们可以用方法引用来替换这个Comparator。
// 将下列数组由小到大排序
Integer arr[] = {1,2,3,4,5,6,7,8};
// 使用方法引用
Arrays.sort(arr,test::overflow);
Arrays.stream(arr).forEach(e-> System.out.println(e));// 8 7 6 5 4 3 2 1
}
// 被引用的方法
public static Integer overflow(Integer o1,Integer o2){
return o2-o1;
}
OK,我们看了这一个例子,接下来我们详细讲一下其中的细节。
二、方法引用的细节
我们看上面的例子发现了一个写法:
Arrays.sort(arr,test::overflow);
这里面最重要的就是::
双冒号,他就是方法引用符。方法引用遵循下面这四个原则:
- 引用处需要是函数式接口
- 被引用的方法需要已经存在
- 被引用方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
- 被引用的方法需要能满足当前的需求
2.1、引用静态方法
格式
:类名::静态方法名
范例
:Integer::parseInt
来看一个范例:这是没有使用方法引用的:
List<String> stringList = new ArrayList<String>();
// 将下列String 转化为 integer
Collections.addAll(stringList,"1","2","3","4","5","6","7","8","9");
// 使用Stream流中的map来转化类型 第一个参数为原始参数 第二个参数为将要转化类型的参数
Collection<Integer> collect = stringList.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
}).collect(Collectors.toList());
// lambda简化
List<Integer> collect1 = stringList.stream().map(e -> Integer.parseInt(e)).collect(Collectors.toList());
这是使用了方法引用的:
List<String> stringList = new ArrayList<String>();
// 将下列String 转化为 integer
Collections.addAll(stringList,"1","2","3","4","5","6","7","8","9");
// 使用Stream流中的map来转化类型 第一个参数为原始参数 第二个参数为将要转化类型的参数
List<Integer> collect = stringList.stream().map(Integer::parseInt).collect(Collectors.toList());
System.out.println(collect); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
2.2、引用成员方法
格式
:对象::成员方法
其他类
:其他类对象::方法名
本类
:this::方法名
父类
:super::方法名
本类与父类方法需要注意的是,没有静态static方法。因为this与super关键字是调用不出静态方法的。
2.3、引用构造方法
为什么要引用构造方法?目的当然为了创建对象
格式
:类名::new
我们直接看例子:
Student:这是一个标准的JAVA bean类 请补全他的getset方法以及构造方法
public class Student {
private String name;
private Integer age;
}
List<String> stringList = new ArrayList<String>();
// 将下列String 转化为 Student对象
Collections.addAll(stringList,"张无忌-19","杨天策-22");
// 没用方法引用
List<Student> collect = stringList.stream().map(new Function<String, Student>() {
@Override
public Student apply(String s) {
String s1 = s.split("-")[0];
int i = Integer.parseInt(s.split("-")[1]);
return new Student(s1,i);
}
}).collect(Collectors.toList());
System.out.println(collect);//[Student{name='张无忌', age=19}, Student{name='杨天策', age=22}]
我们再来看使用了方法引用的例子:这里要注意的,方法引用的性质有一条:被引用方法的形参和返回值需要跟抽象方法保持一致
,我们看这个apply,他的形参只有一个,返回值为Student类型,那么我们的构造方法返回值必然是Student,但是我们的形参也只能有一个。
单独参数构造方法如下:
public class Student {
private String name;
private Integer age;
public Student(String list) {
this.name = list.split("-")[0];
this.age = Integer.parseInt(list.split("-")[1]);
}
}
主函数如下:
List<String> stringList = new ArrayList<String>();
// 将下列String 转化为 Student对象
Collections.addAll(stringList,"张无忌-19","杨天策-22");
// 没用方法引用
List<Student> collect = stringList.stream().map(Student::new).collect(Collectors.toList());
System.out.println(collect);//[Student{name='张无忌', age=19}, Student{name='杨天策', age=22}]
2.4、类名引用成员方法
格式
:类名::成员方法
例子
:String::substring
List<String> stringList = new ArrayList<String>();
// 将下列String 转化为 大写
Collections.addAll(stringList,"a","b");
List<Object> collect = stringList.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).collect(Collectors.toList());
System.out.println(collect);//[A, B]
我们再看引用了成员方法的
List<String> stringList = new ArrayList<String>();
// 将下列String 转化为 大写
Collections.addAll(stringList,"a","b");
stringList.stream().map(String::toUpperCase).forEach(e-> System.out.println(e));// A B
看着是没什么问题吧?我们看看这个toUpperCase( )
诶?不对啊 他没有参数,但是函数式接口里面可是有一个参数的?
这为什么可以引用,之前不说被引用方法的形参和返回值需要跟抽象方法保持一致
吗?
别急,我们来看看使用类名::成员方法
所带来的新的特性:
- 需要有函数式接口
- 被引用的方法必须已经存在
- 被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致。
- 被引用方法的功能需要满足当前的需求
抽象方法形参的详解:
- 第一个参数:表示被引用方法的调用,决定了可以引用哪些类中的方法。在stream流当中,第一个参数一般都表示流里面的每一个数据。假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用string这个类中的方法
- 第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法
2.5、引用数组的构造方法
格式
:数组类型[]::new
List<Integer> arr = new ArrayList<>();
Collections.addAll(arr,1,2,4,6);
Integer[] integers = arr.stream().toArray(Integer[]::new);
Arrays.stream(integers).forEach(e-> System.out.print(e));//1246
三、小练习
方法一:
List<Student> stringList = new ArrayList<>();
String[] names;
stringList.add(new Student("张三",11));
stringList.add(new Student("李四",88));
// 收集姓名到数组中
String[] strings = stringList.stream().map(new Function<Student, String>() {
@Override
public String apply(Student student) {
return student.getName();
}
}).toArray(String[]::new);
Arrays.stream(strings).forEach(e-> System.out.println(e));//张三 李四
方法二:使用lambda表达式简化:
List<Student> stringList = new ArrayList<>();
String[] names;
stringList.add(new Student("张三",11));
stringList.add(new Student("李四",88));
// 收集姓名到数组中
String[] strings = stringList.stream().map(student -> student.getName()).toArray(String[]::new);
Arrays.stream(strings).forEach(e-> System.out.println(e));//张三 李四