Java8 新特性详解

2023/11/5 Java各版本特性

# 1、Java8 特性概览

Java8 是一个 Java 的重要更新,它提供了许多新的特性,这些特性可以帮助我们更好地编写和使用Java代码。

下面是Java8 主要特性的概览:

  • Lambda 表达式:JDK8 支持函数式编程,引入了 Lambda 表达式,使得可以在 Java 中以更简洁的语法编写函数式代码。
  • 函数式接口与注解:JDK8 提供了一些新的函数式接口和注解,用于支持函数式编程。
  • 方法引用:方法引用是 Lambda 表达式的一种简化形式,可以使得代码更简洁可读。
  • 默认方法与静态方法:JDK8 允许接口中定义默认方法和静态方法,从而使得在接口中可以提供一些默认实现代码。
  • Stream API:Stream API 是一套用于操作集合类的 API,可以更方便地进行数据处理和转换操作。
  • Optional 类:Optional 类是一个容器类,可以用来存放一个可能为空的对象。
  • 新的日期和时间 API:JDK8 引入了新的日期和时间 API,解决了原先日期和时间处理类的很多问题,提供了更灵活和易用的日期和时间操作方法。
  • Nashorn, JavaScript 引擎:Java8 提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
  • 重复注解:Java 8允许在同一个元素上多次使用同一个注解,从而避免了使用容器注解的麻烦。
  • CompletableFuture:CompletableFuture 是一个支持异步编程的补充类。它提供了一种方便的方式来处理异步任务的结果,包括异常处理、组合任务等。
  • Type Annotations(类型注解):Java 8增加了对类型注解的支持,使得程序员可以在代码中更精确地指定类型信息。

# 2、Lambda 表达式

# 2.1、Lambda 初识

Lambda 表达式,也称为闭包。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。

  • 很多语言一开始就支持Lambda 表达式,但是 Java 起初只能使用匿名内部类代替Lambda表达式。
//匿名内部类方式排序
List<String> names = Arrays.asList( "a", "b", "d" );

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});
1
2
3
4
5
6
7
8
9
  • Java 在 JDK8 的版本才支持了 Lambda 表达式。

Lambda 表达式的语法格式:

(parameters) -> expression
或
(parameters) ->{ statements; }

// 使用Lambda实现排序
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
1
2
3
4
5
6

# 2.2、Lambda 使用

Lambda编程风格,总结为四类

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

可以不用显示声明参数类型,编译器可以统一识别参数类型。

Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
// 等价于
Collections.sort(names, (String s1, String s2) -> s1.compareTo(s2));
1
2
3
  • 可选的参数圆括号

当方法那只有一个参数时,无需定义圆括号。多个参数需要定义圆括号。

// 单个参数,可不定义圆括号
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
// 多个参数需要定义圆括号
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
1
2
3
4
  • 可选的大括号

当主体只包含了一行时,无需使用大括号。当主体包含多行时,需要使用大括号。

// 单行主体
Arrays.asList( "a", "b", "c" ).forEach( e -> System.out.println( e ) );
// 多行主体
Arrays.asList( "a", "b", "c" ).forEach( e -> {
    System.out.println( e );
    System.out.println( e );
} );
1
2
3
4
5
6
7
  • 可选的返回关键字

如果表达式中的语句块只有一行,则可以不用使用return语句,返回值的类型也由编译器推理得出。如果语句块有多行,可以在大括号中指明表达式返回值

// 单行可省略return
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

// 多行return
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
1
2
3
4
5
6
7
8

# 2.3、Lambda 变量作用域

  • Lambda 表达式可以引用类成员和局部变量,但是会将这些变量隐式得转换成final。
