深入理解Java8双冒号::的使用

一、方法引用

java8允许我们使用lambda表达式创建匿名方法。但有时lambda表达式除了调用现有方法之外什么也不做。在这些情况下,通过名称引用现有的方法,通常能更直白的表现出方法的调用过程。对于已经存在的且具有方法名称的方法,它其实是简洁且易于读取的一种lambda表达式,或者说是对lambda表达式的一种进一步简化。

现在我们来看看下面这个“person”类:

public class Person {

  public enum Sex {
    MALE, FEMALE
  }

  String name;
  LocalDate birthday;
  Sex gender;
  String emailAddress;

  public int getAge() {
    // ...
  }

  public Calendar getBirthday() {
    return birthday;
  }  

  public static int compareByAge(Person a, Person b) {
    return a.birthday.compareTo(b.birthday);
  }}

假设你的社交网络应用程序的成员包含在一个数组中,并且你希望按年龄对数组进行排序。你可以使用以下代码:

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
  public int compare(Person a, Person b) {
    return a.getBirthday().compareTo(b.getBirthday());
  }
}

Arrays.sort(rosterAsArray, new PersonAgeComparator());

上面最后一行代码:中的sort方法源码如下:

static <T> void sort(T[] a, Comparator<? super T> c)

注意,接口comparator是一个功能接口。因此,你可以使用lambda表达式,而不是定义并创建实现comparator的类的新实例,所以上上面的代码可以用lambda表达式改写成下面这样:

Arrays.sort(rosterAsArray,  (Person a, Person b) -> {    return a.getBirthday().compareTo(b.getBirthday());  });

但是,我们已经在Person bean对象中提前写好了用来比较两个person对象的出生日期的方法:,所以你其实可以在lambda表达式的主体中直接调用这个方法方法:

Arrays.sort(rosterAsArray,
  (Person a, Person b) -> {
    return a.getBirthday().compareTo(b.getBirthday());
  }
);

因为这个上面这个lambda表达式是在调用现有的方法,所以我们这里就可以使用上面提到的使用方法引用方式(及双冒号 ::),而不是之前我们熟悉的lambda表达式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用 person::comparebyage 在语义上与lambda表达式(a,b)->person.comparebyage(a,b)相同。他们都有以下特点:

  • 它的形参列表复制自comparator<person>.compare,即(Person, Person)
  • 它的主体调用方法是:person.comparebyage。

二、方法引用的种类(哪些场景可以使用方法引用)

有四种方法引用:


种类

案例
引用静态方法 ContainingClass::staticMethodName
对特定对象的实例方法的引用 containingObject::instanceMethodName
对特定类型的任意对象的实例方法的引用 ContainingType::methodName
对构造函数的引用 ClassName::new

1、引用静态方法

例如:

Person::comparebyage 是对Person类的静态方法 comparebyage 的引用。

2、引用特定对象的实例方法

以下是对特定对象的实例方法的引用示例:

class ComparisonProvider {
  public int compareByName(Person a, Person b) {
    return a.getName().compareTo(b.getName());
  }

  public int compareByAge(Person a, Person b) {
    return a.getBirthday().compareTo(b.getBirthday());
  }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用   myComparisonProvider::compareByName 调用对象myComparisonProvider的一部分方法 compareByName。然后JRE会自动推断方法类型参数,在这种情况下为(Person, Person)

3、引用特定类型的任意对象的实例方法

以下是对特定类型的任意对象的实例方法的引用示例:

String[] stringArray = {
  "Barbara", "James", "Mary", "John",
  "Patricia", "Robert", "Michael", "Linda"
 };

Arrays.sort(stringArray, String::compareToIgnoreCase);

方法引用  string::compareTogignoreCase  的等效lambda表达式会有形参列表(String a,String b),其中a和b是用于更好地描述此示例的任意名称。这次的方法引用,将调用方法a.CompareTognoreCase(b)。

这里官网原文可能说的不是很清楚,也有点拗口,多说一句:

如何理解:对特定类型的 任意对象的 实例方法的引用  这句话呢?

实际上上面代码没有在特定实例(也就是没有new一个指定的String 实例出来,像这样:String s = new String() ,s就是一个指定的String对象的实例)上引用方法 compareTogignoreCase ,而是在String类自身引用的。

我们进入Arrays.sort(xx,xx)方法源码,我们可以看到这个数组排序方法底层算法涉及循环,那么这行代码我们可以想象它会在每循环到数组的一个String元素时,以这个String元素所属类型对象(其实就是String对象)的身份去调用compareTogignoreCase方法,即:

会调用的compareTogignoreCase   --》String Barbara = new String(); Barbara.compareTogignoreCase

会调用的compareTogignoreCase  --》String James= new String();James.compareTogignoreCase

以此类推。。。

我可能解释的也不太好,主要就是想语言上理解一下:对特定类型的 任意对象的 实例方法 的引用

4、引用构造函数

你可以通过使用name new 的方式引用构造函数,这与静态方法的引用方式类似。

下面这个示例方法,是将元素从一个集合复制到另一个集合。我们将以这个方法为例,讲解如何使用方法引用的方式引用构造函数。

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
  DEST transferElements(SOURCE sourceCollection,Supplier<DEST> collectionFactory) {

    DEST result = collectionFactory.get();

    for (T t : sourceCollection) {
      result.add(t);
    }

    return result;
}

函数接口supplier包含一个无参但返回一个对象的get方法,源码可见,他返回的类型就是泛型的类型,如下:

因此,可以使用lambda表达式调用方法transferElements,如下所示:

Set<Person> rosterSetLambda =
  transferElements(roster, () -> { return new HashSet<>(); });

可以使用构造函数引用代替lambda表达式,如下所示:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

上面代码,Java编译器执行的时候会自动推断出这里想要创建一个包含Person类型元素的hashset集合。当然你也可以显示的指定类型,如下:

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

到此这篇关于深入理解Java8双冒号::的使用的文章就介绍到这了,更多相关Java8双冒号::内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java lambda 表达式中的双冒号的用法说明 ::

