JDK8新特性之方法引用(二)
在看方法引用之前我推荐大家去看下我之前写的JDK8新特性之Lambda表达式(一),因为方法引用是基于Lambda的,如果对函数式接口和Lambda表达式还不是特别理解的话对于方法引用可能理解起来有一定难度。
方法引用介绍
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
- 方法引用可以看做是Lambda表达式深层次的表达,换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖
使用方法引用的格式:使用操作符”::“将类(或对象)与方法名分隔开来,主要有以下三种使用情况:
- 情况一:对象::实例方法名
- 情况二:类::静态方法名
- 情况三:类::实例方法名
使用方法引用的要求:实现接口的抽象方法的参数列表和返回值类型,必须和方法引用的方法的参数列表和返回值类型保存一致(只针对情况一和情况二)
代码示例
上面的一些概念可能比较抽象不好理解,下面通过代码示例来展示方法引用的用法
情况一:对象::实例方法名
自定义一个函数式接口FunctionInterface,里面只有一个抽象方法print方法
// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterface {
/**
* 抽象方法
*/
public void print(String str);
}
我们想要在print方法中使用PrintStream类的println()方法(也就是System.out.println();)
使用lambda表达式的方式:
public static void main(String[] args) {
FunctionInterface functionInterface = (str) -> {
System.out.println(str);
};
functionInterface.print("哈哈哈");
}
我们将上面的lambda改造成使用方法引用,这里需要注意之前提到过的使用方法引用的前提是否满足:
- 在Lambda体也就是->的右边,已经有实现的方法了,也就是说我们能够直接调用println方法实现打印的目的而这个println方法属于已经实现过的方法属于PrintStream类
- 我们所调用的已经实现的方法对应的参数和返回值必须和我们自定义的那个抽象方法一致,也就是我们自己定义的print方法参数是str,返回值是void,而println方法中刚好存在这样的方法
使用方法引用的方式,语法格式,对象::实例方法名:
public static void main(String[] args) {
// PrintStream对象
PrintStream printStream = System.out;
//对象::实例方法名
FunctionInterface functionInterface = printStream ::println;
// 这里调用print方法,通过方法引用调用的实际上就是println方法
functionInterface.print("哈哈哈");
}
情况二:类::静态方法名
和第一种情况的对象::实例方法名区别其实是你lambda体(方法体)中所引用的方法是静态方法还是非静态方法,如果是静态方法就是类::静态方法名,非静态方法则是对象::静态方法名。同样我们可以通过代码来展示一下,先使用Lambda表达式的方法
public static void main(String[] args) {
// 通过使用Comparator比较两个数的大小
Comparator<Integer> comparator = (num1, num2) -> Integer.compare(num1,num2);
System.out.println(comparator.compare(2,3));
}
使用方法引用的方式,语法格式为,类::静态方法名:
public static void main(String[] args) {
/**
* 通过使用Comparator比较两个数的大小,由于Integer中的compare方法是静态方法,
* 且参数和返回值和Comparator的compare方法是一样的,因此可以使用方法引用
* 由于是静态方法可以直接使用类名调用
*/
Comparator<Integer> comparator = Integer::compare;
System.out.println(comparator.compare(2,3));
}
情况三:类::实例方法名
这种使用方式相比于前两种使用方式来说可能不太好理解,因为方法中的参数列表个数似乎和方法引用的方法中的参数个数不相匹配,举个例子自定义函数式接口中的方法int myCompare(T t1,T t2)
,String类中的int s1.compareTo(s2)
,看上去似乎参数列表不匹配,但是当myCompare的t1可以作为String中compareTo方法的调用者myCompare的t2可以作为String中compareTo方法的参数时,就能够使用方法引用。我们先看下Lambda表达式的实现:
public static void main(String[] args) {
/**
* 比较两个字符串的大小,使用到了String中的compareTo方法
*/
Comparator<String> comparator = (s1,s2) -> s1.compareTo(s2);
int result = comparator.compare("abcd", "abcf");
System.out.println(result);
}
使用方法引用来实现,语法格式,类::实例方法名,这种情况下我们可以这么理解,类(方法的调用者对应的类)::实例方法名(调用的具体方法),先看下使用
public static void main(String[] args) {
/**
* 类::实例方法名
* 类:String代表的是第一个参数对应的类
* 实例方法名:所调用的实例方法,参数会自动进行匹配
*/
Comparator<String> comparator = String::compareTo;
int result = comparator.compare("abcd", "abcf");
System.out.println(result);
}
这个确实比较不好理解,需要多练习才能理解,下面我们再举个例子来看看,类::实例方法名的使用
先自定义一个函数式接口:
@FunctionalInterface
public interface MyInterface {
/**
* 自定义的字符替换方法
* @param str 要替换的字符串
* @param oldStr 原字符串
* @param newStr 新字符串
* @return
*/
public String myStrReplace(String str,String oldStr,String newStr);
}
我们知道String类中有个字符串替换方法replace,首先我们使用Lambda表达式来完成myStrReplace方法的实现
public static void main(String[] args) {
MyInterface myInterface = (str,oldStr,newStr)->{
return str.replace(oldStr,newStr);
};
String str = "I am a doctor";
String oldStr = "doctor";
String newStr = "policeman";
String result = myInterface.myStrReplace(str, oldStr, newStr);
System.out.println(result);
}
方法引用的方式:
public static void main(String[] args) {
/**
* ::的左边是第一个参数对应的类
* ::的右边是调用的方法,参数的话会自动对应
*/
MyInterface myInterface = String::replace;
String str = "I am a doctor";
String oldStr = "doctor";
String newStr = "policeman";
String result = myInterface.myStrReplace(str, oldStr, newStr);
System.out.println(result);
}
构造器引用
构造器引用和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型
自定义一个User类
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
我们想要获得一个有初始值的User对象,使用Jdk内置的Supplier接口,这个接口在文章开头的推荐文章中有介绍,这里只进行使用不做介绍了。先使用Lambda表达式来完成。
public static void main(String[] args) {
Supplier<User> supplier = () -> new User();
User user = supplier.get();
}
Supplier的get方法是无参的,User拥有无参构造,通过方法引用来得到User对象,使用构造器引用实现:
public static void main(String[] args) {
Supplier<User> supplier = User::new;
User user = supplier.get();
}
再举个有参数的例子来加强理解,比如我们想要获得指定姓名和年龄的user对象,先创建函数式接口
@FunctionalInterface
public interface UserFunctionalInterface {
public User getUser(String name,Integer age);
}
使用Lambda表达式完成:
public static void main(String[] args) {
UserFunctionalInterface userFunctionalInterface = (name, age) -> new User(name, age);
User user = userFunctionalInterface.getUser("张三", 23);
}
使用构造器引用来实现:
public static void main(String[] args) {
UserFunctionalInterface userFunctionalInterface = User::new;
User user = userFunctionalInterface.getUser("张三", 23);
}
数组引用
大家可以把数组当做一个特殊的类,则写法和构造器引用是一样的
比如想要获取一个指定大小的数组,我们使用Jdk内置的Function接口来实现,这个接口也不做介绍之前文章介绍过,先用Lambda表达式来完成
public static void main(String[] args) {
// 想要一个长度为5的String数组
Function<Integer,String[]> function = (num) -> new String[num];
String[] apply = function.apply(5);
System.out.println(Arrays.toString(apply));
}
使用方法引用来实现:
public static void main(String[] args) {
// 想要一个长度为5的String数组
Function<Integer, String[]> function = String[]::new;
String[] apply = function.apply(5);
System.out.println(Arrays.toString(apply));
}
总结
方法引用、构造器引用、数组引用都是基于Lambda表达式的,方法引用大大简化了代码,同时也增加了阅读的难度,可以说不论Lambda表达式还是方法引用都是一种语法,刚开始接触的时候都难免觉得困难就好比我们刚学编程的时候,所以只要多加练习习惯这种语法就好了。