// separator已经被隐式得转换成final
String separator = ",";
Arrays.asList( "a", "b", "c" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
// 等价于
final String separator = ",";
Arrays.asList( "a", "b", "c" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
1
2
3
4
5
6
7
8
  • Lambda 表达式的局部变量可以不用声明为final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)。
int num = 1;
Arrays.asList(1,2,3,4).forEach(e -> System.out.println(num + e));
num =2;
//报错信息:Local variable num defined in an enclosing scope must be final or effectively final
1
2
3
4
  • Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
int num = 1;
Arrays.asList(1,2,3,4).forEach(num -> System.out.println(num));
//报错信息:Variable 'num' is already defined in the scope
1
2
3

# 3、函数式接口与注解

# 3.1、概念及基本使用

函数接口是为了良好兼容 Lambda 表达式而设计的。

  • 函数接口

指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式。

  • 注解@FunctionalInterface

在实践中,函数式接口非常脆弱,只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface

  • 案例
@FunctionalInterface
public interface GreetingService {
    void sayMessage(String message);
}

// 调用可以直接使用 Lambda 表达式
GreetingService greetService = message -> System.out.println("Hello " + message);
greetService.sayMessage("World");  // 输出:Hello World
1
2
3
4
5
6
7
8

函数式接口就是为了能使用 Lambda 表达式。可以定义多个非抽象方法,是指可以有多个静态方法和多个默认方法,但是抽象方法只能有一个。

# 3.2、内置函数接口

Java8 引入了一些内置的函数接口,用于支持函数式编程。这些接口可以减少开发者编写重复的代码,并且提高代码的可读性和可维护性。

  • Supplier:Supplier接口代表一个不接受任何参数,返回一个结果的函数。可以使用get()方法获取结果。
  • Consumer:Consumer接口代表一个接受一个参数但不返回结果的函数。可以使用accept(T t)方法处理参数。
  • Function:Function接口代表一个接受一个参数,并返回一个结果的函数。可以使用apply(T t)方法处理参数并返回结果。
  • Predicate:Predicate接口代表一个接受一个参数,并返回一个布尔值的函数。可以使用test(T t)方法判断参数是否满足条件。
  • UnaryOperator:UnaryOperator接口是Function的一个特例,接受一个参数,并返回与参数类型相同的结果。
  • BinaryOperator:BinaryOperator接口是Function的一个特例,接受两个参数,并返回与参数类型相同的结果。

下面会通过案例看一下这些函数接口的使用。

# 3.3、Supplier

Supplier接口代表一个不接受任何参数,返回一个结果的函数。可以使用get()方法获取结果。

Supplier<String> supplier = () -> "Hello"
System.out.println(randomNumber.get());
// 输出结果:Hello
1
2
3

# 3.4、Consumer

Consumer接口代表一个接受一个参数但不返回结果的函数。可以使用accept(T t)方法处理参数。

Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase());
printUpperCase.accept("hello");
// 输出结果:hello
1
2
3

# 3.5、Function

Function接口代表一个接受一个参数,并返回一个结果的函数。可以使用apply(T t)方法处理参数并返回结果。

Function<Integer, String> intToString = num -> num.toString();
System.out.println(intToString.apply(10));
// 输出结果:10
1
2
3

# 3.6、Predicate

Predicate接口代表一个接受一个参数,并返回一个布尔值的函数。可以使用test(T t)方法判断参数是否满足条件。

Predicate<Integer> isPositive = num -> num > 0;
System.out.println(isPositive.test(5));
// 输出结果:true
1
2
3

# 3.7、UnaryOperator

UnaryOperator接口是Function的一个特例,接受一个参数,并返回与参数类型相同的结果。

UnaryOperator<Integer> square = num -> num * num;
System.out.println(square.apply(5));
// 输出结果:25
1
2
3

# 3.8、BinaryOperator

BinaryOperator接口是Function的一个特例,接受两个参数,并返回与参数类型相同的结果。

BinaryOperator<Integer> sum = (num1, num2) -> num1 + num2;
System.out.println(sum.apply(3, 5));
// 输出结果:8
1
2
3

# 4、方法引用

方法引用允许您通过名称引用现有方法,从而使代码更简洁和易读。使用一对冒号::,通过方法的名字来指向一个方法。

