JDK8辅助学习(三):Lambda表达式----方法引用(JDK8 :: 双冒号的使用)

方法引用由来

      从 JDK8新特性 一文 『Lambda表达式』模块,当我们学习了 1.Lambda表达式  2.Lambda表达式原理分析  3.Lambda表达式----常用的内置函数式接口 ,学习了 Lambda 表达式以及它的简写形式,并使用 Lambda 表达式可以对代码进行简化。 但是 Lambda 表达式在某些情况下还是有可能会出现代码冗余的情况。

      下面我们就来举一个栗子:使用 Lambda 表达式来求一个数组的和

public class MethodRefDemo{

    public static void main(String[] args){
        int resSum = printSum((int[] arr)->{
            int sum = 0;
            for(int n : arr){
                sum += n;
            }
            return sum;
        });
        System.out.println("数组求和:"+resSum);
    }

    //使用 Lambda表达式 编写的printSum()方法
    public static int printSum(Function<int[],Integer> function){
        int[] arr = {11,22,33,44,55,66};
        return function.apply(arr);
    }

    //数组求和方法 getSum()
    public static int getSum(int[] arr){
        int sum = 0;
        for(int n : arr){
            sum += n;
        }
        return sum;
    }

}

       我们分析发现,类中已经有一个 getSum() 方法可以用来为数组求和,但是此处为了使用 Lambda 表达式,迫于Lambda表达式使用的前提:①参数必须是抽象方法 ②接口类只有一个抽象方法。显然getSum()方法不能满足该条件,所以我们需要重新编写一个 printSum()方法,参数为 Function 函数式接口。

       但是,使用 Lambda 表达式在调用 printSum()  方法时,就需要对数组求和方法在 Lambda 表达式中再重新写一次,显然这个求和的方法体已经和 getSum()方法有所重复。此处显然是冗余情况

       针对这种冗余情况,你会说为什么不直接调用 getSum() 方法呢?是的,可以直接调用 getSum() 方法,如下所示

public class MethodRefDemo{

    public static void main(String[] args){
        int resSum = printSum(arr->{
            //此处直接调用 getSum()方法
            return getSum(arr);
        });
        System.out.println("数组求和:"+resSum);
    }
   //部分重复代码省略(printSum()、getSum())
}

       我们再来分析,printSum()方法处 Lambda 表达式除了调用 getSum()方法,又啥都没做。所以在此处 Lambda 表达式代码显然也是冗余的。接下来我们就引入了 方法引用 概念

方法引用

   继续修改代码后,方法引用(使用 :: 来表示)如下:

public class MethodRefDemo{

    public static void main(String[] args){
        int resSum = printSum(MethodRefDemo::getSum);
        System.out.println("数组求和:"+resSum);
    }
    //部分重复代码省略(printSum()、getSum())
}

      请注意其中的双冒号 :: 写法,这被称为"方法引用",是一种新的语法。

方法引用的格式

符号表示:双冒号(::)

符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用

应用场景:如果 Lambda 所要实现的方案,已经在其他方法存在相同方案,那么则可以使用方法引用。

常见引用方式

方法引用在 JDK8 中使用方式相当灵活,有如下几种形式:

 1.instanceName::methodName    对象名::方法名

 2.ClassName::staticMethodName    类名::静态方法名

 3.ClassName::methodName    类名::方法名

 4.ClassName::new    类名::new   

 5.TypeName[ ]::new    数组类型::new  (String[]::new)

1.对象名::方法名

     这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名::方法名的方式调用

public class InstanceName_methodName_demo {

    public static void main(String[] args) {
        Date now = new Date();

        //Lambda 普通写法(调用方法)
        Supplier<Long> supplier1 = ()->{
            return now.getTime();
        };
        Long aLong = supplier1.get();
        System.out.println("普通写法:"+aLong);

        //Lamdba 方法引用(对象名::成员方法名)
        Supplier<Long> supplier2 = now::getTime;
        Long bLong = supplier2.get();
        System.out.println("方法引用写法:"+bLong);

        //使用 方法引用 来修改Time
        Consumer<Long> consumer = now::setTime;
        consumer.accept(100000000000L);
        Supplier<Long> supplier = now::getTime;
        Long aLong1 = supplier.get();
        System.out.println("修改后的Time:"+aLong1);

    }
}

   注意事项:

  1. 被引用的方法,参数要和接口中抽象方法的参数一样;(即:getTime() 方法无参,Supplier接口抽象方法get()方法也无参)
  2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值。      

