Java8新特性

Mr.Tong...
  • Java知识体系
  • Java8新特性
  • Java知识体系
  • Java8新特性
大约 10 分钟

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。

Java8 新增了非常多的特性,我们主要讨论以下几个:

  • Lambda 表达式:Lambda 允许把函数作为参数传递到方法中。
  • 方法引用:可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 默认方法 :默认方法就是一个在接口里面有了一个实现的方法。
  • 新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
  • Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • Date Time API:加强对日期与时间的处理。
  • Optional 类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Nashorn, JavaScript 引擎: Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

Java 8与 Java 7 的编程风格是有区别的,如下例子:

📖Lambda 表达式(重要)

✅匿名内部类存在的问题

当需要启动一个线程去完成任务时,通常会通过一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,并使用 Thread 类来启动该线程。

public class test01 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                System.out.println("新线程任务执行!");
            }
        });
    }
}

代码分析:

上面创建的匿名内部类的过程做了哪些事情:

  1. 定义了一个没有名字的类
  2. 这个类实现了Runnable接口
  3. 创建了这个类的对象

而使用匿名内部类语法是很冗余的。

Lambda体验:

new Thread(() -> System.out.println("新线程2任务执行!")).start();

使用Lambda表达式可以发现代码更加简单了。

Lambda的优点:

所以Lambda的优点就是简化匿名内部类的使用,语法更加简单。

✅Lambda的标准格式

Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:

(参数类型 参数名称)  ->  {
	代码体;
}

格式说明:

  • (参数类型 参数名称):参数列表
  • {代码体;}:方法体
  • -> :箭头,分隔参数列表和方法体

Lambda与方法的对比:

匿名内部类

public void run() {
	System.out.println("aa");
}

Lambda

()  ->  System.out.println("bb!")

✅Lambda的前提条件

Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有2个前提条件::

  1. 方法的参数或局部变量类型必须为接口才能使用Lambda。
  2. 接口中有且仅有一个抽象方法

✅Lambda的重要特征

  1. 简洁性

Lambda 表达式提供了一种更为简洁的语法,尤其适用于函数式接口。相比于传统的匿名内部类,Lambda 表达式使得代码更为紧凑,减少了样板代码的编写。

比如将下面集合进行排序:

//有如下集合,要求进行排序
 List<String> names = new ArrayList<String>();
      names1.add("Google ");
      names1.add("Runoob ");
      names1.add("Taobao ");
      names1.add("Baidu ");
      names1.add("Sina ");
//Java7实现
Collections.sort(names, new Comparator<String>() {
         @Override
         public int compare(String s1, String s2) {
            return s1.compareTo(s2);
         }
});
//Java8新特性Lambda实现
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));

非常明显的看出Lambda表达式的简洁性。

  1. 函数式编程支持

Lambda 表达式是函数式编程的一种体现,它允许将函数当作参数传递给方法,或者将函数作为返回值,这种支持使得 Java 在函数式编程方面更为灵活,能够更好地处理集合操作、并行计算等任务。

// 使用 Lambda 表达式作为参数传递给方法
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
  1. 变量捕获

Lambda 表达式可以访问外部作用域的变量,这种特性称为变量捕获,Lambda 表达式可以隐式地捕获 final 或事实上是 final 的局部变量。

// 变量捕获
int x = 10;
MyFunction myFunction = y -> System.out.println(x + y);
myFunction.doSomething(5); // 输出 15
  1. 方法引用

Lambda 表达式可以通过方法引用进一步简化,方法引用允许你直接引用现有类或对象的方法,而不用编写冗余的代码。

// 使用方法引用
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
  1. 可并行性
// 使用 Lambda 表达式和 Stream API 进行并行计算
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();

✅Lambda省略格式

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
//省略前
(int a) -> {
	return new Person();
}
//省略后
a -> new Person()

Lambda 表达式实例:

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

✅了解Lambda的实现原理

先说结论:

Lambda在程序运行的时候:

  1. 在类中新增一个静态私有的方法,这个方法的方法体就是Lambda表达式中的代码
  2. 还会形成一个匿名内部类,实现接口,重写抽象方法,并在重写方法中会调用新生成的静态私有方法

代码分析:

匿名内部类情况:

  1. 准备下面匿名内部类代码:
//接口
public interface Animal {
    public abstract void eat();
}
//在test02类中调用
public class test02 {
    public static void main(String[] args) {
        goOpen(new Animal() {
            @Override
            public void eat() {
                System.out.println("使用匿名内部类实现吃东西");
            }
        });
    }
    public static void goOpen(Animal open){
        open.eat();
    }
}
  1. 将代码进行编译,产生了下面文件

