【JAVA入门】Day35 - 方法引用
文章目录
方法引用就是“把已经有的方法当作函数式接口中抽象方法的方法体”来使用。
下面是一个排序方法,它的第二个参数是一个 Comparator 接口的实现类,它是一个函数式接口,因此我们可以用方法引用改写这段代码。
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
直接将已经存在的方法作为第二个参数。
public int subtraction(int n1, int n2) {
return n2 - n1;
}
Arrays.sort(arr, 比较规则 );
要使用方法引用有一些限制:
- 最重要的限制是,引用处必须是一个函数式接口,是可以把接口实现类作为参数传递的。
- 对于被引用的方法有以下三个限制:
1.被引用的方法必须已经存在了。
2.被引用的方法的形参和返回值需要跟抽象方法保持一致。
3.被引用的方法的功能要满足当前需求。
以下面这段代码为例,我们将演示方法引用相较于之前方法的简洁性。
package Functions;
import java.util.Arrays;
import java.util.Comparator;
public class FunctionDemo1 {
public static void main(String[] args) {
//需求:创建一个数组,进行倒序排序
Integer[] arr = {3,5,4,1,6,2};
//匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
//lambda表达式
Arrays.sort(arr, (Integer o1, Integer o2) -> {
return o2 - o1;
});
//lambda表达式简化格式
Arrays.sort(arr, (o1, o2) -> o2 - o1);
//方法引用
//表示引用FunctionDemo1类里面的subtraction方法
//把这个方法当作抽象方法的方法体
Arrays.sort(arr, FunctionDemo1::subtraction);
System.out.println(Arrays.toString(arr));
}
//被引用的方法可以是Java已经写好的,也可以是一些第三方工具类
public static int subtraction(int num1, int num2) {
return num2 - num1;
}
}
一、方法引用的分类
方法引用一般就分为三大类:引用静态方法、引用成员方法(这个分为引用其他类的成员方法、引用本类的成员方法、引用父类的成员方法)、引用构造方法。但是也有其他调用方式,比如:使用类名引用成员方法、引用数组的构造方法。
1.引用静态方法
格式:
类名::静态方法
范例:
Integer::parseInt
【练习】集合中有“1”“2”“3”“4”“5”五个字符串数字,要求把他们变成 int 类型。
package Functions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
public class FunctionDemo2 {
public static void main(String[] args) {
/*
集合中有“1”“2”“3”“4”“5”五个字符串数字,要求把他们变成 int 类型。
*/
//1.创建集合并添加元素
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "1","2","3","4","5");
//2.常规方式
ArrayList<Integer> list2 = new ArrayList<>();
for (String s : list) {
int i = Integer.parseInt(s);
list2.add(i);
}
//3.方法引用
/* list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
}).forEach(s -> System.out.println(s));*/
//1.方法需要已经存在
//2.方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
//3.方法的功能需要把形参的字符串转换成整数
//引用的是Integer方法中的pareInt()方法
list.stream().map(Integer::parseInt).forEach(s -> System.out.println(s));
}
}
2.引用成员方法
格式:
对象::成员方法
① 其他类:
其他类对象::方法名
② 本类:
this::方法名
③ 父类:
super::方法名
2.1 引用其他类的成员方法
package Functions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;
public class FunctionDemo3 {
public static void main(String[] args) {
/*
只要以张开头的,而且名字是3个字的
*/
//1.创建集合
ArrayList<String> list = new ArrayList<>();
//2.添加数据
Collections.addAll(list, "张无忌","周芷若","赵敏","张强","张三丰");
//3.过滤数据
//完整写法
/* list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张") && s.length() == 3;
}
}).forEach(s -> System.out.println(s));*/
//方法引用写法
//引用其他类的成员方法
//已知有另一个类StringOperation中有同样功能方法
//成员方法需要创建对象来使用,因此要先创建StringOperation类对象
StringOperation so = new StringOperation();
list.stream().filter(so::stringJudge);
}
}
2.2 引用本类和父类的成员方法
要注意,要引用本类成员方法时,若引用处位于 main() 方法中,它是一个静态方法,静态方法是没有 this 和 super 变量的,因此这里就只能先创建好本类的对象再进行调用。
package Functions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;
public class FunctionDemo3 {
public static void main(String[] args) {
/*
只要以张开头的,而且名字是3个字的
*/
//1.创建集合
ArrayList<String> list = new ArrayList<>();
//2.添加数据
Collections.addAll(list, "张无忌","周芷若","赵敏","张强","张三丰");
//3.过滤数据
//完整写法
/* list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张") && s.length() == 3;
}
}).forEach(s -> System.out.println(s));*/
//方法引用写法
//引用本类的成员方法
//已知本类中有同样功能方法
//成员方法需要创建对象来使用,因此要先创建本类对象
list.stream().filter(new FunctionDemo3()::stringJudge);
}
public boolean stringJudge(String s) {
return s.startsWith("张") && s.length() == 3;
}
}
因此,想要使用 this 和 super,就要在一个非静态方法中,如下面的例子:
下面的例子中,我们创建了一个窗体,自己在本类中写了一个按钮的事件监听方法,并作为参数传给了按钮的addActionListener()方法,这里就是使用了本类的方法引用,这样做使得该窗体无需实现 ActionListener 接口也能自己实现按钮点击的事件逻辑。
package Functions;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class LoginJFrame extends JFrame {
//创建一个按钮,点击按钮打印一句话
JButton go = new JButton("Go");
public LoginJFrame() {
//初始化界面
initJFrame();
//添加组件
initView();
//显示界面
this.setVisible(true);
}
//设置界面
public void initJFrame() {
//设置宽高
this.setSize(400,500);
}
//添加组件
public void initView() {
go.setBounds(120,274,150,50);
this.getContentPane().add(go);
//给按钮添加点击事件
//该方法形参是一个函数式接口
//因此可以在此处使用方法引用
//此时在本类可以自己写一个按钮监听的方法操作,作为方法引用,使用this::method传入
//这样可以使得该窗体不必实现ActionListener接口也能实现按钮的点击事件
go.addActionListener(this::method);
}
//自己写的按钮监听方法
public void method(ActionEvent e){
Object obj = e.getSource();
if(obj == go){
System.out.println("go按钮被点击了");
}
}
}
下面的例子中,我们让一个窗体继承了一个 MyJFrame 类,再让 MyJFrame 类继承自 JFrame,然后我们在 MyJFrame 类中自己编写了一个事件监听方法 method,然后在 MyJFrame 类中让按钮的 addActionListener() 方法引用 MyJFrame 类中的事件监听方法 method,这样就实现了引用自父类的成员方法。
父类 MyJFrame:
package Functions;
import javax.swing.*;
import java.awt.event.ActionEvent;
//注意形参和原方法保持一致
public class MyJFrame extends JFrame {
public void method(ActionEvent e){
System.out.println("go按钮被点击了");
}
}
子类 LoginJFrame:
package Functions;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class LoginJFrame extends MyJFrame {
//创建一个按钮,点击按钮打印一句话
JButton go = new JButton("Go");
public LoginJFrame() {
//初始化界面
initJFrame();
//添加组件
initView();
//显示界面
this.setVisible(true);
}
//设置界面
public void initJFrame() {
//设置宽高
this.setSize(400,500);
}
//添加组件
public void initView() {
go.setBounds(120,274,150,50);
this.getContentPane().add(go);
//给按钮添加点击事件
//该方法形参是一个函数式接口
//因此可以在此处使用方法引用
//此时在父类可以自己写一个按钮监听的方法操作,作为方法引用,使用super::method传入
go.addActionListener(super::method);
}
}
由上面的例子可以看出,方法引用可以代替函数式接口作为方法的参数传递,利用方法引用可以调整代码结构,省去实现多余的接口。
2.3 引用构造方法
我们引用构造方法就是为了创建对象。
格式:
类名::new
范例:
Student::new
以下面的例子为讲解,我们为什么要引用构造方法。
首先我们在 Student 类中重载了一个构造方法,它是为了适配构造方法引用而设的。
package Functions;
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//用方法引用,在Student类中重载构造方法,按照上面的public Student apply(String s)的方法体重写
//str表示流里面的每一个数据
public Student(String str){
this.name = str.split("-")[0];
this.age = Integer.parseInt(str.split("-")[1]);
}
}
然后我们在测试类中引用构造方法,代替了原来的 map 方法中的函数式接口重写的方法 apply。
package Functions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FunctionDemo4 {
/*
集合里存储姓名和年龄,比如:张无忌,15
要求:将数据封装成Student对象并收集到List集合中
*/
public static void main(String[] args) {
//1.创建集合对象
ArrayList<String> list = new ArrayList<>();
//2.添加数据
Collections.addAll(list, "张无忌-15","周芷若-14","赵敏-13","张强-20","张三丰-100","张翠山-40","张良-35","王二麻子-37","谢广坤-41");
//3.封装成Student对象并收集到List集合中
//String --> Student
/* //用map方法
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());*/
//用方法引用,在Student类中重载构造方法,按照上面的public Student apply(String s)的方法参数和功能重写
//构造方法没有返回值,只要保证重载的构造方法对象和你原方法返回的对象保持一致即可
List<Student> newList = list.stream().map(Student::new).collect(Collectors.toList());
}
}
这里要注意的就是,构造方法生成的对象,要和原来方法你要求返回的对象一致(姓名、年龄的格式),且构造方法的参数必须和原方法保持一致(String s)。
2.4 使用类名引用成员方法
直接用类名引用成员方法。
格式:
类名::成员方法
范例:
String::substring
【练习】集合里有一些字符串,要求变成大写后进行输出。
package Functions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
public class FunctionDemo5 {
public static void main(String[] args) {
/*
集合里有一些字符串,要求变成大写后进行输出。
类名::成员方法
规则:
1.需要有函数式接口
2.被引用的方法必须已经存在
3.被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致
4.被引用的方法功能要满足当前需求
抽象方法形参的详解:
第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法(比如第一个参数是String类型,那么只能引用String类中的方法)
在Stream流当中,第一个参数一般都表示流里面的每一个数据
假设流里面的数据是字符串,那么使用这种方式进行方法调用,只能引用String这个类中的方法
第二个参数到最后一个参数:跟被引用的方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法
*/
//1.创建集合对象
ArrayList<String> list = new ArrayList<>();
//2.添加数据
Collections.addAll(list, "aaa", "bbb", "ccc", "ddd");
//3.变成大写
//String --> String
/* list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(s -> System.out.println(s));*/
//拿流里面的每一个数据,去调用String类中的toUpperCase()方法,方法的返回值就是转换之后的字符串
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
}
}
这种方法有一定的局限性:
1.它不能引用所有类中的成员方法。
2.它能引用的类中的成员方法,取决于它的抽象方法(上面代码中的 apply(String s))的第一个参数类型,这个参数是什么类型,它就只能引用这个类中的方法。
3.如果这个抽象方法(apply(String s))没有第二个及以后的参数,那么它就可以引用一个类中的空参方法。
2.5 引用数组的构造方法
格式:
数据类型[]::new
范例:
Integer[]::new
【练习】集合中存储一些整数,然后收集到数组当中。
package Functions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.IntFunction;
public class FunctionDemo6 {
public static void main(String[] args) {
/*
细节:数组的类型,需要跟流中的数据的类型保持一致
*/
//1.创建集合
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9,10);
//2.收集到数组当中
/* Integer[] arr = list.stream().toArray(new IntFunction<Integer[]>() {
@Override
public Integer[] apply(int value) {
return new Integer[value];
}
});*/
//此时会将流中每一个数据存入生成的数组中
list.stream().toArray(Integer[]::new);
}
}
二、方法引用的例题
【练习1】集合中存一些字符串,比如:张三,23。收集到Student数组当中。
package Functions;
import JavaStudy1.Array.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
public class FuntcionTest1 {
public static void main(String[] args) {
/*
集合中存一些字符串,比如:张三,23。收集到Student数组当中。
*/
//1.创建集合添加元素
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-15","周芷若-14","赵敏-13","张强-20","张三丰-100","张翠山-40","张良-35","王二麻子-37","谢广坤-41");
//2.先把字符串变成Student对象,然后再把Student对象收集起来
//String --> Student
Student[] arr = list.stream().map(Student::new).collect(Collectors.toList()).toArray(Student[]::new);
System.out.println(Arrays.toString(arr));
}
}
【练习2】创建集合并添加学生对象,要求获取姓名放到数组中。
package Functions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class FunctionTest2 {
public static void main(String[] args) {
/*
创建集合并添加学生对象,要求获取姓名放到数组中。
"张无忌-15","周芷若-14","赵敏-13","张强-20","张三丰-100","张翠山-40","张良-35","王二麻子-37","谢广坤-41"
*/
//1.创建集合添加数据
ArrayList<Student> list = new ArrayList<>();
Collections.addAll(list, new Student("张无忌",15),new Student("周芷若",14),new Student("赵敏",13)
,new Student("张强",20),new Student("张三丰",100),new Student("张翠山",40));
//2.把姓名获取出来生成字符串数组
//Student --> String
String[] arr = list.stream().map(Student::getName).toArray(String[]::new);
System.out.println(Arrays.toString(arr));
}
}
【练习3】创建集合添加学生对象,把姓名和年龄拼成:张三-23的字符串,并放到数组当中。
package Functions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class FunctionTest3 {
public static void main(String[] args) {
/*
创建集合添加学生对象,把姓名和年龄拼成:张三-23的字符串,并放到数组当中。
*/
//1.创建集合并添加学生对象
ArrayList<Student> list = new ArrayList<>();
Collections.addAll(list, new Student("张无忌",15),new Student("周芷若",14),new Student("赵敏",13)
,new Student("张强",20),new Student("张三丰",100),new Student("张翠山",40));
//2.取出姓名和年龄拼成字符串
//Student --> String
String[] arr = list.stream().map(Student::toString).toArray(String[]::new);
System.out.println(Arrays.toString(arr));
}
}