Java 8 - 05 方法引用

简介: Java 8 - 05 方法引用

20200510181139786.png


Pre

先来看段代码

  Comparator<Enginner> enginnerComparator = new Comparator<Enginner>() {
            @Override
            public int compare(Enginner o1, Enginner o2) {
                return o1.getJob().compareTo(o2.getJob());
            }
        };
 enginnerComparator.compare(new Enginner("Java",18),new Enginner("Go",10));
 List<Enginner> enginnerList = Arrays.asList(new Enginner("Java",18),new Enginner("Go",10));
 System.out.println("0enginnerList:" + enginnerList);
 enginnerList.sort(enginnerComparator);
 System.out.println("1enginnerList:" + enginnerList);


排序嘛 ,是不是很长


使用方法引用,一行代码搞定,如下:

 enginnerList.sort(Comparator.comparing(Enginner::getJob));
 System.out.println("2enginnerList:" + enginnerList);

20200517085324163.png


方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下比起使用Lambda表达式, 更易读 。上面的栗子就是借助了Java 8 API ,用方法引用写的一个排序的例子。


方法引用


方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。


实际上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。 显式地指明方法的名称,代码的可读性会更好 。

当你需要使用方法引用时,目标引用放在分隔符 :: 前,方法的名称放在后面

Enginner::getJob

就是引用了 Enginner类中定义的方法 getJob 。 请记住,不需要括号,因为你没有实际调用这个方法。

方法引用就是Lambda表达式 (Enginnera) -> a.getJob() 的快捷写法


再来看几个等效的例子 加深下印象

(Enginner a) -> a.getJob()    等价于  Enginner ::getJob


做下实验

public class MethodReferrenceDemo {
    public static <T, R> R doSomething(T t, Function<T, R> f) {
        return f.apply(t);
    }
    public static void main(String[] args) {
        Function<Enginner, String> ef = (Enginner e) -> e.getJob();
        System.out.println(doSomething(new Enginner("Java", 18), ef));
        Function<Enginner, String> ef2 = Enginner::getJob;
        System.out.println(doSomething(new Enginner("Java", 18), ef2));
    }
}


20200517091203167.png

还比如

() -> Thread.currentThread().dumpStack()  等价于 Thread.currentThread()::dumpStack
(str, i) -> str.substring(i)  等价于  String::substring
(String s) -> System.out.println(s)  等价于  System.out::println


我们可以把方法引用看作针对仅仅涉及单一方法的Lambda的语法糖,因为你表达同样的事情

时要写的代码更少了。


如何构建方法引用

方法引用主要有三类。


指向静态方法的方法引用

举个例子 : Integer 的 parseInt 方法,写作 Integer::parseInt

        Function<String, Integer> stringIntegerFunction2 = (String s) -> Integer.parseInt(s);
        Function<String, Integer> stringIntegerFunction3 = Integer::parseInt;
        System.out.println(stringIntegerFunction2.apply("18"));
        System.out.println(stringIntegerFunction3.apply("18"));


指向任意类型实例方法的方法引用

举个例子 : String 的 length 方 法 , 写 作 String::length

  Function<String, Integer> stringIntegerFunction = (String s) -> s.length();
  Function<String, Integer> stringIntegerFunction1 = String::length;
  System.out.println(stringIntegerFunction.apply("abc"));
  System.out.println(stringIntegerFunction1.apply("abc"));


类似于 String::length 方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。


例如,Lambda表达式 (String s) -> s.toUppeCase() 可以写作 String::toUpperCase


指向现有对象的实例方法的方法引用


假设你有一个局部变量 eng用于存放 Enginner 类型的对象,它支持实例方法 getValue ,那么你就可以写 eng::getValue


这种写法是我们在Lambda中调用一个已经存在的外部对象中的方法。 例如,Lambda表达式()->eng.getValue() 可以写作 eng::getValue 。


再来看几个例子, 将Lambda表达式重构为等价的方法引用

lambda :   args -> ClassName.staticMethod(args)
等价于  (1)
方法引用:  className::staticMethod
lambda :   (arg0 ,rest)-> argo.instanceMethod(rest)   (arg0是ClassName类型的)
等价于   (2)
方法引用:  ClassName::instanceMethod
lambda :   (args)-> expr.instanceMethod(args)    
等价于
方法引用:  expr::instanceMethod

请注意,还有针对构造函数、数组构造函数和父类调用(super-call)的一些特殊形式的方法引用。


比方说你想要对一个字符串的 List 排序,忽略大小写。 List 的 sort 方法需要一个 Comparator 作为参数 。 我们知道 Comparator 描述了 一个具有 (T, T) -> int 签名的函数描述符。你可以利用 String 类中的 compareToIgnoreCase方法来定义一个Lambda表达式。

 List<String> list = Arrays.asList("apple","pear","banana");
 list.sort((s1,s2)-> s1.compareToIgnoreCase(s2));

Lambda表达式的签名与 Comparator 的函数描述符兼容。利用前面所述的方法,这个例子可以使用方法引用改成下面的样子

list.sort(String::compareToIgnoreCase);


请注意,编译器会进行一种与Lambda表达式类似的类型检查过程,来确定对于给定的函数式接口,这个方法引用是否有效:方法引用的签名必须和上下文类型匹配

来个小测验吧