image-20240402212443892

  1. 将生成的匿名类使用jadx反编译工具进行反编译,得到的代码如下:
package com.jdk8;
/* loaded from: test02$1.class */
class test02$1 implements Animal {
    test02$1() {}
    public void eat() {
        System.out.println("使用匿名内部类实现吃东西");
    }
}

发现生成的test02$1类实现了Animal接口,并重写了 eat方法。

Lambda情况:

  1. 改写成Lambda代码:
public class test02 {
    public static void main(String[] args) {
        goOpen(() -> System.out.println("使用Lambda实现吃东西"));
    }
    public static void goOpen(Animal open){
        open.eat();
    }
}
  1. 将代码进行编译,发现并没有新增匿名class字节文件

image-20240402213852154

  1. 使用JDK自带的javap工具对test02.class字节码进行反汇编,命令如下:
javap -c -p 文件名.class
    -c:表示对代码进行反汇编
    -p:显示所有的类和成员    

结果如下:发现反汇编后的代码新生成了一个序号为0的方法lambda$main$0,而这个方法里面的内容就是Lambda代码块中的内容。

image-20240402215712460

那么这个新生成的方法是如何调用的呢?我们可以使用下面指令在运行时会将生成的内部类class码输出到一个文件中。

java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名

image-20240402221046254

image-20240402221247637

  1. 将这个在运行时生成的匿名内部类进行反编译,代码如下

image-20240402221718494

我们发现运行时生成的匿名内部类中会去调用新生成的方法,而新生成的lambda$main$0方法中的内容正是Lambda中的代码。

所以Lambda的实现原理就是开头所说的:

Lambda在程序运行的时候:

  1. 在类中新增一个静态私有的方法,这个方法的方法体就是Lambda表达式中的代码
  2. 还会形成一个匿名内部类,实现接口,重写抽象方法,并在重写方法中会调用新生成的静态私有方法

✅Lambda和匿名内部类对比

了解Lambda和匿名内部类在使用上的区别

  1. 所需的类型不一样、

    • 匿名内部类,需要的类型可以是类,抽象类,接口
    • Lambda表达式,需要的类型必须是接口
  2. 抽象方法的数量不一样

    • 匿名内部类所需的接口中抽象方法的数量随意

    • Lambda表达式所需的接口只能有一个抽象方法

  3. 实现原理不同

    • 匿名内部类是在编译后会形成class
    • Lambda表达式是在程序运行的时动态生成class

📖方法引用

✅方法引用的格式

使用双冒号::写法,被称为方法引用。

  • 符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用
  • 应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。

常见引用方式

方法引用在JDK 8中使用方式相当灵活,有以下几种形式:

  1. instanceName::methodName 对象::方法名
  2. ClassName::staticMethodName 类名::静态方法
  3. ClassName::methodName 类名::普通方法
  4. ClassName::new 类名::new 调用的构造器
  5. TypeName[]::new String[]::new 调用数组的构造器

✅构造器引用

✅静态方法引用

📖函数式接口

✅常用内置函数式接口

  • Supplier接口
  • Consumer接口
  • Function接口
  • Predicate接口

应用场景:

  1. Supplier 接口
    • 用于延迟计算,只有在需要时才进行计算,比如惰性加载。
    • 用于生成随机数、UUID 或其他对象。
    • 用于配置信息的提供。
  2. Consumer 接口
    • 用于消费某个参数,比如打印日志、发送消息等。
    • 用于处理集合中的每个元素,如列表的遍历。
  3. Function 接口
    • 用于数据的转换,如将字符串转换为整数、将对象转换为字符串等。
    • 用于数据的处理,如格式化数据、映射数据等。
    • 用于流式处理中的 map 操作,将流中的元素映射为另一种类型的元素。
  4. Predicate 接口
    • 用于条件判断,如筛选符合条件的元素。
    • 用于过滤集合中的元素,如筛选出满足某些条件的对象。
    • 用于流式处理中的 filter 操作,根据条件过滤流中的元素。

这些函数式接口提供了一种灵活的方式来处理函数,可以使代码更加简洁、易读和易于维护。它们在 Java 8 引入的流式处理、并行处理等新特性中得到了广泛的应用,使得对数据的处理更加高效和方便。同时,它们也为函数式编程和Lambda表达式的使用提供了重要的支持

📖接口新增的两个方法

📖Stream流(重要)

📖Optional 类

📖日期时间 API

你认为这篇文章怎么样?

  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.14.1