2.类名::静态方法名

       由于在 java.lang.System 类中已经存在了静态方法 currentTimeMillis(),所以当我们需要通过 Lambda 表达式来调用该方法时,可以使用方法引用。通过类名::静态方法名的方式来调用

public class className_abstractMethodName_demo {

    public static void main(String[] args) {

        //Lambda普通写法(类名.静态方法名)
        Supplier<Long> supplier1 = ()->{
            return System.currentTimeMillis();
        };

        Long aLong = supplier1.get();
        System.out.println("普通调用:"+aLong);

        //Lambda 方法引用
        Supplier<Long> supplier2 = System::currentTimeMillis;
        Long bLong = supplier2.get();
        System.out.println("方法引用:"+bLong);
    }
}

3.类名::方法名

       在Java面向对象中,类名是只能调用静态方法的。此处使用类名::方法名是有前提的,实际上是拿第一个参数作为方法的调用者。看如下代码先了解一下你就明白了。

Demo:我们现在来计算字符串的长度。

public class className_methodName_demo {

    public static void main(String[] args) {
        
        //Lambda 表达式(使用 对象.方法名 形式调用)
        Function<String,Integer> fn1 = (String str)->{
            return str.length();
        };
        Integer aLength = fn1.apply("hello world");
        System.out.println("字符长度为:"+aLength);
        
        //Lambda 表达式(使用 方法引用【类名::方法名】方式调用)
        Function<String,Integer> fn2 = String::length;
        Integer bLength = fn2 .apply("hello world"); 
        System.out.println("字符长度为:"+bLength);

    }
}

分析一下:

       如果还没有看懂,那就来看下面练习:自定义方法,如何来使用【类名::方法名】方法引用。看完下面练习,你就能够了解如何使用【类名::方法名】这种方式的方法引用了。

练习(步骤分析):

/**
 * TODO 实例:自定义方法 (由繁到简)一步步分析
 *      从 1.Lambda表达式  ---> 2.对象名::方法名 --->  3.类名::方法名 形式 一步步分析
 *
 * @author liuzebiao
 * @Date 2020-1-6 15:38
 */
public class Demo01 {
    
    //数组求和方法
    public int getSum(int[] arr){
        int sum = 0;
        for(int n : arr){
            sum += n;
        }
        return sum;
    }

    public static void main(String[] args) {
        //自定义一个数组求和方法 getSum(),本例使用【类名::方法名形式】方法引用方式
        //分析:调用getSum()方法,传入一个 int[]类型参数,返回一个Integer值,此处用到 Function<int[],Integer> 函数式接口

        //步骤:
        //1.这种方式显然可以,但是Lambda表达式是冗余的
        Demo01 demo = new Demo01();
        Function<int[],Integer> f1 = (int[] arr)->{
            return demo.getSum(arr);
        };

        //2.我们可以使用方法引用   demo::getSum;【对象名::方法名】
        Function<int[],Integer> f2 = demo::getSum;

        //3.上面2中使用的是 对象名::成员方法,自己写的方法怎么使用【类名::方法名】方式
        // 分析:此时就得用到 BiFunction 函数式接口了。BiFunction 类似于 Function 函数式接口
        // 差别在于参数个数不同:BiFunction 传递2个参数,Function传递1个参数

        // 为什么需要传递两个参数呢?
        // 通过刚才图片分析: 2=1.3方法。此处可以理解为【Integer =Demo01.getSum(int[])】这种格式
        // 所以如果使用【类名::方法名】这种方式,第一个参数必须是当前类名,剩下才是传递的参数和返回参数类型
        // 如果分析用2个参数,使用【类名::方法名】这种方式,则需要传3个参数,JDK8帮我们提供了BiFunction/BiConsumer/BiPredicate这3种

        BiFunction<Demo01, int[], Integer> f3 = Demo01::getSum;
        int[] arr = {11, 22, 33, 44, 55};
        // 走到此处,调用apply()方法传参时,此处还得传递一个当前类的实例(new Demo01() new实例/Demo01.class.newInstance()反射等方式)
        // 显然还不如直接使用 上面 2 中的【对象名::方法名】这种方式呢。所以【类名::方法名】这种方式很少会用到
        // 所以 BiFunction/BiConsumer/BiPredicate这3种很少用到
        Integer sum = f3.apply(new Demo01(), arr);
        System.out.println(sum);
    }
}

