JDK8新特性之方法引用(二)

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改造成使用方法引用,这里需要注意之前提到过的使用方法引用的前提是否满足:

  1. 在Lambda体也就是->的右边,已经有实现的方法了,也就是说我们能够直接调用println方法实现打印的目的而这个println方法属于已经实现过的方法属于PrintStream类
  2. 我们所调用的已经实现的方法对应的参数和返回值必须和我们自定义的那个抽象方法一致,也就是我们自己定义的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表达式还是方法引用都是一种语法,刚开始接触的时候都难免觉得困难就好比我们刚学编程的时候,所以只要多加练习习惯这种语法就好了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容