方法引用有四种用法:

  • 静态方法引用(Class::staticMethod)
  • 实例对象的成员方法的引用(instance::instanceMethod)
  • 类的成员方法引用(Class::instanceMethod)
  • 构造器引用(Class::new)

# 4.1、静态方法引用

静态方法引用:使用类名和方法名来引用一个静态方法。

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

// 静态方法引用示例
Calculate c = MathUtils::add;
int result = c.calculate(3, 4); // 调用静态方法 MathUtils.add,返回结果 7
1
2
3
4
5
6
7
8
9

# 4.2、实例对象的成员方法的引用

实例对象的成员方法的引用:使用对象实例和方法名来引用一个实例方法。

public class StringUtils {
    public boolean startsWith(String str, String prefix) {
        return str.startsWith(prefix);
    }
}

StringUtils su = new StringUtils();
Predicate<String> p = su::startsWith;
boolean startsWithFoo = p.test("foobar", "foo"); // 调用实例方法 StringUtils.startsWith,返回结果 true
1
2
3
4
5
6
7
8
9

# 4.3、类的成员方法引用

类的成员方法引用:使用类名和方法名来引用一个实例方法。需要注意的是,方法引用的函数式接口类型的第一个参数类型为该方法的调用者类型。

public class StringUtils {
    public boolean startsWith(String str, String prefix) {
        return str.startsWith(prefix);
    }
}

Predicate<String> p = String::startsWith;
boolean startsWithFoo = p.test("foobar", "foo"); // 调用实例方法 String.startsWith,返回结果 true
1
2
3
4
5
6
7
8

# 4.4、构造器引用

构造器引用:使用类名和关键字new来引用一个构造方法。

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// 构造方法引用示例
Supplier<Person> s = Person::new;
Person p = s.get(); // 调用构造方法 Person(),返回一个新的 Person 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 5、默认方法与静态方法

Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。

Java 8增加接口默认方法和静态方法有以下几个主要原因:

  • 向后兼容性

在Java 8之前的版本中,接口是不能包含具体实现的方法的。这就导致了一个问题,即如果一个接口中定义了很多方法,而且已经被很多类实现了,那么要添加新的方法时就会破坏这些已有的实现。为了解决这个问题,Java 8引入了默认方法和静态方法,使得接口能够在不破坏现有实现的情况下添加新的方法。

  • 接口的扩展性

默认方法和静态方法使得接口具有了更大的灵活性和扩展性。接口可以提供一些默认的方法实现,而实现类可以选择性地重写这些方法,从而提供自己的实现。这样一来,接口的设计者可以在不破坏现有实现的情况下,为接口添加新的功能。

  • 减少重复代码

默认方法和静态方法可以用于在接口中定义通用的方法逻辑。如果多个实现类中有相同的方法逻辑,可以将这些逻辑提取到接口的默认方法中,避免了重复编写代码。

  • 函数式编程的支持

默认方法和静态方法为函数式编程提供了更好的支持。在Java 8中,可以将接口中的抽象方法当作函数式接口来使用,使用Lambda表达式来实现接口中的抽象方法。默认方法和静态方法可以用于补充函数式接口中的其他功能。

  • 定义接口
public interface Vehicle {
    //默认方法
   default void print(){
      System.out.println("我是一辆车!");
   }
    // 静态方法
   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}
1
2
3
4
5
6
7
8
9
10
  • 测试
public class Tester {
   public static void main(String args[]){
      Vehicle vehicle = new Car();
      vehicle.print();
   }
}

interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }

   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}

interface FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}

class Car implements Vehicle, FourWheeler {
   public void print(){
      Vehicle.super.print();
      FourWheeler.super.print();
      Vehicle.blowHorn();
      System.out.println("我是一辆汽车!");
   }
}

/**
 * 输出结果:
 * 我是一辆车!
 * 我是一辆四轮车!
 * 按喇叭!!!
 * 我是一辆汽车!
 */
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
32
33
34
35
36
37
38
39