4.类名::new(构造器引用)

      由于构造器的名称与类名完全一样,所以构造器引用使用 【类名称::new构造器】的格式表示。

  Demo:根据构造器引入,创建 Person 对象

/**
 * TODO person实体类
 *
 * @author liuzebiao
 * @Date 2020-1-7 15:47
 */
public class Person {

    private String  name;

    private int age;

    public Person() {
        System.out.println("执行无参构造方法");
    }

    public Person(String name, int age) {
        System.out.println("执行有参构造方法,name:"+name+";age:"+age);
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
/**
 * TODO 构造器引入Demo
 *
 * @author liuzebiao
 * @Date 2020-1-7 15:49
 */
public class className_new_constructor {

    public static void main(String[] args) {

        //1.使用对象无参构造器,创建一个 Person 对象
        //1.1 使用 Lambda 表达式
        Supplier<Person> sup1 = ()->{
            return new Person();
        };
        Person person1 = sup1.get();
        System.out.println(person1);

        //1.2 使用方法引用【类名::new(构造器)】方式
        Supplier<Person> sup2 = Person::new;
        Person person2 = sup2.get();
        System.out.println(person2);

        //2.使用对象有参构造器,创建一个 Person 对象
        //此种情况会用到 BiFunction 函数式接口(前两个参数对应构造器参数类型,第三个参数为返回值类型)
        //2.1 使用 Lambda 表达式
        BiFunction<String,Integer,Person> bif1 = (String name,Integer age)->{
            return new Person(name,age);
        };
        Person person3 = bif1.apply("Mary", 18);
        System.out.println(person3);

        //2.2 使用方法引用【类名::new(构造器)】方式
        BiFunction<String,Integer,Person> bif2 = Person::new;
        Person person4 = bif2.apply("Mary", 18);   //它会根据 apply()方法传入的参数来判断调用哪个构造器
        System.out.println(person4);
    }
}

使用前提:

       构造器参数列表要与接口中抽象方法的参数列表一致!

疑点:

       针对大于2个参数的构造器(暂不清楚如何使用构造器引入,或许JDK就没提供吧,毕竟参数太多没意义,哈哈),暂时还是先用咱最传统的new Person(xxx,xxx,xxx)方式吧。

5.数组类型::new(数组引入)

    你会纳闷:数组也有构造器?其实数组也是Object的子类,同样也是有构造器的,只是语法稍有不同。

  Demo:根据数组引入,创建 一个数组

/**
 * TODO 数组引用 创建数组
 *
 * @author liuzebiao
 * @Date 2020-1-7 16:39
 */
public class array_new_demo {

    public static void main(String[] args) {
        
        //1.使用 Lambda 表达式,创建一个长度为10的int数组
        Function<Integer,int[]> fn1 = (Integer num)->{
            return new int[num];
        };
        int[] arr1 = fn1.apply(10);
        System.out.println(Arrays.toString(arr1));

        //2.使用 数组引用 方式,创建一个长度为10的int数组
        Function<Integer,int[]> fn2 = int[]::new;
        int[] arr2 = fn2.apply(10);
        System.out.println(Arrays.toString(arr2));
    }
}

总结

        方法引用是对 Lambda表达式符合特定情况下的一种缩写,它使得我们的 Lambda表达式更加的精简,也可以理解为 Lambda 表达式的缩写形式,不过要注意的是方法引用只能"引用" 已经存在的方法。


附:JDK8新特性(目录)

    本目录为 JDK8新特性 学习目录,包含JDK8 新增全部特性的介绍。

    如需了解,请跳转链接查看:我是跳转链接


方法引用(JDK8 :: 双冒号的使用),就介绍到这里了

如果本文对你有所帮助,那就给我点个赞呗 ^_^ 

End

发布了247 篇原创文章 · 获赞 44 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/lzb348110175/article/details/103868123