方法引用
1. 概述
方法引用是 Java 编程语言中的一种特性,它提供了一种简洁的语法来直接引用现有的方法。
方法引用可以被认为是 Lambda 表达式的一种缩写形式,用于将方法作为参数传递或在函数式接口中使用。
-
方法引用:
- 把已经存在的方法拿过来用,当做函数式接口中抽象方法的方法体
-
方法引用符:
::
-
方法引用的要求:
-
需要有函数式接口
-
被引用的方法必须已经存在
-
被引用的方法的形参和返回值需要跟抽象方法保持一致
-
被引用方法的功能要满足当前的需求
-
方法引用可以简化代码,使得代码更加易读和模块化。它经常用于函数式接口、流操作和方法链式调用等场景。
方法引用只能用于
函数式接口
,即只有一个抽象方法的接口。
-
代码示例
需求:创建一个数组,进行倒序排列package text.text02; import java.util.Arrays; import java.util.Comparator; /*方法引用: 把已经存在的方法拿过来用,当做函数式接口中抽象方法的方法体 方法引用符: :: 方法引用的要求: 1.需要有函数式接口 2.被引用的方法必须已经存在 3.被引用的方法的形参和返回值需要跟抽象方法保持一致 4.被引用方法的功能要满足当前的需求 需求:创建一个数组,进行倒序排列 */ public class text73 { public static void main(String[] args) { Integer[] arr = {2, 5, 4, 6, 3, 1}; //匿名内部类 Arrays.sort(arr, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); System.out.println("匿名内部类: " + Arrays.toString(arr)); //Lambda表达式 //因为第二个参数的类型Comparator是一个函数式接口 Arrays.sort(arr, (Integer o1, Integer o2) -> { return o2 - o1; } ); System.out.println("Lambda表达式: " + Arrays.toString(arr)); //Lambda简写模式 Arrays.sort(arr, (o1, o2) -> o2 - o1); System.out.println("Lambda简写模式: " + Arrays.toString(arr)); //方法引用 //方法引用的要求: //1.需要有函数式接口 //2.被引用的方法必须已经存在 //3.被引用的方法的形参和返回值需要跟抽象方法保持一致 //4.被引用方法的功能要满足当前的需求 //表示引用text73里面的order方法,把这个方法当做抽象方法compare的方法体 Arrays.sort(arr, text73::order); System.out.println("方法引用: " + Arrays.toString(arr)); } //被引用的方法 public static int order(int i1, int i2) { return i2 - i1; } }
-
输出结果
2.引用静态方法
2.1 概述
引用静态方法是方法引用的一种形式,用于引用已定义的静态方法。
语法是 ClassName::staticMethodName
其中
ClassName
是包含静态方法的类名,staticMethodName
是要引用的静态方法的名称。
-
方法引用的要求:
-
需要有函数式接口
-
被引用的方法必须已经存在
-
被引用的方法的形参和返回值需要跟抽象方法保持一致
-
被引用方法的功能要满足当前的需求
-
-
格式:类名::静态方法
示例:text73::order
2.2 代码示例
- 代码示例
练习:集合中含有以下数字,要求将他们都变成int类型package text.text02; import java.util.ArrayList; import java.util.Collections; import java.util.function.Function; /* 引用静态方法: 格式:类名::静态方法 示例:text73::order 练习:集合中含有以下数字,要求将他们都变成int类型 "1","2","3","4","5" */ public class text74 { public static void main(String[] args) { //创建集合并添加元素 ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "1", "2", "3", "4", "5"); //常规方法 System.out.println("常规方法:"); for (String s : list) { int i = Integer.parseInt(s); System.out.print(i + " "); } System.out.println(); //方法引用 //方法引用的要求: //1.需要有函数式接口 //2.被引用的方法必须已经存在 //3.被引用的方法的形参和返回值需要跟抽象方法保持一致 //4.被引用方法的功能要满足当前的需求 //表示引用Integer里面的parseInt方法,把这个方法当做抽象方法apply的方法体 System.out.println("方法引用:"); list.stream() .map(Integer::parseInt) .forEach(s -> System.out.print(s + " ")); } }
- 输出结果
3. 引用成员方法
3.1 概述
引用成员方法是方法引用的一种形式,用于引用已定义的非静态成员方法。
- 语法是
instance::methodName
其中
instance
是对象实例,methodName
是要引用的成员方法的名称。
-
格式:
对象:: 成员方法
-
方法引用的要求:
-
需要有函数式接口
-
被引用的方法必须已经存在
-
被引用的方法的形参和返回值需要跟抽象方法保持一致
-
被引用方法的功能要满足当前的需求
-
-
引用成员方法可以是实例方法或对象方法,取决于方法在哪个类中定义。
- 如果方法在同一个类中定义,可以使用实例方法引用,即使用类的实例加上双冒号和方法名。
- 如果方法在不同的类中定义,需要使用对象方法引用,即使用对象的实例加上双冒号和方法名。
编译器会根据上下文自动匹配适当的对象实例来调用方法引用。
3.2 分类
3.2.1 其他类
格式:其他类对象::方法名
- 引用其他类的成员方法:
-
静态方法引用:可以直接通过类名和方法名来引用其他类的静态方法。语法为
ClassName::staticMethodName
。 -
实例方法引用:需要创建其他类的实例,然后通过实例和方法名来引用其成员方法。语法为
instance::methodName
。
-
示例:
class StringUtils {
static String toUpperCase(String s) {
return s.toUpperCase();
}
}
class OtherClass {
String appendWorld(String s) {
return s + " World";
}
}
public class Main {
public static void main(String[] args) {
// 静态方法引用
Converter converter1 = StringUtils::toUpperCase;
String result1 = converter1.convert("hello");
System.out.println(result1); // 输出: HELLO
// 实例方法引用
OtherClass otherClass = new OtherClass();
Converter converter2 = otherClass::appendWorld;
String result2 = converter2.convert("Hello");
System.out.println(result2); // 输出: Hello World
}
}
在上面的示例中,我们定义了一个
StringUtils
类和一个OtherClass
类。StringUtils
类有一个静态方法toUpperCase
,OtherClass
类有一个实例方法appendWorld
。通过静态方法引用和实例方法引用,我们可以在Main
类中引用这两个类中的方法。
3.2.2 本类
格式: this::方法名
-
静态方法没有this,如果非要调用本类中的方法,可以创建本来对象调用
-
静态成员被共享于所有类的实例,而不是与特定的实例绑定。因此,静态成员没有"this" 引用可以使用。
- 引用本类的成员方法:
- 实例方法引用:只需要使用
this
关键字加上双冒号和方法名来引用本类的实例方法。
- 实例方法引用:只需要使用
示例:
class MyClass {
void printMessage(String message) {
System.out.println("Message: " + message);
}
void greet(String name) {
System.out.println("Hello, " + name);
}
void testMethod() {
Consumer<String> consumer1 = this::printMessage;
consumer1.accept("Hello World");
Consumer<String> consumer2 = this::greet;
consumer2.accept("John");
}
}
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.testMethod();
}
}
在上面的示例中,我们定义了一个
MyClass
类,其中有两个实例方法printMessage
和greet
。通过实例方法引用,我们在testMethod
中引用了本类的方法,并通过Consumer
接口来调用这些方法。
3.2.3 父类
格式super::方法名
-
静态方法没有super,如果非要调用本类中的方法,可以创建本来对象调用
-
静态成员被共享于所有类的实例,而不是与特定的实例绑定。因此,静态成员没有"this" 引用可以使用。
-
引用父类的成员方法:
- 实例方法引用:可以使用
super
关键字加上双冒号和方法名来引用父类的实例方法。
- 实例方法引用:可以使用
示例:
class ParentClass {
void printMessage() {
System.out.println("Hello from Parent");
}
}
class ChildClass extends ParentClass {
void testMethod() {
Consumer<Void> consumer = super::printMessage;
consumer.accept(null);
}
}
public class Main {
public static void main(String[] args) {
ChildClass childClass = new ChildClass();
childClass.testMethod();
}
}
在上面的示例中,我们定义了一个
ParentClass
父类和一个ChildClass
子类。ParentClass
类有一个实例方法printMessage
,在ChildClass
的testMethod
中,我们使用父类的实例方法引用来引用父类的printMessage
方法,并通过Consumer
接口来调用该方法。
3.3 代码示例
- 代码示例
-
练习:集合中有些名字,按照要求过滤
-
数据:“张无忌”,“周芷诺”,“赵敏”,“张强”,“张三丰”
-
要求:只要以“张”开头,而且名字是3个字的
package text.text02; import java.util.ArrayList; import java.util.Collections; import java.util.function.Consumer; import java.util.function.Predicate; /*引用成员方法: 格式:对象:: 成员方法 1.其他类 其他类对象::方法名 2.本类 this::方法名(静态方法没有this,如果非要调用本类中的方法,可以创建本来对象调用) 静态成员被共享于所有类的实例,而不是与特定的实例绑定。因此,静态成员没有"this" 引用可以使用。 3.父类 super::方法名 (静态方法没有super,如果非要调用本类中的方法,可以创建本来对象调用) 练习:集合中有些名字,按照要求过滤 数据:"张无忌","周芷诺","赵敏","张强","张三丰" 要求:只要以“张”开头,而且名字是3个字的 */ public class text75 extends kind2 { public static void main(String[] args) { //创建集合并添加数据 ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "张无忌", "周芷诺", "赵敏", "张强", "张三丰"); //常规方法的省略模式 System.out.println("常规方法的省略模式:"); list.stream() .filter(s -> s.startsWith("张")) .filter(s -> s.length() == 3) .forEach(s -> System.out.print(s + " ")); System.out.println(); //常规方法的完整模式 System.out.println("常规方法的完整模式:"); list.stream() .filter(new Predicate<String>() { @Override public boolean test(String s) { return s.startsWith("张"); } }) .filter(new Predicate<String>() { @Override public boolean test(String s) { return s.length() == 3; } }) .forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.print(s + " "); } }); System.out.println(); //方法引用 //方法引用的要求: //1.需要有函数式接口 //2.被引用的方法必须已经存在 //3.被引用的方法的形参和返回值需要跟抽象方法保持一致 //4.被引用方法的功能要满足当前的需求 System.out.println("方法引用:"); //本类中:(静态方法没有this,如果非要调用本类中的方法,可以创建本来对象调用)静态成员被共享于所有类的实例,而不是与特定的实例绑定。因此,静态成员没有"this" 引用可以使用。 System.out.print("本类中的方法引用:"); list.stream() .filter(new text75()::stringJudge) .forEach(s -> System.out.print(s + " ")); System.out.println(); //其他类中: System.out.print("其他类中的方法引用:"); list.stream() .filter(new kind1()::stringJudge) .forEach(s -> System.out.print(s + " ")); System.out.println(); //父类中:(静态方法没有super,如果非要调用本类中的方法,可以创建本来对象调用) System.out.print("父类中的方法引用:"); list.stream() .filter(new kind2()::stringJudge) .forEach(s -> System.out.print(s + " ")); } public boolean stringJudge(String s) { return s.startsWith("张") && s.length() == 3; } } class kind1 { public boolean stringJudge(String s) { return s.startsWith("张") && s.length() == 3; } } class kind2 { public boolean stringJudge(String s) { return s.startsWith("张") && s.length() == 3; } }
-
- 输出结果
4. 引用构造方法
4.1 概述
引用构造方法的语法形式为:类名::new
,其中类名是需要引用构造方法的类的名称。
-
格式:类名:: new (引用构造方法中的返回值不用管,构造方法没有返回值)
示例:Student::new
-
方法引用的要求:
-
需要有函数式接口
-
被引用的方法必须已经存在
-
被引用的方法的形参和返回值需要跟抽象方法保持一致
-
被引用方法的功能要满足当前的需求
-
4.2 代码示例
- 代码示例
-
练习:集合里面存储姓名和年龄,比如:张无忌,15
-
要求:将数据封装成Student对象并收集到List集合中
package text.text02; import java.util.ArrayList; import java.util.Collections; import java.util.function.Function; /*引用构造方法: 格式:类名:: new (引用构造方法中的返回值不用管,构造方法没有返回值) 示例:Student::new 方法引用的要求: 1.需要有函数式接口 2.被引用的方法必须已经存在 3.被引用的方法的形参和返回值需要跟抽象方法保持一致 4.被引用方法的功能要满足当前的需求 练习:集合里面存储姓名和年龄,比如:张无忌,15 要求:将数据封装成Student对象并收集到List集合中 */ public class text76 { public static void main(String[] args) { //创建集合并添加数据 ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "张无忌,12", "周芷诺,32", "赵敏,23", "张强,23", "张三丰,43", "张翠山,34", "张良,19", "王二,38"); //常规方法 System.out.println("======================常规方法:======================"); list.stream() .map(new Function<String, Student9>() { @Override public Student9 apply(String s) { String name = s.split(",")[0]; int age = Integer.parseInt(s.split(",")[1]); return new Student9(name, age); } }) .forEach(s -> System.out.println(s.getName() + " , " + s.getAge())); //方法引用 System.out.println("======================方法引用:======================"); list.stream() .map(Student9::new) .forEach(s -> System.out.println(s.getName() + " , " + s.getAge())); } } class Student9 { private String name; private int age; public Student9() { } //被引用的构造方法 public Student9(String s) { this.name = s.split(",")[0]; this.age = Integer.parseInt(s.split(",")[1]); } public Student9(String name, int age) { this.name = name; this.age = age; } /** * 获取 * * @return name */ public String getName() { return name; } /** * 设置 * * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * * @return age */ public int getAge() { return age; } /** * 设置 * * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student9{name = " + name + ", age = " + age + "}"; } }
-
- 输出结果
5. 使用类名引用成员方法
5.1 概述
引用方法的语法形式为:类名::方法名,其中类名是要引用方法的类的名称,方法名是要引用的方法的名称。
-
格式:类名::成员方法
示例:String::subString
-
方法引用的规则:(独有)
-
需要有函数式接口
-
被引用的方法必须已经存在
-
被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致;如果没有第二个形参,说明被引用的方法需要无参的成员方法
-
被引用方法的功能需要满足当前的需求
-
-
抽象方法形参的详解:
-
第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法,在Stream流当中,第一个参数一般都表示流里面的每一个数据
假设流里面的数据是字符串,那么使用这种方法进行方法引用,只能引用String这个类中的方法
-
第二个参数到最后一个参数:跟被引用的方法的形参保持一致,如果没有第二个形参,说明被引用的方法需要无参的成员方法
-
-
局限性
不能引用所有类中的成员方法,是跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法
5.2 代码示例
- 代码示例
-
练习:集合里面一些字符串,要求变成大写后进行输出
-
集合:“aaa”,“bbb”,“ccc”,“ddd”
package text.text02; import java.util.ArrayList; import java.util.Collections; import java.util.function.Function; /*使用类名引用成员方法: 格式:类名::成员方法 示例:String::subString 方法引用的规则:(独有) 1.需要有函数式接口 2.被引用的方法必须已经存在 3.被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致;如果没有第二个形参,说明被引用的方法需要无参的成员方法 4.被引用方法的功能需要满足当前的需求 抽象方法形参的详解: 第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法 在Stream流当中,第一个参数一般都表示流里面的每一个数据 假设流里面的数据是字符串,那么使用这种方法进行方法引用,只能引用String这个类中的方法 第二个参数到最后一个参数:跟被引用的方法的形参保持一致,如果没有第二个形参,说明被引用的方法需要无参的成员方法 局限性:不能引用所有类中的成员方法,是跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法 练习:集合里面一些字符串,要求变成大写后进行输出 集合:"aaa","bbb","ccc","ddd" */ public class text77 { public static void main(String[] args) { //创建集合并添加数据 ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "aaa", "bbb", "ccc", "ddd"); //常规方法 System.out.print("常规方法:"); list.stream() .map(new Function<String, String>() { @Override public String apply(String s) { String str = s.toUpperCase(); return str; } }) .forEach(s -> System.out.print(s + " ")); System.out.println(); //方法引用 System.out.print("方法引用:"); list.stream() //拿着流里面的每一个数据,去调用String类中的toUpperCase方法,方法的返回值就是转换之后的结果 .map(String::toUpperCase) .forEach(s -> System.out.print(s + " ")); } }
-
- 输出结果
6. 引用数组的构造方法
6.1 概述
-
格式:数据类型[] ::new
示例:int [] :: new
-
方法引用的要求:
-
需要有函数式接口
-
被引用的方法必须已经存在
-
被引用的方法的形参和返回值需要跟抽象方法保持一致
-
被引用方法的功能要满足当前的需求
-
-
细节:数组的类型,需要跟流中数据的类型一致。
6.2 代码示例
- 代码示例
练习:集合中存储一些整数,收集到数组当中package text.text02; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.function.IntFunction; /*引用数组的构造方法: 格式:数据类型[] ::new 示例:int [] :: new 细节:数组的类型,需要跟流中数据的类型一致。 练习:集合中存储一些整数,收集到数组当中 */ public class text78 { public static void main(String[] args) { //创建集合并添加元素 ArrayList<Integer> list = new ArrayList<>(); Collections.addAll(list, 1, 2, 3, 4, 5, 6); //常规方法 System.out.print("常规方法:"); Integer[] arr1 = list.stream() .toArray(new IntFunction<Integer[]>() { @Override public Integer[] apply(int value) { return new Integer[value]; } }); System.out.println(Arrays.toString(arr1)); //方法引用 System.out.print("方法引用:"); Integer[] arr2 = list.stream() .toArray(Integer[]::new); System.out.println(Arrays.toString(arr2)); } }
- 输出结果
7. 代码示例1
-
代码示例
练习1:集合里面存储一些字符串数据,比如:张三,23
收集到Student类型的数组当中(使用方法引用完成)package text.text02; import java.util.ArrayList; import java.util.Arrays; /* 练习1:集合里面存储一些字符串数据,比如:张三,23 收集到Student类型的数组当中(使用方法引用完成) */ public class text79 { public static void main(String[] args) { //创建集合并添加数据 ArrayList<String> list = new ArrayList<>(); list.add("周润发,23"); list.add("成龙,24"); list.add("刘德华,46"); list.add("吴京,14"); list.add("周星驰,25"); list.add("李连杰,54"); list.add("林心如,24"); list.add("张曼玉,35"); list.add("杨幂,15"); list.add("杨柳岩,23"); list.add("林志玲,45"); list.add("杨祖贤,23"); //方法引用(收集到Student类型的数组当中) Student10[] array = list.stream() .map(Student10::new) .toArray(Student10[]::new); System.out.println(Arrays.toString(array)); } } class Student10 { private String name; private int age; public Student10() { } public Student10(String s) { this.name = s.split(",")[0]; this.age = Integer.parseInt(s.split(",")[1]); } public Student10(String name, int age) { this.name = name; this.age = age; } /** * 获取 * * @return name */ public String getName() { return name; } /** * 设置 * * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * * @return age */ public int getAge() { return age; } /** * 设置 * * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student10{name = " + name + ", age = " + age + "}"; } }
-
输出结果
8. 代码示例2
-
代码示例
练习2:创建集合添加学生对象,学生对象属性:name,age
只获取姓名并放到数组当中(使用方法引用完成)package text.text02; /* 练习2:创建集合添加学生对象,学生对象属性:name,age 只获取姓名并放到数组当中(使用方法引用完成) */ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; public class text80 { public static void main(String[] args) { //创建集合并添加学生对象 ArrayList<Student11> list = new ArrayList<>(); Collections.addAll(list, new Student11("刘备", 23) , new Student11("刘备", 23) , new Student11("张飞", 24) , new Student11("马超", 45) , new Student11("关羽", 24) , new Student11("赵云", 54) , new Student11("曹操", 12) , new Student11("吕布", 15) , new Student11("貂蝉", 16)); //方法引用(只获取姓名并放到数组当中) String[] array = list.stream() .map(Student11::getName) .toArray(String[]::new); System.out.println(Arrays.toString(array)); } } class Student11 { private String name; private int age; public Student11() { } public Student11(String name, int age) { this.name = name; this.age = age; } /** * 获取 * * @return name */ public String getName() { return name; } /** * 设置 * * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * * @return age */ public int getAge() { return age; } /** * 设置 * * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student10{name = " + name + ", age = " + age + "}"; } }
-
输出结果
9. 代码示例3
-
代码示例
练习3:创建集合添加学生对象,学生对象属性:name,age
把姓名和年龄拼接成:张三,23的字符串,并放到数组当中(使用方法引用完成)package text.text02; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; /* 练习3:创建集合添加学生对象,学生对象属性:name,age 把姓名和年龄拼接成:张三,23的字符串,并放到数组当中(使用方法引用完成) */ public class text81 { public static void main(String[] args) { //创建集合并添加学生对象 ArrayList<Student12> list = new ArrayList<>(); Collections.addAll(list, new Student12("刘备", 23) , new Student12("刘备", 23) , new Student12("张飞", 24) , new Student12("马超", 45) , new Student12("关羽", 24) , new Student12("赵云", 54) , new Student12("曹操", 12) , new Student12("吕布", 15) , new Student12("貂蝉", 16)); //方法引用 String[] array = list.stream() .map(Student12::getStudent) .toArray(String[]::new); System.out.println(Arrays.toString(array)); } } class Student12 { private String name; private int age; public Student12() { } public Student12(String name, int age) { this.name = name; this.age = age; } /** * 获取 * * @return name */ public String getName() { return name; } public String getStudent() { return name + "," + age; } /** * 设置 * * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * * @return age */ public int getAge() { return age; } /** * 设置 * * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student10{name = " + name + ", age = " + age + "}"; } }
-
输出结果
10. 注意事项
-
成员方法引用的目标类型必须与函数式接口中声明的方法的参数和返回类型相匹配。如果引用的方法与接口中声明的方法具有不同的参数和返回类型,编译器将无法推断正确的类型,并且会导致编译错误。
-
当引用静态方法时,需要使用类名来引用方法。例如,
Integer::parseInt
引用了Integer
类的静态方法parseInt
。 -
当引用实例方法时,需要使用对象或类名来引用方法。例如,
String::toUpperCase
引用了String
类的非静态方法toUpperCase
。如果在引用实例方法时需要指定对象,请在引用方法后使用::
操作符传递对象。 -
当引用构造方法时,需要使用类名和
new
关键字来引用构造方法。例如,ArrayList::new
引用了ArrayList
类的构造方法。 -
方法引用不是万能的。虽然它可以提高代码的可读性和简洁性,但并不是所有情况下都适用。有时候,使用方法引用会导致代码更加难以理解,此时可以选择使用 lambda 表达式或传统的方法调用。