测验:方法引用
下列Lambda表达式的等效方法引用是什么?
(1) Function<String, Integer> stringToInteger =
(String s) -> Integer.parseInt(s);
(2) BiPredicate<List<String>, String> contains =
(list, element) -> list.contains(element);
答案如下。
(1) 这个Lambda表达式将其参数传给了 Integer 的静态方法 parseInt 。这种方法接受一
个需要解析的 String ,并返回一个 Integer 。
因此,可以使用 Lambda表达式调用静态方法来重写Lambda表达式,如下所示:
Function<String, Integer> stringToInteger = Integer::parseInt;
(2) 这个Lambda使用其第一个参数,调用其 contains 方法。由于第一个参数是 List 类型
的,你可以使用刚才的(2) 如下:
BiPredicate<List<String>, String> contains = List::contains;
这是因为,目标类型描述的函数描述符是  (List<String>,String) -> boolean ,而
List::contains 可以被解包成这个函数描述符。



构造函数引用


对于一个现有构造函数,我们可以利用它的名称和关键字 new 来创建它的一个引用:ClassName::new 。它的功能与指向静态方法的引用类似。


例如,假设有一个构造函数没有参数。它适合 Supplier 的签名 () -> Enginner。

Enginner的构造函数

20200517213320680.png


我们可以这样做:

    // 构造函数引用指向默认Enginner的构造函数
     Supplier<Enginner> supplier = Enginner::new;
  // 调用 Supplier 的 get 方法 将产生一个新的 enginner 
     Enginner enginner = supplier.get();


等价于

    Supplier<Enginner> s = () -> new Enginner();// 利用默认构造函数创建 Enginner的Lambda表达式
    Enginner supplier2 = s.get();// 调用 Supplier 的 get 方法 将产生一个新的 Enginner 


如果Enginner构造函数的签名是 Enginner(String job) ,那么它就适合 Function 接口的签

名,我们可以这样写:

    Supplier<Enginner> s = () -> new Enginner();// 利用默认构造函数创建 Enginner的Lambda表达式
    Enginner supplier2 = s.get();// 调用 Supplier 的 get 方法 将产生一个新的 Enginner 


如果Enginner构造函数的签名是 Enginner(String job) ,那么它就适合 Function 接口的签

名,我们可以这样写:

// 指向 Enginner(String job) 的构造函数引用
  Function<String ,Enginner> f2 =  Enginner::new; 
// 调用该 Function 函数的 apply 方法,并给出职位,将产生一个 Enginner 
  Enginner e2 =f2.apply("Java");
  System.out.println(e2.getJob());


如果你有一个具有两个参数的构造函数 Enginner(String job, Integer age) ,那么

它就适合 BiFunction 接口的签名 . 两个参数的 使用BiFunction 即可 (Bi = Binary )

  BiFunction<String ,Integer,Enginner> f3 = Enginner::new;
  Enginner e4 = f3.apply("Java",18);
  System.out.println(e4 .getJob() + " - " +  e4.getAge());


上面是使用方法引用,如果直接用lambda呢? 如下

     BiFunction<String,Integer,Enginner> f4 = (str , age)-> new Enginner(str, age);
    Enginner ee =f4.apply("Go", 10);
    System.out.println(ee .getJob() + " - " +  ee.getAge());

20200517214621383.png


自定义构造函数引用


上面的栗子我们将有零个、一个、两个参数的构造函数转变为构造函数引用。那要怎么样才能对具有三个参数的构造函数,比如 Color(int, int, int), 使用构造函数引用呢?


构造函数引用的语法是 ClassName::new ,那么在这个例子里面就是Color::new 。但是你需要与构造函数引用的签名匹配的函数式接口。但是语言本身并没有提供这样的函数式接口,你可以自己创建一个:

public interface TriFunction<T, U, V, R>{
    R apply(T t, U u, V v);
}

现在你可以像下面这样使用构造函数引用了:

TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new;


目录
打赏
0
0
0
0
99
分享
相关文章
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
3月前
|
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
114 9
Java快速入门之数组、方法
### Java快速入门之数组与方法简介 #### 一、数组 数组是一种容器,用于存储同种数据类型的多个值。定义数组时需指定数据类型,如`int[]`只能存储整数。数组的初始化分为静态和动态两种: - **静态初始化**:直接指定元素,系统自动计算长度,如`int[] arr = {1, 2, 3};` - **动态初始化**:手动指定长度,系统给定默认值,如`int[] arr = new int[3];` 数组访问通过索引完成,索引从0开始,最大索引为`数组.length - 1`。遍历数组常用`for`循环。常见操作包括求和、找最值、统计特定条件元素等。
Java容器及其常用方法汇总
Java Collections框架提供了丰富的接口和实现类,用于管理和操作集合数据。
Java容器及其常用方法汇总
|
11天前
|
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
45 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
9天前
|
java.time常用方法汇总
`java.time` API 是从 Java 8 开始引入的时间日期处理库,旨在替代老旧的 `java.util.Date` 和 `Calendar`。它提供了更简洁、强大和灵活的方式处理日期、时间、时区及时间间隔,支持全球化和时间计算需求。API 包含获取当前时间、创建指定时间、解析和格式化字符串、进行加减运算、比较时间、获取年月日时分秒、计算时间间隔、时区转换以及判断闰年等功能。示例代码展示了如何使用这些功能,极大简化了开发中的时间处理任务。
Java快速入门之类、对象、方法
本文简要介绍了Java快速入门中的类、对象和方法。首先,解释了类和对象的概念,类是对象的抽象,对象是类的具体实例。接着,阐述了类的定义和组成,包括属性和行为,并展示了如何创建和使用对象。然后,讨论了成员变量与局部变量的区别,强调了封装的重要性,通过`private`关键字隐藏数据并提供`get/set`方法访问。最后,介绍了构造方法的定义和重载,以及标准类的制作规范,帮助初学者理解如何构建完整的Java类。
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
62 9
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
68 12
Java 方法注释:规范、实用和高质量的写法
本文深入探讨了如何编写高质量的 Java 方法注释
63 11

热门文章

最新文章