跳至主要內容

Java8回顾

codejava新特性约 2859 字大约 10 分钟

Java8

Lambda 表达式

匿名内部类

在Java 8之前,我们在某些情况下可能需要用到匿名内部类,比如:

public static void main(String[] args) {
    //现在我们想新建一个线程来搞事情
    Thread thread = new Thread(new Runnable() {   
        //创建一个实现Runnable的匿名内部类
        @Override
        public void run() {   //具体的实现逻辑
            System.out.println("Hello World!");
        }
    });
    thread.start();
}

在创建Thread时,我们需要传入一个Runnable接口的实现类,来指定具体的在新的线程中要执行的任务,相关的逻辑需要我们在run()方法中实现,这时为了方便,我们就直接使用匿名内部类的方式传入一个实现,但是这样的写法实在是太过臃肿了。

Lambda 使用

在Java 8之后,我们可以对类似于这种匿名内部类的写法,进行缩减。

真正有用的那一部分代码,实际上就是我们对run()方法的具体实现,而其他的部分实际上在任何地方编写都是一模一样的,现在只需要一个简短的lambda表达式即可:

public static void main(String[] args) {
    //现在我们想新建一个线程来做事情
    Thread thread = new Thread(() -> {
        System.out.println("Hello World!");  //只需留下我们需要具体实现的方法体
    });
    thread.start();
}

即 原本需要完整编写包括类、方法在内的所有内容,全部不再需要,而是直接使用类似于() ‐> { 代码语句 }的形式进行替换即可。

这只是一种写法而已,如果各位不好理解,可以将其视为之前匿名内部类写法的一种缩短。

但是注意,它的底层其实并不只是简简单单的语法糖替换,而是通过invokedynamic指令实现的

匿名内部类会在编译时创建一个单独的class文件,但是lambda却不会,间接说明编译之后lambda并不是以匿名内部类的形式存在的:

//现在我们想新建一个线程来做事情
Thread thread = new Thread(() -> {
    throw new UnsupportedOperationException();   
    //这里我们拋个异常看看
});
thread.start();
alt text
alt text

可以看到,实际上是Main类中的lambda$main$0()方法抛出的异常,但是我们的Main类中压根没有这个方法,很明显是自动生成的。 所以,与其说Lambda是匿名内部类的语法糖,不如说是我们为所需要的接口提供了一个方法作为它的实现。 比如Runnable接口需要一个方法体对它的run()方法进行实现,而这里我们就通过lambda的形式给了它一个方法体,这样就万事具备了,而之后创建实现类就只需要交给JVM去处理就好了。

Lambda 具体规范

Lambda表达式的具体规范:

  • 标准格式为:([参数类型 参数名称,]...) ‐> { 代码语句,包括返回值 }
  • 和匿名内部类不同,Lambda仅支持接口,不支持抽象类
  • 接口内部必须有且仅有一个抽象方法(可以有多个方法,但是必须保证其他方法有默认实现,必须留一个抽象方法出来)

Java中接口的方法默认是 public abstract, 变量默认是 public static final

比如我们之前使用的Runable类:

@FunctionalInterface   
//添加了此注解的接口,都支持lambda表达式,符合函数式接口定义
public interface Runnable {
    public abstract void run();   
    //有且仅有一个抽象方法,此方法返回值为void,且没有参数
}

因此,Runable的的匿名内部类实现,就可以简写为:

Runnable runnable = () -> {    };

我们也可以写一个:

@FunctionalInterface
public interface Test {   
  //接口类型
    String test(Integer i);    
    //只有这一个抽象方法,且接受一个int类型参数,返回一个String类型结果
}

它的Lambda表达式的实现就可以写为:

Test test = (Integer i) -> { return i+""; };  //这里我们就简单将i转换为字符串形式

不过还可以进行优化,首先方法参数类型是可以省略的:

Test test = (i) -> { return i+""; };

由于只有一个参数,可以不用添加小括号(多个参数时需要):

Test test = i -> { return i+""; };

由于仅有返回语句这一行,所以可以直接写最终返回的结果,并且无需花括号:

Test test = i -> i+"";
应用现有的方法函数作为方法体 (方法引用)

Lambda 本质是:我们为所需要的接口提供了一个方法作为它的实现

除了我们手动编写接口中抽象方法的方法体之外,如果已经有实现好的方法,是可以直接拿过来用的,比如:

String test(Integer i);   //接口中的定义
public static void main(String[] args) {
    Test test = Main::impl;    
    //使用 类名::方法名称 的形式来直接引用一个已有的方法作为实现
}

public static String impl(Integer i){
    return "我是已经存在的实现"+i;
}

所以,我们可以直接将此方法,作为lambda表达式的方法体实现(其实这就是一种方法引用,引用了一个方法过来)

方法引用 举例

比如我们现在需要对一个数组进行排序:

public static void main(String[] args) {
    Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};   //来个数组
    Arrays.sort(array, new Comparator<Integer>() {   //Arrays.sort()可以由我们自己指定排序规则,只需要实现Comparator方法即可
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    });
    System.out.println(Arrays.toString(array));   //按从小到大的顺序排列
}

但是我们发现,Integer类中有一个叫做compare的静态方法:

