Java8新特性

Interface

新特性

  • default修饰的方法,是普通实例方法,可以用this调用可以被子类继承、重写

    • 如果两个接口都定义了某个default同名方法,某个类同时实现了这两个接口,那么,这个同名方法必须重写,否则编译报错
  • static修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用


函数式编程(Functional Programming, FP)是一种编程范式,它将计算视为数学函数的求值,并强调函数的应用。与命令式编程不同,函数式编程更关注表达式的计算和函数的组合,而不是变量的状态和赋值操作。就像管道符一样,一个函数的输出是下一个函数的输入

Lambda表达式和Stream流都是函数式编程的应用

Java Lambda

Lambda表达式是Java 8引入的一个重要特性,使得我们可以用更加简洁和函数式的方式编写代码。

Lambda表达式可以用于实现函数式接口(即只有一个抽象方法的接口),并在集合操作、并行处理等场景中得到广泛应用。

基本语法

Lambda表达式的基本语法如下:

1
2
(parameters) -> expression
(parameters) -> { statements; }
  • 参数部分:可以是一个或多个参数。如果只有一个参数且类型可以推断,则可以省略括号。
  • 箭头符号-> 将参数与函数体分开。
  • 函数体:可以是单个表达式或一组语句。如果是单个表达式,可以省略大括号和返回关键字。

基础示例

无参数的Lambda表达式

1
2
Runnable runnable = () -> System.out.println("Hello, Lambda!");
new Thread(runnable).start();

单参数的Lambda表达式

1
2
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello, World!");

多参数的Lambda表达式

1
2
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; // 这三个Integer中,前两个是参数,最后一个是返回值
System.out.println(add.apply(5, 3)); // 输出 8

使用Lambda表达式操作集合

过滤和映射集合

1
2
3
4
5
List<String> list = Arrays.asList("one", "two", "three", "four");
list.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.forEach(System.out::println);

集合排序

1
2
3
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.sort((a, b) -> a.compareTo(b));
System.out.println(list); // 输出 [apple, banana, cherry]

函数式接口

常见函数式接口

Java 8 引入了一些常用的函数式接口,位于 java.util.function 包中。下面是一些常用接口及其示例。

Consumer<T>
1
2
Consumer<String> printer = System.out::println;
printer.accept("Hello, Consumer!");
Function<T, R>
1
2
Function<String, Integer> length = String::length;
System.out.println(length.apply("Hello")); // 输出 5
Supplier<T>
1
2
Supplier<String> supplier = () -> "Hello, Supplier!";
System.out.println(supplier.get());
Predicate<T>
1
2
Predicate<Integer> isPositive = x -> x > 0;
System.out.println(isPositive.test(5)); // 输出 true
BiFunction<T, U, R>
1
2
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println(multiply.apply(3, 4)); // 输出 12

实战

Runnable 接口

1
2
3
4
5
6
7
8
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The runable now is using!");
}
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();

Comparator 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<Integer> strings = Arrays.asList(1, 2, 3);

Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;}
});

//Lambda
Collections.sort(strings, (o1, o2) -> o1 - o2);
//分解开
Comparator<Integer> comparator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comparator);

Listener 接口

1
2
3
4
5
6
7
8
9
JButton button = new JButton();
button.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
e.getItem();
}
});
//lambda
button.addItemListener(e -> e.getItem());

Java Stream

Java 8 引入了 Stream API,它提供了一种高效且易于使用的处理数据集合(如集合、数组等)的方式。

Stream API 使得我们能够以声明式的方式编写代码,从而提高代码的可读性和可维护性。

什么是 Stream

Stream 是一个数据元素的序列,它支持各种操作以计算结果。Stream 操作有两种类型:

  • 中间操作:返回一个新的 Stream,这些操作是惰性的,只在终端操作执行时才会执行。
  • 终端操作:产生一个结果(如 ListInteger 等),或者执行副作用(如 forEach)。

Stream 的创建

Stream 可以从多种数据源创建,例如集合、数组、文件等。

从集合创建 Stream

1
2
List<String> list = Arrays.asList("one", "two", "three");
Stream<String> stream = list.stream();

从数组创建 Stream

1
2
String[] array = {"one", "two", "three"};
Stream<String> stream = Arrays.stream(array);

从文件创建 Stream

1
2
3
4
5
6
Path path = Paths.get("file.txt");
try (Stream<String> lines = Files.lines(path)) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}

使用 Stream.of 创建 Stream

1
Stream<String> stream = Stream.of("one", "two", "three");

中间操作

中间操作是惰性的,这意味着它们在调用时不会立即执行,而是在终端操作调用时执行。

filter

过滤元素,保留符合条件的元素。

1
Stream<String> filteredStream = stream.filter(s -> s.length() > 3);

map

将每个元素映射为另一个对象。

1
Stream<Integer> mappedStream = stream.map(String::length);

flatMap

将每个元素转换为一个流,然后将这些流合并为一个流。

1
Stream<String> flatMappedStream = stream.flatMap(s -> Arrays.stream(s.split(" "))); //示例 "a b c" -> {"a", "b", "c"}

sorted

对元素进行排序。

1
Stream<String> sortedStream = stream.sorted();

distinct

去除重复元素。

1
Stream<String> distinctStream = stream.distinct();

peek

对每个元素执行操作并返回一个新的 Stream。主要用于调试。

1
Stream<String> peekedStream = stream.peek(System.out::println);

终端操作

终端操作会触发流的计算,并且流一旦使用终端操作就不能再使用。

forEach

对每个元素执行操作,无返回值。

1
stream.forEach(System.out::println);

collect

将流转换为其他形式。

1
List<String> list = stream.collect(Collectors.toList());

reduce

将流中的元素组合起来,产生一个单一的值。

1
Optional<String> concatenated = stream.reduce((s1, s2) -> s1 + s2);

count

返回流中元素的个数。

1
long count = stream.count();

anyMatch

判断是否有任意一个元素符合给定的条件。

1
boolean anyMatch = stream.anyMatch(s -> s.length() > 3);

allMatch

判断是否所有元素都符合给定的条件。

1
boolean allMatch = stream.allMatch(s -> s.length() > 3);

noneMatch

判断是否没有任何元素符合给定的条件。

1
boolean noneMatch = stream.noneMatch(s -> s.length() > 3);

findFirst

返回流中的第一个元素。

1
Optional<String> firstElement = stream.findFirst();

findAny

返回流中的任意一个元素(适用于并行流)。

1
Optional<String> anyElement = stream.findAny();

toArray()

将流中的元素收集到一个数组中。

1
String[] array = stream.toArray(String[]::new);

collect(Collectors.toList())

将流中的元素收集到一个列表中。

1
List<String> list = stream.collect(Collectors.toList());

collect(Collectors.toSet())

将流中的元素收集到一个集合中,去除重复元素。

1
Set<String> set = stream.collect(Collectors.toSet());

min() & max()

返回流中的最小值和最大值。

1
2
Optional<String> min = stream.min(Comparator.naturalOrder());
Optional<String> max = stream.max(Comparator.naturalOrder());

并行流

Stream API 支持并行处理,可以利用多核处理器来提高性能。

1
2
List<String> list = Arrays.asList("one", "two", "three");
Stream<String> parallelStream = list.parallelStream();

实战

计算字符串的总长度

1
2
3
4
5
List<String> list = Arrays.asList("one", "two", "three");
int totalLength = list.stream()
.mapToInt(String::length)
.sum();
System.out.println(totalLength); // 输出 11

查找最长的字符串

1
2
3
4
List<String> list = Arrays.asList("one", "two", "three");
Optional<String> longest = list.stream()
.max(Comparator.comparingInt(String::length));
longest.ifPresent(System.out::println); // 输出 "three"

过滤、转换并收集结果

1
2
3
4
5
6
List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> result = list.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result); // 输出 [THREE, FOUR]

使用 Stream API 可以很方便地将 List 转换为数组以及将数组转换为 List。以下是两种操作的示例:

将 List 转换为数组

1
2
List<String> list = Arrays.asList("one", "two", "three");
String[] array = list.stream().toArray(String[]::new);

这里的 toArray() 方法接受一个参数,该参数是一个数组构造器引用,用于创建正确大小的数组。

将数组转换为 List

1
2
String[] array = {"one", "two", "three"};
List<String> list = Arrays.stream(array).collect(Collectors.toList());

这里使用了 Arrays.stream() 方法将数组转换为流,并使用 collect(Collectors.toList()) 将流中的元素收集到一个列表中。

总结

  • Stream API 是 Java 8 中引入的强大工具,使我们能够以声明式和函数式的方式处理集合数据
  • 它提供了丰富的操作集合数据的方法,包括中间操作和终端操作,支持顺序和并行处理
  • 熟练掌握 Stream API 可以大大提高代码的可读性和性能。

Java Optional

Java 8 引入了 Optional 类,用于解决空指针异常(NullPointerException)问题。

Optional 是一个容器对象,它可以包含或不包含非空值。通过使用 Optional,可以更加优雅地处理可能为 null 的值,避免因为 null 引起的异常。

创建 Optional

创建包含非空值的 Optional

1
Optional<String> optional = Optional.of("value");

创建可能为空的 Optional

1
Optional<String> optional = Optional.ofNullable(null);

检查 Optional 是否包含值

判断 Optional 是否包含值

1
boolean present = optional.isPresent();

如果 Optional 包含值,则返回 true,否则返回 false。

判断 Optional 是否为空

1
boolean empty = optional.isEmpty();

如果 Optional 不包含值,则返回 true,否则返回 false。这是 Java 11 引入的新方法。

获取 Optional 的值

获取 Optional 中的值

1
String value = optional.get();

如果 Optional 包含值,则返回该值,否则抛出 NoSuchElementException 异常。

处理 Optional 的值

如果值存在,则执行操作

1
optional.ifPresent(val -> System.out.println("Value: " + val));

如果 Optional 包含值,则执行给定的操作,否则什么也不做。

如果值不存在,则执行操作

1
optional.ifEmpty(() -> System.out.println("Value is empty"));

如果 Optional 不包含值,则执行给定的操作,否则什么也不做。这是 Java 11 引入的新方法。

获取默认值

获取 Optional 中的值,如果值不存在则返回默认值

1
String value = optional.orElse("default");

如果 Optional 包含值,则返回该值,否则返回指定的默认值。

获取 Optional 中的值,如果值不存在则使用供应函数返回值作为默认值

1
String value = optional.orElseGet(() -> "default"); // 如果为空,则 value = "default"

如果 Optional 包含值,则返回该值,否则调用提供的供应函数,并返回其结果作为默认值。

抛出异常

如果值不存在,则抛出指定的异常

1
String value = optional.orElseThrow(() -> new IllegalArgumentException("Value is empty"));

如果 Optional 包含值,则返回该值,否则抛出指定的异常。

使用 map 转换值

1
Optional<Integer> lengthOptional = optional.map(String::length);

如果 Optional 包含值,则对该值应用给定的函数,并返回包含结果的 Optional;否则返回一个空的 Optional。

过滤值

1
Optional<String> filteredOptional = optional.filter(val -> val.startsWith("prefix"));

如果 Optional 包含值且满足给定的谓词条件,则返回包含该值的 Optional;否则返回一个空的 Optional。

组合多个 Optional

组合两个 Optional

1
Optional<String> resultOptional = optional1.flatMap(val1 -> optional2.map(val2 -> val1 + val2));

如果两个 Optional 都包含值,则应用提供的函数,否则返回一个空的 Optional。

组合多个 Optional

1
Optional<String> resultOptional = OptionalUtils.combine(optional1, optional2, optional3, ..., (val1, val2, val3, ...) -> /* Combine values */);

如果所有的 Optional 都包含值,则应用提供的函数,否则返回一个空的 Optional。这个方法需要一个工具类 OptionalUtils 来实现,因为标准库中并没有直接提供类似的组合方法。

示例

使用 Optional 避免空指针异常

1
2
Optional<String> optional = Optional.ofNullable(getValue());
String result = optional.orElse("default");

这里 getValue() 方法可能返回 null,使用 Optional 可以避免空指针异常。

使用 map 转换值

1
2
Optional<String> optional = Optional.ofNullable(getString());
Optional<Integer> lengthOptional = optional.map(String::length);

如果字符串不为 null,则获取其长度;否则返回一个空的 Optional。

总结

Optional 类是 Java 8 中引入的一种处理可能为 null 的值的方式,它提供了一系列方法来更加优雅地处理可能为空的值,避免空指针异常。

虽然使用 Optional 会增加一些代码的复杂性,但它能够提高代码的健壮性和可读性,建议在合适的场景中使用 Optional。

Java Date-Time API

java.time 包解决了 java.util.Date 的大部分痛点

  • 非线程安全
  • 时区处理麻烦
  • 各种格式化、和时间计算繁琐
  • 设计有缺陷,Date 类同时包含日期和时间;还有一个 java.sql.Date,容易混淆。

java.time 主要类

java.util.Date 既包含日期又包含时间,而 java.time 把它们进行了分离

1
2
3
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //时间 format: HH:mm:ss

LocalDate - 日期

LocalDate 类表示一个日期,不包含时间和时区信息。

创建 LocalDate

1
2
LocalDate date = LocalDate.now();  // 获取当前日期
LocalDate date = LocalDate.of(2022, 5, 18); // 指定年月日创建日期

获取日期信息

1
2
3
int year = date.getYear();
int month = date.getMonthValue();
int day = date.getDayOfMonth();

操作日期

1
2
LocalDate tomorrow = date.plusDays(1);  // 增加一天
LocalDate yesterday = date.minusDays(1); // 减少一天

LocalTime - 时间

LocalTime 类表示一个时间,不包含日期和时区信息。

创建 LocalTime

1
2
LocalTime time = LocalTime.now();  // 获取当前时间
LocalTime time = LocalTime.of(12, 30, 45); // 指定时分秒创建时间

获取时间信息

1
2
3
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();

操作时间

1
2
LocalTime nextHour = time.plusHours(1);  // 增加一小时
LocalTime prevHour = time.minusHours(1); // 减少一小时

LocalDateTime - 日期时间

LocalDateTime 类表示一个日期和时间,不包含时区信息。

创建 LocalDateTime

1
2
LocalDateTime dateTime = LocalDateTime.now();  // 获取当前日期时间
LocalDateTime dateTime = LocalDateTime.of(2022, 5, 18, 12, 30, 45); // 指定年月日时分秒创建日期时间

获取日期时间信息

1
2
LocalDate date = dateTime.toLocalDate();  // 获取日期部分
LocalTime time = dateTime.toLocalTime(); // 获取时间部分

操作日期时间

1
2
LocalDateTime nextHour = dateTime.plusHours(1);  // 增加一小时
LocalDateTime prevHour = dateTime.minusHours(1); // 减少一小时

ZonedDateTime - 带时区的日期时间

ZonedDateTime 类表示一个带有时区的日期时间。

创建 ZonedDateTime

1
2
ZonedDateTime zonedDateTime = ZonedDateTime.now();  // 获取当前带时区的日期时间
ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Asia/Shanghai")); // 指定时区创建日期时间

获取时区信息

1
ZoneId zoneId = zonedDateTime.getZone();

日期时间格式化与解析

Java Date-Time API 提供了 DateTimeFormatter 类用于日期时间的格式化和解析。

日期时间格式化

1
2
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = dateTime.format(formatter);

日期时间解析

1
2
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime parsedDateTime = LocalDateTime.parse("2022-05-18 12:30:45", formatter);

日期时间间隔

Java Date-Time API 提供了 DurationPeriod 类来表示时间间隔。

Duration - 时间间隔

1
2
Duration duration = Duration.between(dateTime1, dateTime2);
long seconds = duration.getSeconds();

Period - 日期间隔

1
2
Period period = Period.between(date1, date2);
int days = period.getDays();

其他常用功能

时间单位

1
long minutes = ChronoUnit.MINUTES.between(time1, time2);

日期调整器

1
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());

总结

  • Java Date-Time API 提供了一套强大且易于使用的日期和时间处理功能,包括日期、时间、日期时间、时区、格式化、解析、间隔等。

  • 它的设计更加清晰、类型安全,并且支持线程安全。建议在项目中尽可能使用新的 Date-Time API,避免使用旧的 java.util.Datejava.util.Calendar 类。