# 6、Stream API

# 6.1、Stream 初识

Java 8引入了Stream API,它是处理集合和数组的函数式编程方式。Stream是一种可遍历的数据元素序列,相当于Java中的流水线,可以通过一系列的操作来处理数据。

Stream具有以下特点和优点:

  • 内部迭代:Stream提供了一种内部迭代的机制,通过一系列操作来处理数据,而不是显示的使用迭代器来遍历集合。这种方式更加简洁和高效。
  • 惰性求值:Stream中的操作分为中间操作和终端操作,中间操作不会立即执行,只有终端操作被调用才会执行中间操作。这种惰性求值的特性可以提高性能。
  • 函数式编程:Stream API使用函数式编程的方式进行操作,可以通过Lambda表达式来实现对数据的处理,大大简化了代码的书写和阅读。
  • 并行操作:Stream API可以进行并行操作,使用多线程来提高处理大量数据的效率。通过parallel()方法可以将Stream转换为并行流。

Stream的常用操作包括:

  • 过滤和切片:filter、distinct、limit、skip等
  • 映射:map、flatMap等
  • 排序:sorted
  • 匹配和查找:anyMatch、allMatch、noneMatch、findFirst、findAny等
  • 归约:reduce
  • 收集:collect

# 6.2、过滤和切片

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 过滤偶数
List<Integer> evenNumbers = numbers.stream()
                                  .filter(n -> n % 2 == 0)
                                  .collect(Collectors.toList());

// 去重
List<Integer> distinctNumbers = numbers.stream()
                                       .distinct()
                                       .collect(Collectors.toList());

// 截取前3个元素
List<Integer> limitNumbers = numbers.stream()
                                    .limit(3)
                                    .collect(Collectors.toList());

// 跳过前3个元素
List<Integer> skipNumbers = numbers.stream()
                                   .skip(3)
                                   .collect(Collectors.toList());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 6.3、映射

List<String> words = Arrays.asList("Java", "Stream", "API");

// 将单词转为大写
List<String> upperCaseWords = words.stream()
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());

// 将每个单词拆分成字符数组
List<char[]> charArrays = words.stream()
                               .map(String::toCharArray)
                               .collect(Collectors.toList());

// 将每个单词拆分成字符列表
List<Character> chars = words.stream()
                             .flatMap(word -> word.chars().mapToObj(c -> (char) c))
                             .collect(Collectors.toList());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 6.4、排序

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);

// 升序排序
List<Integer> sortedNumbers = numbers.stream()
                                     .sorted()
                                     .collect(Collectors.toList());

// 降序排序
List<Integer> reversedNumbers = numbers.stream()
                                       .sorted(Comparator.reverseOrder())
                                       .collect(Collectors.toList());
1
2
3
4
5
6
7
8
9
10
11

# 6.5、匹配和查找

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 判断是否有满足条件的元素
boolean anyMatch = numbers.stream()
                          .anyMatch(n -> n > 3);

// 判断是否所有元素都满足条件
boolean allMatch = numbers.stream()
                          .allMatch(n -> n > 0);

// 判断是否没有满足条件的元素
boolean noneMatch = numbers.stream()
                           .noneMatch(n -> n > 5);

// 查找第一个满足条件的元素
Optional<Integer> first = numbers.stream()
                                 .filter(n -> n > 3)
                                 .findFirst();

// 查找任意一个满足条件的元素
Optional<Integer> any = numbers.stream()
                               .filter(n -> n > 3)
                               .findAny();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6.6、归约

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 求和
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);

// 求最大值
Optional<Integer> max = numbers.stream()
                               .reduce(Integer::max);
1
2
3
4
5
6
7
8
9

# 6.7、收集

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 转为数组
int[] array = numbers.stream()
                     .mapToInt(Integer::intValue)
                     .toArray();

// 转为Set
Set<Integer> set = numbers.stream()
                          .collect(Collectors.toSet());

// 转为Map
Map<Integer, String> map = numbers.stream()
                                  .collect(Collectors.toMap(n -> n, n -> "value" + n));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 6.8、并行(parallel)

parallelStream是流并行处理程序的代替方法。

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
1
2
3

# 6.9、常用用法

// 分组
// 按年龄进行分组
Map<Integer, List<Person>> groupedByAge = people.stream()
        .collect(Collectors.groupingBy(Person::getAge));

// 按姓名首字母进行分组,并按姓名字母排序
Map<Character, List<Person>> groupedByName = people.stream()
        .collect(Collectors.groupingBy(p -> p.getName().charAt(0),
                Collectors.toList()));
        

List<String> words = Arrays.asList("apple", "banana", "blueberry", "cherry", "coconut");

// 按单词长度分组,并保持分组后的顺序
Map<Integer, List<String>> groupedWords = words.stream()
        .collect(Collectors.groupingBy(String::length, LinkedHashMap::new, Collectors.toList()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 7、Optional 类

Java8 引入了Optional类,用来解决空指针异常问题。Optional类是一个容器类,可以保存类型T的值,或者仅仅保存null。

  • Optional类的主要方法如下:

  • ofNullable(T value):创建一个可选的对象,可以保存传入的值。如果传入值为null,则创建一个空的Optional对象。
  • isPresent():判断Optional对象是否包含值。
  • get():获取Optional对象中保存的值。
  • ifPresent(Consumer<? super T> consumer):如果Optional对象有值,则调用consumer接口处理该值。
  • orElse(T other):如果Optional对象有值,则返回该值,否则返回传入的other值。
  • orElseGet(Supplier<? extends T> supplier):如果Optional对象有值,则返回该值,否则返回supplier接口返回的值。
  • orElseThrow(Supplier<? extends X> supplier):如果Optional对象有值,则返回该值,否则抛出supplier接口指定的异常。
  • 案例
public class OptionalDemo {
    public static void main(String[] args) {
        String value = "Hello, world!";
        
        // 创建一个包含非空值的Optional对象
        Optional<String> optional1 = Optional.ofNullable(value);
        // 判断对象是否包含值
        if (optional1.isPresent()) {
            System.out.println(optional1.get());
        }
        
        // 创建一个空的Optional对象
        Optional<String> optional2 = Optional.ofNullable(null);
        // 使用ifPresent方法处理值
        optional2.ifPresent(val -> System.out.println(val));
        
        // 使用orElse方法返回值
        String result1 = optional1.orElse("Default Value");
        System.out.println(result1);
        
        // 使用orElseGet方法返回值
        String result2 = optional2.orElseGet(() -> "Default Value");
        System.out.println(result2);
        
        // 使用orElseThrow方法抛出异常
        try {
            String result3 = optional2.orElseThrow(() -> new IllegalArgumentException("Value is null"));
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

/**
 * 输出结果:
 * Hello, world!
 * Default Value
 * Default Value
 * Value is null
 */
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
32
33
34
35
36
37
38
39
40

# 8、新的日期和时间 API

# 8.1、java.time中的关键类

Java8 引入了新的 Date-Time API(JSR 310) 来改进时间、日期的处理。旧版的日期时间存在很多问题:非线程安全、设计很差(日期时间转换麻烦)、时区处理麻烦等。

  • 新的java.time包中包含以下几个种关键类:

  • Clock类:Clock类使用时区来返回当前的纳秒时间和日期。
  • LocalDate、LocalTime 和 LocalDateTime类:都是用于处理日期时间的 API。
  • ZonedDateTime类:可获取特定时区的信息。
  • Duration类:比较日期时间,它持有的时间精确到秒和纳秒。

# 8.2、Clock

Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()和TimeZone.getDefault()。

  • 主要方法如下

  • instant(): 获取当前的时间戳。
  • millis(): 获取当前的时间戳,以毫秒为单位。
  • system(ZoneId zone): 获取特定时区的Clock实例。
  • systemDefaultZone(): 获取默认时区的Clock实例。
  • getZone(): 获取此Clock的时区。
  • withZone(ZoneId zone): 返回此Clock的副本,并将其设置为新的时区。
  • offset(Clock baseClock, Duration offsetDuration): 获取一个新的Clock,与给定的基础Clock偏移一定时间。
  • fixed(Instant fixedInstant, ZoneId zone): 获取一个永远不会改变的Clock。
  • 案例
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );

/**
 * 输出结果:
 * 2021-02-24T12:24:54.678Z
 * 1614169494678
 */
1
2
3
4
5
6
7
8
9

# 8.3、LocalDate

java.time.LocalDate 是一个不可变(immutable)的日期对象,表示一个日期,通常以 yyyy-MM-dd 格式表示。它是线程安全的。

  • 主要的方法:

  • now(): 获取当前的日期。
  • of(int year, int month, int dayOfMonth): 创建 LocalDate 的实例。
  • plusDays(long daysToAdd): 返回加上指定的天数后的 LocalDate 实例。
  • minusDays(long daysToSubtract): 返回减去指定的天数后的 LocalDate 实例。
  • getDayOfMonth(): 返回这个日期的月份中的天数。
  • getDayOfWeek(): 返回这个日期是一个星期中的第几天。
  • getDayOfYear(): 返回这个日期是一年中的第几天。
  • isLeapYear(): 判断该日期是否是闰年。
  • 案例
public class Main {
    public static void main(String[] args){
        LocalDate date = LocalDate.now();
        System.out.println("当前日期为 : "+ date);

        LocalDate specificDate = LocalDate.of(1998, 07, 22);
        System.out.println("特定日期为 : "+ specificDate);

        LocalDate addDays = date.plusDays(5);
        System.out.println("五天后的日期为 : "+ addDays);

        LocalDate minusDays = date.minusDays(5);
        System.out.println("五天前的日期为 : "+ minusDays);

        int day = date.getDayOfMonth();
        System.out.println("这个月的第几天 : "+ day);

        int dayYear = date.getDayOfYear();
        System.out.println("这一年的第几天 : "+ dayYear);
    }
}

/**
 * 输出结果:
 * 当前日期为 : 2022-07-25
 * 特定日期为 : 1998-07-22
 * 五天后的日期为 : 2022-07-30
 * 五天前的日期为 : 2022-07-20
 * 这个月的第几天 : 25
 * 这一年的第几天 : 206
 */
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

# 8.5、LocalTime

java.time.LocalTime 是 Java 8 中用于表示时间的类。与 LocalDate类似,LocalTime 是一个不可变且线程安全的类,它的实例代表了一天中的一个时间。

  • 主要的方法:

  • now(): 获取当前的时间。
  • of(int hour, int minute, int second): 创建 LocalTime 的实例。
  • plusHours(long hoursToAdd): 返回增加指定小时数后的 LocalTime 实例。
  • minusHours(long hoursToSubtract): 返回减去指定小时数后的 LocalTime 实例。
  • getHour(): 获取此时间的小时。
  • getMinute(): 获取此时间的分钟。
  • getSecond(): 获取此时间的秒。
  • 案例
public class Main {
  public static void main(String[] args){
    LocalTime now = LocalTime.now();
    System.out.println("当前时间为 : "+ now);
    
    LocalTime specificTime = LocalTime.of(12,20,25,40);
    System.out.println("特定时间为 : "+ specificTime);
    
    LocalTime morning = now.plusHours(5);
    System.out.println("五小时后的时间为 : "+ morning);
    
    int hour = now.getHour();
    System.out.println("当前小时数为 : "+ hour);
    
    int minute = now.getMinute();
    System.out.println("当前分钟数为 : "+ minute);
    
    int second = now.getSecond();
    System.out.println("当前秒数为 : "+ second);
  }
}

/**
 * 输出结果:
 * 当前时间为 : 22:30:45.123456789
 * 特定时间为 : 12:20:25.000000040
 * 五小时后的时间为 : 03:30:45.123456789
 * 当前小时数为 : 22
 * 当前分钟数为 : 30
 * 当前秒数为 : 45
 */
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

# 8.6、LocalDateTime

java.time.LocalDateTime是一个不可变的日期-时间对象,表示日期和时间,默认格式为 yyyy-MM-dd-HH-mm-ss.zzz。它允许我们存储的日期和时间是无时区的,也就是说 LocalDateTime 是不包含具体时区的(如纽约或者东京等)信息的。

  • 主要的方法:

  • now(): 获取当前日期和时间的 LocalDateTime 实例。
  • of(int year, int month, int dayOfMonth, int hour, int minute): 创建 LocalDateTime 的实例。
  • plusDays(long days): 返回增加指定天数后的 LocalDateTime 的实例。
  • minusDays(long days): 返回减去指定天数后的 LocalDateTime 的实例。
  • getDayOfMonth(): 获取这个日期是这个月的第几天。
  • getDayOfWeek(): 获取这个日期是这周的第几天。
  • getDayOfYear(): 获取这个日期是这年的第几天。
  • getHour(), getMinute(), getSecond(): 分别获取小时、分钟和秒。
  • toLocalDate(): 将此 LocalDateTime 转换为 LocalDate。
  • toLocalTime(): 将此 LocalDateTime 转换为 LocalTime。
  • 案例
public class Main {
  public static void main(String[] args){
    LocalDateTime now = LocalDateTime.now();
    System.out.println("当前日期和时间为: "+ now);
    
    LocalDateTime specificDate = LocalDateTime.of(1995, 8, 25, 14, 30);
    System.out.println("特定日期和时间为 : "+ specificDate);
    
    LocalDateTime tomorrow = now.plusDays(1);
    System.out.println("明天的这个时间为 : "+ tomorrow);
    
    LocalDateTime yesterday = now.minusDays(1);
    System.out.println("昨天的这个时间为 : "+ yesterday);
    
    int dayOfMonth = now.getDayOfMonth();
    System.out.println("今天是这个月的第几天 : "+ dayOfMonth);
    
    int hour = now.getHour();
    System.out.println("现在是几点 : "+ hour);
  }
}

/**
 * 输出结果:
 * 当前日期和时间为: 2023-05-10T15:30:20.420
 * 特定日期和时间为 : 1995-08-25T14:30
 * 明天的这个时间为 : 2023-05-11T15:30:20.420
 * 昨天的这个时间为 : 2023-05-09T15:30:20.420
 * 今天是这个月的第几天 : 10
 * 现在是几点 : 15
 */
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

# 8.7、ZonedDateTime

ZonedDateTime类可用于获取和操作包含时区信息的日期和时间。

  • 主要的方法:

  • now():获取当前日期和时间。
  • parse(String str):从指定的字符串创建 ZonedDateTime 对象。
  • of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone):创建具有完整字段的 ZonedDateTime 对象。
  • getYear(), getMonth(), getDayOfMonth(), getHour(), getMinute(), getSecond():获取 ZonedDateTime 对象的年、月、日、小时、分钟或秒。
  • plusDays(long days), minusWeeks(long weeks):增加或减少 ZonedDateTime 对象的日期或时间。
  • withZoneSameInstant(ZoneId zone):切换ZonedDateTime对象的时区,同时保持相同的瞬时时间。
  • 案例
// 获取当前日期和时间
ZonedDateTime date1 = ZonedDateTime.now();
System.out.println("当前日期和时间: " + date1);

// 使用字符串创建 ZonedDateTime 对象
ZonedDateTime date2 = ZonedDateTime.parse("2020-01-28T19:33:45.345+05:30[Asia/Kolkata]");
System.out.println("指定字符串创建的日期和时间: " + date2);

// 创建具有完整字段的 ZonedDateTime 对象
ZonedDateTime date3 = ZonedDateTime.of(2020, 1, 28, 19, 33, 45, 500, ZoneId.of("Asia/Kolkata"));
System.out.println("指定字段创建的日期和时间: " + date3);

// 获取日期和时间字段
System.out.println("年份: " + date3.getYear());
System.out.println("月份: " + date3.getMonthValue());  // 注意:这个月的数字从1开始
System.out.println("一月中的天: " + date3.getDayOfMonth());
System.out.println("小时: " + date3.getHour());
System.out.println("分钟: " + date3.getMinute());
System.out.println("秒: " + date3.getSecond());

// 增加或减少日期或时间
ZonedDateTime date4 = date3.plusDays(10);
System.out.println("10天后的日期和时间: " + date4);
ZonedDateTime date5 = date3.minusWeeks(2);
System.out.println("两周前的日期和时间: " + date5);

// 切换时区
ZonedDateTime date6 = date3.withZoneSameInstant(ZoneId.of("Europe/Paris"));
System.out.println("巴黎的日期和时间: " + date6);
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

# 8.8、Duration

Duration类用于表示两个日期、时间、日期时间之间的时长。

  • 主要的方法:

  • between(Temporal startInclusive, Temporal endExclusive):创建表示两个时间点之间的持续时间的 Duration。
  • plusDays(long daysToAdd):返回由指定天数增加的此 Duration 的副本。类似的方法还有 plusHours(), plusMinutes(), plusSeconds(), plusMillis(), plusNanos()。
  • minusDays(long daysToSubtract):返回由指定天数减去的此 Duration 的副本。类似的方法还有 minusHours(), minusMinutes(), minusSeconds(), minusMillis(), minusNanos()。
  • 案例
LocalDateTime from = LocalDateTime.of(2015, Month.APRIL, 20, 6, 15, 45);
LocalDateTime to = LocalDateTime.of(2015, Month.APRIL, 20, 6, 30, 55);

// 创建 Duration
Duration duration = Duration.between(from, to); 

// 输出结果
System.out.println("分钟: " + duration.toMinutes()); // 输出:15
System.out.println("秒: " + duration.getSeconds()); // 输出: 905

// 增加天数
Duration increasedDuration = duration.plusDays(2);
System.out.println("增加天数后的持续时间 (秒): " + increasedDuration.getSeconds());

// 减少小时数
Duration decreasedDuration = duration.minusHours(2);
System.out.println("减少小时数后的持续时间 (秒):" + decreasedDuration.getSeconds());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 9、Base64

在 Java 8中,Base64 编码已经成为 Java 类库的标准。

public class Tester {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";
        final String encoded = Base64.getEncoder().encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );
        final String decoded = new String(Base64.getDecoder().decode( encoded ), StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}

/**
 * 输出结果:
 * QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
 * Base64 finally in Java 8!
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 10、Nashorn, JavaScript 引擎

  • 从Java JDK 1.8开始,Java标准库引入了新的JavaScript引擎Nashorn,取代了之前的Rhino引擎。Nashorn使用了新的语言特性,基于JSR 292规范的动态类型系统,它能够将JavaScript代码编译成Java字节码并在Java虚拟机上运行。

  • Nashorn提供了Java和JavaScript之间的无缝互操作性,允许在Java代码中直接调用JavaScript函数,反之亦然。它还支持将JavaScript代码嵌入到Java应用程序中,充分利用Java的强大生态系统。

  • 由于Nashorn是一个嵌入式引擎,因此可以在Java应用程序中灵活地使用它。它可以用于编写脚本任务、动态生成代码、开发插件等应用场景。

  • 尽管目前Nashorn在实际开发中使用较少,但它仍然是一个有价值的工具,特别是在需要与JavaScript进行集成的场景下。

如有需要,可以进一步学习和了解Nashorn的使用方法。