java8新特性:方法引用(双冒号)、lambda表达式

lambda表达式

重要特征

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

将以上四大特征理解就差不多了

使用

总的来说,就一个功能:实现有唯一的抽象函数的接口

  1. 在使用接口的时候,一般我们需要再写一个实现类,来实现这个接口,然后才可以调用这个方法。但是有了lambda表达式之后,我们可以不写这个实现类,直接在需要调用的地方,使用lambda表达式来实现这个方法,然后直接调用就行(前提:该接口只有一个需要实现的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //接口
    interface A {
    void t();
    }

    @Test
    public void aaa() {
    //使用lambda表达式进行实现接口中方法
    A a1 = () -> System.out.println("jjj");
    //直接调用就行
    a1.t();
    }
  2. 在集合的forEach方法中,直接使用lambda函数

    1
    2
    3
    4
    5
    @Test
    public void aaa() {
    List l = new ArrayList();
    l.forEach((a) -> System.out.println(a));
    }

    这里之所以能够使用Lambda表达式,原理其实和第1点中一样

    因为forEach方法其实是List的父类Collection的父类Iterable的一个方法

    而Iterable接口的这个forEach()的参数是Consumer类型

    而Consumer类型又只有一个抽象方法accept()

    所以此处本质上是使用lambda表达式实现这个Consumer类的唯一的抽象方法accept()

    (具体可参见JDK源码)

方法引用(::双冒号)

看了很多博客,如果想要更正确的理解,还可以参考这篇博客:(22条消息) java中的方法引用_lkforce-CSDN博客_java方法引用

(感觉这篇讲的比较清晰了img)

本博客可能会与上面那篇博客有些地方的分类不一样,但是本文看完之后,如果懂了原理,就都会懂得

首先一句话:

”::“双冒号运算符就是Java中的方法引用

方法引用的种类

方法引用有四种,分别是:

  • 指向静态方法的引用
  • 指向某个对象的实例方法的引用
  • 指向某个类型的实例方法的引用
  • 指向构造方法的引用
种类 案例 Lambda表达式写法
引用静态方法 ContainingClass::staticMethodName x -> String.valueOf(x)
对特定对象的实例方法的引用 containingObject::instanceMethodName x -> x.toString()
对特定类型的任意对象的实例方法的引用 ContainingType::methodName () -> x.toString()
对构造函数的引用 ClassName::new () -> new ArrayList<>()

注意:可以看到,::运算符调用方法的时候,都是没有写出参数列表的,但是JVM会自动地将参数进行传递

!!!此表只是用来作参考,不重要!!!

!!!此表只是用来作参考,不重要!!!

!!!此表只是用来作参考,不重要!!!

使用前提

调用的方法,必须是原本就已经存在的方法(而不是像lambda表达式那样自己再写个函数式接口来实现一个抽象方法的)

本质

在网上看到一个人见解是这样的,感觉说的很有道理“把方法引用还原成一个接口实现对象

image-20211129213132551

传参过程

(想了很久)我觉的应该是,在调用的时候,只需要外面的语句,有赋值的含义就行了(当然,这说法很不科学,仅为我个人的为了搞清楚具体的使用方法而编出来的),然后JVM就会根据 方法的形参列表外面的语句,自动进行传入参数。

案例

静态方法语法

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test() {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");

//静态方法语法 ClassName::methodName
list.forEach(Demo::print);
}

public static void print(String content){
System.out.println(content);
}

类实例方法语法

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test() {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");

//对象实例语法 instanceRef::methodName
list.forEach(new Demo()::print);
}

public void print(String content){
System.out.println(content);
}

超类方法语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Example extends BaseExample{

@Test
public void test() {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");

//对象的超类方法语法: super::methodName
list.forEach(super::print);
}
}

class BaseExample {
public void print(String content){
System.out.println(content);
}
}

类构造器语法

构造方法也是方法,构造方法引用实际上表示一个函数式接口中的唯一方法引用了一个类的构造放法,引用的是那个参数相同的构造方法

(在下面的案例中是:TargetClass::new引用了与接口ImTheOne中唯一的抽象方法的ImTheOne的参数列表一样的构造函数public TargetClass(String a){ oneString = a; },来实现这个唯一的抽象方法;所以,然后我们就调用这个被实现过的抽象的方法,来构造实例了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.wbg;

public interface ImTheOne {
TargetClass getTargetClass(String a);
}

class TargetClass {
String oneString;

public TargetClass() {
oneString = "default";
}

public TargetClass(String a) {
oneString = a;
}
}

class Test {
public static void main(String[] args) {

ImTheOne imTheOne = TargetClass::new;
TargetClass targetClass = imTheOne.getTargetClass("abc");
System.out.println(targetClass.oneString);

//相当于以下效果
ImTheOne imTheOne2 = (a) -> new TargetClass("abc");
TargetClass targetClass2 = imTheOne2.getTargetClass("123");
System.out.println(targetClass2.oneString);
}
}

温馨提示(个人见解):类构造器的用法感觉可读性真不高,所以本人是不建议使用的

方法引用的总结

经过多方整合+个人理解琢磨,终于有一点小感悟了!

img

还是那句话,双冒号运算符,也叫方法引用,本质上就是字面上的意思:方法引用

但是这里有一个过程(这个过程懂了就什么都懂了):在使用方法引用的时候,就是将被引用的方法,重新还原成一个接口实现对象,这样就即可以直接被调用(对应前面三种的语法),又能用来实现接口中的唯一的抽象方法了(也就是类构造器的语法)。如此一来的话,就什么都说的通了


补充一个方法引用语法的用途

数组引用

数组::new

数组引用算是构造器引用的一种,可以引用一个数组的构造,举个例子:

1
2
3
4
5
6
7
8
9
10
11
@FunctionalInterface
public interface ImTheOne<T> {
T getArr(int a);
}
public class Test {
public static void main(String[] args) {
ImTheOne<int[]> imTheOne = int[]::new;
int[] stringArr = imTheOne.getArr(5);
System.out.println(stringArr.length);
}
}

原理和上面讲的一样,可以花点时间看下,加深一下对方法引用的理解。

Contents
  1. 1. lambda表达式
    1. 1.1. 重要特征
    2. 1.2. 使用
  2. 2. 方法引用(::双冒号)
    1. 2.1. 方法引用的种类
      1. 2.1.1. 使用前提
      2. 2.1.2. 本质
      3. 2.1.3. 传参过程
    2. 2.2. 案例
      1. 2.2.1. 静态方法语法
      2. 2.2.2. 类实例方法语法
      3. 2.2.3. 超类方法语法
      4. 2.2.4. 类构造器语法
    3. 2.3. 方法引用的总结
  3. 3. 补充一个方法引用语法的用途
    1. 3.1. 数组引用
|