public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

返回正数,第二个参数排前面,返回负数,第一个参数排前面

这个方法是一个静态方法,但是它却和Comparator需要实现的方法返回值和参数定义一模一样,所以:

public static void main(String[] args) {
    Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
    Arrays.sort(array, Integer::compare);   //直接指定一手,效果和上面是一模一样
    System.out.println(Arrays.toString(array));
}

非静态方法作为方法引用

默认使用 类::方法

如果使用非静态方法,依然采用 类::方法 的情况

Lambda 会识别,然后使用相应接口的抽象方参数列表的第一个作为目标对象,后续参数作为目标对象成员方法的参数来尝试调用

我们注意到Comparator要求我们实现的方法为:

public int compare(Integer o1, Integer o2) {
     return o1 - o2;
}

其中o1和o2都是Integer类型的,我们发现Integer类中有一个compareTo方法:

public int compareTo(Integer anotherInteger) {
    return compare(this.value, anotherInteger.value);
}

只不过这个方法并不是静态的,而是对象所有:

Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
Arrays.sort(array, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);   //这样进行比较也行,和上面效果依然是一样的
    }
});
System.out.println(Arrays.toString(array));

实际上,当我们使用非静态方法时,会使用抽象方参数列表的第一个作为目标对象,后续参数作为目标对象成员方法的参数,也就是说,此时,o1作为目标对象,o2作为参数,正好匹配了compareTo方法,所以,直接缩写:

public static void main(String[] args) {
    Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
    Arrays.sort(array, Integer::compareTo);  //注意这里调用的不是静态方法
    System.out.println(Arrays.toString(array));
}
对象::方法

成员方法也可以让对象本身不成为参与的那一方,仅仅引用方法

通过具体对象,即 对象::方法,这样就仿造了静态方法时的情况,此时就不会使用默认的情况(即用参数1调用方法,该方法参数为参数2)

而是类似 静态方法作为方法引用时,两个参数对应。

public static void main(String[] args) {
    Main mainObject = new Main();
    Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
    Arrays.sort(array, mainObject::reserve);  
    //使用Main类的成员方法,但是mainObject对象并未参与进来,只是借用了一下刚好匹配的方法
    System.out.println(Arrays.toString(array));
}

public int reserve(Integer a, Integer b){  //现在Main类中有一个刚好匹配的方法
    return b.compareTo(a);
}

构造方法作为方法引用

当然,类的构造方法 类::new 同样可以作为方法引用传递:

类的构造方法默认返回自身对象

public interface Test {
    String test(String str);   //现在我们需要一个参数为String返回值为String的实现
}

我们发现,String类中刚好有一个:

public String(String original) {   
  //由于String类的构造方法返回的肯定是一个String类型的对象,
  //且此构造方法需要一个String类型的对象,所以,正好匹配了接口中的
    this.value = original.value;
    this.coder = original.coder;
    this.hash = original.hash;
}

于是:

public static void main(String[] args) {
    Test test = String::new;   
    //没错,构造方法直接使用new关键字就行
}

当然除了上面提到的这些情况可以使用方法引用之外,还有很多地方都可以,还请各位小伙伴自行探索了。

Java 8也为我们提供了一些内置的函数式接口供我们使用:Consumer、Function、Supplier等,具体请回顾一下JavaSE篇视频教程。

Optional类

Java 8中新引入了Optional特性,来让我们更优雅的处理空指针异常。

我们先来看看下面这个例子:

public static void hello(String str){   
  //现在我们要实现一个方法,将传入的字符串转换为小写并打印
    System.out.println(str.toLowerCase());  
    //那太简单了吧,直接转换打印一气呵成
}

但是这样实现的话,我们少考虑了一个问题,万一给进来的strnull呢?

如果是null的话,在调用toLowerCase方法时岂不是直接空指针异常了?

所以我们还得判空一下:

public static void hello(String str){
    if(str != null) {
        System.out.println(str.toLowerCase());
    }
}

但是这样写着就不能一气呵成了,我现在又有强迫症,我就想一行解决

这时,Optional来了,我们可以将任何的变量包装进Optional类中使用:

public static void hello(String str){
    Optional
            .ofNullable(str)   //将str包装进Optional
            .ifPresent(s -> {   
              //ifPresent表示只有对象不为null才会执行里面的逻辑,实现一个Consumer(接受一个参数,返回值为void)
                System.out.println(s);   
            });
}

由于这里只有一句打印,所以我们来优化一下:

public static void hello(String str){
    Optional
            .ofNullable(str)   //将str包装进Optional
            .ifPresent(System.out::println);  
    //println也是接受一个String参数,返回void,所以这里使用我们前面提到的方法引用的写法
}

这样,我们就又可以一气呵成了,是不是感觉比之前的写法更优雅。

除了在不为空时执行的操作外,还可以直接从Optional中获取被包装的对象:

System.out.println(Optional.ofNullable(str).get());

不过此时当被包装的对象为null时会直接抛出异常,当然,我们还可以指定如果get的对象为null的替代方案:

System.out.println(Optional.ofNullable(str).orElse("VVV"));
//orElse表示如果为空就返回里面的内容
上次编辑于: