06、Java8新特性(完结)

本文详细介绍了Java 8的主要新特性,包括Lambda表达式的使用、Stream流式操作、接口的默认方法与静态方法、方法引用的多种形式,以及新的日期和时间API。通过实例解析了Lambda的语法糖、函数式接口如Supplier、Consumer、Function和Predicate的运用。此外,还探讨了JDK8中接口的默认方法和静态方法、Optional类的使用,以及Fork/Join框架和并行流。最后,文章展示了日期时间API的改进,如LocalDate、LocalTime和LocalDateTime,以及时间校正器和时区处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 8新特性

1、概述

Java 8发布于2014-03-18,发布至今已经8年了,是目前企业中使用最广泛的一个版本。Java 8是一次重大的版本升 级,带来了很多的新特性。

JDK 8新特性

  • Lambda表达式
  • 集合之Stream流式操作
  • 接口的增强
  • 并行数组排序
  • Optional中避免Null检查
  • 新的时间和日期 API
  • 可重复注解

1.1为什么学?

  • 能够看懂公司里的代码
  • 大数量下处理集合的效率高
  • 代码可读性高
  • 消灭嵌套地狱

1.2函数式编程思想

关注对数据进行操作

优点:

  • 代码简洁,开发快速
  • 接近自然语言,易于理解
  • 易于并发编程

2、Lambda表达式

Lambda是JDK8中的一个语法糖,可以对某些匿名内部类的写法进行简化,它是函数式编程思想的一个重要体现,让我们不用关注是什么对象,而是关注我们对数据进行了什么操作。

核心原则

可推导可省略

2.1 基本格式

(参数列表)—>{
	代码
}

2.2 示例

匿名内部类创建线程

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类创建线程");
            }
        }
).start();

Lambda表达式写法:

new Thread(()->{
    System.out.println("Lambda表达式创建线程");
}).start();

2.2.1 无参返回值的Lambda

public interface Swimmable {
    public abstract void run();
}
public class DemoLambdaUse {
    public static void main(String[] args) {
        //匿名内部类写法
        goRun(new Swimmable() {
            @Override
            public void run() {
                System.out.println("匿名内部类写法");
            }
        });

        //Lambda表达式写法
        goRun(()->{
            System.out.println("Lambda表达式写法");
        });
    }

    public static void goRun(Swimmable swimmable){
        swimmable.run();
    }
}

2.2.2 有参数返回的Lambda

下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:

public abstract int compare(T o1, T o2);

当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。

ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(9);
list.add(5);

//匿名内部类写法
Collections.sort(list, new Comparator<Integer>(){

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

//Lambda表达式写法
Collections.sort(list, (Integer o1, Integer o2) ->{
    return o1 - o2;
});

for (Integer integer : list) {
    System.out.println(integer);
}
List<Integer> asList = Arrays.asList(11, 33, 22, 44);
//匿名内部类写法
asList.forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
});

//Lambda表达式写法
asList.forEach((s) -> {
    System.out.println(s);
});

//Lambda表达式写法 变形
asList.forEach(s->System.out.println(s));

asList.forEach(s->{
	if (s > 20){
		System.out.println(s);
	}
});

2.3 Lambda的实现原理

匿名内部类实现原理

说实现原理前先说说匿名内部类实现原理

public class DemoLambdaUse {
    public static void main(String[] args) {
        //匿名内部类写法
        goRun(new Swimmable() {
            @Override
            public void run() {
                System.out.println("匿名内部类写法");
            }
        });
    }

    public static void goRun(Swimmable swimmable){
        swimmable.run();
    }
}

匿名内部类会在编译后产生一个类: DemoLambdaImpl$1.class

image-20220116191823770

反编译后的内容

package com.sl.java8;

final class DemoLambdaUse$1 implements Swimmable {
    DemoLambdaUse$1() {
    }

    public void run() {
        System.out.println("匿名内部类写法");
    }
}

Lambda表达式实现原理

public class DemoLambdaUse {
    public static void main(String[] args) {
        //Lambda表达式写法
        goRun(()->{
            System.out.println("Lambda表达式写法");
        });
    }

    public static void goRun(Swimmable swimmable){
        swimmable.run();
    }
}

image-20220116203534196

运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一 个新的类

我们使用 JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。 在DOS命令行输入:

javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员
F:\workspace\java_projcet\study\java8\target\classes\com\sl\java8>javap -c -p DemoLambdaUse.class
Compiled from "DemoLambdaUse.java"
public class com.sl.java8.DemoLambdaUse {
  public com.sl.java8.DemoLambdaUse();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Lcom/sl/java8/Swimmable;
       5: invokestatic  #3                  // Method goRun:(Lcom/sl/java8/Swimmable;)V
       8: return

  public static void goRun(com.sl.java8.Swimmable);
    Code:
       0: aload_0
       1: invokeinterface #4,  1            // InterfaceMethod com/sl/java8/Swimmable.run:()V
       6: return

  private static void lambda$main$0();
    Code:
       0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #6                  // String Lambda表达式写法
       5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

多了一个私有的静态方法

private static void lambda$main$0();

其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加 上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文 件中。使用java命令如下:

F:\workspace\java_projcet\study\java8\target\classes>java -Djdk.internal.lambda.dumpProxyClasses com.sl.java8.DemoLambdaUse
Lambda表达式写法

image-20220116204441105

反编译DemoLambdaUse$$Lambda$1.class

package com.sl.java8;

import java.lang.invoke.LambdaForm.Hidden;

// $FF: synthetic class
final class DemoLambdaUse$$Lambda$1 implements Swimmable {
    private DemoLambdaUse$$Lambda$1() {
    }

    @Hidden
    public void run() {
        DemoLambdaUse.lambda$main$0();
    }
}

可以看到这个匿名内部类实现了 Swimmable 接口,并且重写了 swimming 方法, swimming 方法调用 Demo04LambdaImpl.lambda$main$0() ,也就是调用Lambda中的内容。最后可以将Lambda理解为:

public class Demo04LambdaImpl {
	public static void main(String[] args) {
		goSwimming(new Swimmable() {
			public void swimming() {
				Demo04LambdaImpl.lambda$main$0();
			}
		});
	}

	private static void lambda$main$0() {
		System.out.println("Lambda表达式游泳");
	}
	
	public static void goSwimming(Swimmable swimmable) {
		swimmable.swimming();
	}
}

总结:

Lambda在程序运行的时候形成一个类

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

2.4 Lambda省略格式

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

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

省略后

a -> new Person()

2.5 Lambda的前提条件

Lambda表达式不是随便使用的,使用时有几个条件要特别注意:

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

Lambda表达式的前提条件:

  1. 方法的参数或变量的类型是接口
  2. 这个接口中只能有一个抽象方法

2.6 函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口

ava 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上:

//检测这个接口是不是只有一个抽象方法
@FunctionalInterface
public interface Swimmable {
    public abstract void run();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即 使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

2.7 常用内置函数式接口

Supplier接口

它们主要在 java.util.function 包中。下面是最常用的几个接口。

Supplier接口

java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类 型的对象数据。

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

示例

public class TestSupplier {

    public static void printMax(Supplier<Integer> supplier){
        System.out.println(supplier.get());
    }

    public static void main(String[] args) {

        printMax(()->{

            int[] arry = {11, 33, 22};
            Arrays.sort(arry);
            return arry[arry.length -1];
        });
    }

}

Consumer接口

java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。

public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

默认方法:andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。

java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。

这省去了重复编写if语句和抛出空指针异常的麻烦。

示例:

public class TestConsumer {
    public static void test(Consumer<String> c1, Consumer<String> c2){
        String s = "HelloWorld";
        c1.accept(s);

    }

    public static void main(String[] args) {
        test(s -> {
            System.out.println(s.toLowerCase());
        });
    }
}

示例2:

public class TestConsumer {

    public static void test(Consumer<String> c1, Consumer<String> c2){
        String s = "HelloWorld";
        c1.andThen(c2).accept(s);

    }

    public static void main(String[] args) {
        test(s -> {
            System.out.println(s.toLowerCase());
        },s -> {
            System.out.println(s.toUpperCase());
        });
    }
}

Function接口

java.util.function.Function 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件, 后者称为后置条件。有参数有返回值。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

示例:

public class TestFunction {

    public static void test(Function<String, Integer> function){
        Integer apply = function.apply("10");
        System.out.println("in:" + (apply+ 5));
    }

    public static void main(String[] args) {

        test(s->{
            return Integer.parseInt(s);
        });
    }

}

默认方法:andThen

Function 接口中有一个默认的 andThen 方法,用来进行组合操作。

该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:

public class Demo09FunctionAndThen {
    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
        		return Integer.parseInt(s);
        	}, (Integer i) -> {
        		return i * 10;
        });
    }
    public static void test(Function<String, Integer> f1, Function<Integer, Integer> f2) {
        // Integer in = f1.apply("66"); // 将字符串解析成为int数字
        // Integer in2 = f2.apply(in);// 将上一步的int数字乘以10
        Integer in3 = f1.andThen(f2).apply("66");
        System.out.println("in3: " + in3); // 660
    }
}

请注意,Function的前置条件泛型和后置条件泛型可以相同。

Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate 接口。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

示例:

public class TestPredicate {

    private static void test(Predicate<String> predicate){
        boolean test = predicate.test("1");
        System.out.println(test);
    }

    public static void main(String[] args) {

        test(s->{
            return s.equals("1");
        });

    }
}

2.8 Lambda表达式和匿名内部类

匿名内部类 Lambda表达式
所需类型 可以是类,抽象类,接口 必须是接口
抽象方法数量 随意 只能有一个抽象方法
实现原理 在编译后会形成class 在程序运行时动态生成class

总结:

当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类

3、JDK8接口新增的两个方法

接口增强介绍

jdk8之前:

public interface Swimmable {
	静态常量;
    抽象方法;
}

增强后:

可以有默认方法静态方法

interface 接口名 {
    静态常量;
    抽象方法;
    默认方法;
    静态方法;
}

3.1默认方法

格式

interface 接口名 {
	修饰符 default 返回值类型 方法名() {
		代码;
	}
}

public interface Swimmable {
    //默认方法;
    default void func(){
        System.out.println("接口中的抽象方法");
    }
}

class SwimmableImpl implements Swimmable{
    
    public static void main(String[] args) {
        new SwimmableImpl().func();
    }

}

使用

  1. 实现接口&#x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值