    双冒号运算就是Java中的[方法引用],[方法引用]的格式是 类名::方法名 注意是方法名哦,后面没有括号"()"哒.为啥不要括号,因为这样的是式子并不代表一定会调用这个方法.这种式子一般是用作Lambda表达式,Lambda有所谓懒加载嘛,不要括号就是说,看情况调用方法. 例如 表达式: person -> person.getAge(); 可以替换成 Person::getAge 表达式 () -> new HashMap<>(); 可以替换成 HashMa

  • 浅谈对Java双冒号::的理解

    本文为个人理解,不保证完全正确. 官方文档中将双冒号的用法分为4类,按照我的个人理解可以分成2类来使用. 官方文档 官方文档中将双冒号的用法分为了以下4类: 用法 举例 引用静态方法 ContainingClass::staticMethodName 引用特定对象的实例方法 containingObject::instanceMethodName 引用特定类型的任意对象的实例方法 ContainingType::methodName 引用构造函数 ClassName::new 以下是我的理解 个

  • java8中:: 用法示例(JDK8双冒号用法)

    JDK8中有双冒号的用法,就是把方法当做参数传到stream内部,使stream的每个元素都传入到该方法里面执行一下. 代码其实很简单: 以前的代码一般是如此的: public class AcceptMethod { public static void printValur(String str){ System.out.println("print value : "+str); } public static void main(String[] args) { List al

  • 深入理解Java8双冒号::的使用

    一.方法引用 java8允许我们使用lambda表达式创建匿名方法.但有时lambda表达式除了调用现有方法之外什么也不做.在这些情况下,通过名称引用现有的方法,通常能更直白的表现出方法的调用过程.对于已经存在的且具有方法名称的方法,它其实是简洁且易于读取的一种lambda表达式,或者说是对lambda表达式的一种进一步简化. 现在我们来看看下面这个"person"类: public class Person { public enum Sex { MALE, FEMALE } Str

  • Java双冒号(::)运算符使用详解

    目录 1.说明 2.先来说下@FunctionalInterface 3. 下面来讲讲这个 "::"是干嘛的 4. 建立一个Person类 4:构建多个person对象,放入数组中,然后对数组中的person重新排序 5:揭秘 "::"符号 6.0 方法引用的支持如下 1.说明 之前没用过::这个东西,今天看flink的时候发现官网有个例子用到了这个符号, 本着求知欲去百度查了一番,没找到能说到我心里去的解释,本着求知欲的态度,我去了官网看了看. java :: 2

  • 双冒号 ::在PHP中的使用情况

    前几天在百度知道里面看到有人问PHP中双冒号::的用法,当时给他的回答比较简洁因为手机打字不大方便!今天突然想起来,所以在这里总结一下我遇到的双冒号::在PHP中使用的情况! 双冒号操作符即作用域限定操作符Scope Resolution Operator可以访问静态.const和类中重写的属性与方法. 在类定义外使用的话,使用类名调用.在PHP 5.3.0,可以使用变量代替类名. Program List:用变量在类定义外部访问 <?php class Fruit { const CONST_

  • c++中冒号(:)和双冒号(::)的使用说明

    (1)表示机构内位域的定义(即该变量占几个bit空间) 复制代码 代码如下: typedef struct _XXX{          unsigned char a:4;          unsigned char c;} ; XXX (2)构造函数后面的冒号起分割作用,是类给成员变量赋值的方法,初始化列表,更适用于成员变量的常量const型. 复制代码 代码如下: struct _XXX{          _XXX() : y(0xc0) {}}; (3) public:和privat

  • Kotlin中双冒号::使用方法

    Kotlin 中 双冒号操作符 表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法.先来看一下例子: fun main(args: Array<String>) { println(lock("param1", "param2", ::getResult)) } /** * @param str1 参数1 * @param str2 参数2 */ fun getResult(str1: String, str2: Stri

  • C++中双冒号::的作用浅析

    C++中经常使用的作用符::,作用如下: •作用域限定符.在类体内申明函数,并在类体外定义函数时,必须在定义函数时加上类名和作用域限定符. class MyClass{ public: int num; int fun();//类体内申明函数 }; //类体外定义函数 int MyClass::fun(){ return 1; } •静态数据和静态成员函数成员既可以通过对象名引用,也可以通过类名加::来引用.例如,MyClass::a.静态成员函数的目的就是为了操作静态数据成员,静态成员函数引用

  • 详解C++中的双冒号 ::

    C++中的双冒号 ::第一种,类作用域,用来标明类的变量.函数 Human::setName(char* name); 第二种,命名空间作用域,用来注明所使用的类.函数属于哪一个命名空间的 std::cout << "Hello World" << std::endl; 第三种,全局作用域,用来区分局部.全局的.最容易被忽视的一种,很多时候写了一个全局函数或者想要调用一个全局函数,却发现IDE或者Editor找不到该函数,原因是因为局部函数与想要调用的全局函数名

随机推荐