从零开始学Java8新特性(使用方法和注意事项

简介

Java8引入了很多新特性,作为一个懒人,一直觉得掌握部分常用的即可,但在新项目中发现同事代码充分利用了Java8特性,不仅代码优雅,而且层次分明,这些特性很大程度改变了我们的代码风格。如果不学习,很有可能看不懂同事的代码,那真的就尴尬啦。故萌生重学一遍Java8新特性,查漏补缺,也供将来面试复习使用。

1. Lambda表达式及函数表达式

1.1 介绍

Lambda表达式是Java8中引入的一种新的语法,用于简化匿名内部类的创建。我们可以把Lambda表达式看成一段可以传递的代码,使用它,可以使代码更加简洁、紧凑。使用Lambda表达式时,接口必须是函数式接口(即只有一个抽象方法的接口)。

1.2 Lambda表达式demo

( )-> System.out.println("hello,world");

(int x, int y) -> x + y;

(x, y) -> {
	int sum = x + y;
	System.out.println("Sum: " + sum);
	return sum;
}
  1. ()里面是参数列表,Lambda表达式可以包括零或多个参数,用 “,”分隔
  2. Lambda的主体可以是一个表达式或者一组表达式,它的结果将成为Lambda表达式的返回值。如果是一组表达式,必须使用“{}”包裹。

1.3 Lambda表达式声明

Demo:

  <函数式接口>  <变量名> = (参数1,参数2...) -> {
                //方法体
    }
  1. Lambda表达式的参数类型可以省略。
// 参数类型明确
Function<Integer, Integer> square = (Integer x) -> x * x;
// 参数类型推断
Function<Integer, Integer> squareInferred = (x) -> x * x;
  1. Lambda表达式的返回值类型可以省略。
// 显式声明返回类型
Comparator<String> lengthComparator = (String s1, String s2) -> s1.length() - s2.length();
// 返回类型推断
Comparator<String> lengthComparatorInferred = (s1, s2) -> s1.length() - s2.length();
  1. Lambda表达式若访问了局部变量,则局部变量必须是final的,不加会自动加,只要修改该局部变量就会报错。

1.3 函数式接口Demo

@FunctionalInterface
interface MyFunctionalInterface {
    void myMethod(); // 抽象方法

    // 默认方法
    default void defaultMethod() {
        System.out.println("Default method");
    }

    // 静态方法
    static void staticMethod() {
        System.out.println("Static method");
    }
}

  1. 函数式接口必须是只有单一抽象方法的接口,这个抽象方法的方法体由Lambda表达式来实现。
  2. 如果想在函数式接口增加其他方法而不影响它是函数式接口,可以使用默认方法和静态方法(后面详写)
  3. 函数式接口通常用“@FunctionInterface”注解来明确它是函数式,使用它后编译器会自动检查这个接口是否符合函数式接口的条件。

2. Stream API

Stream API引入了一种新的抽象序列类型,可以更轻松地对集合进行操作,支持串行和并行。它提供了一套丰富的操作流水线式处理数据,实现更简洁、高效、并行花的集合操作。

2.1 创建流

可以通过集合、数组、I/O通道等方式创建流

List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> streamFromList = list.stream();

int[] array = {1, 2, 3, 4, 5};
IntStream streamFromArray = Arrays.stream(array);

Stream<String> streamOfStrings = Stream.of("apple", "banana", "orange");

2.2 中间操作

中间操作是对流进行转换的操作,它不会自动执行,而是在终端操作被调用时才会执行。

List<String> filteredList = list.stream()
                                .filter(s -> s.startsWith("a"))//过滤
                                .map(String::toUpperCase)
                                .distinct()//去重
                                .sorted()//排序
                                .collect(Collectors.toList());
2.2.1 map

map用于将每个元素映射为另一个元素,map接收一个函数作为参数,该函数被应用于流中的每一个元素,并将每个输入元素映射为一个输出元素。映射后的结果形成一个新的流,不会改变原始流中的元素。通常用于进行一些转换提取计算操作。
具体语法:

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

其中T是流中元素的类型,R是映射后元素的类型。mapper则是一个函数,接收T类型元素,返回R类型结果。
demo:

words.stream().map(String::toUpperCase) -- 将字符串转大写
students.stream().map(Student::getName) --获取对象列表的属性集合
2.2.2 reduce

reduce 合并流的元素并产生单个值。
reduce方法签名:
Stream.java

T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);

IntStream.java

int reduce(int identity, IntBinaryOperator op);

LongStream.java

long reduce(int identity, LongBinaryOperator op);
  • identity = 默认值或初始值,如果缺少这个参数,则没有默认值或初始值,并返回Optional(下面有介绍)
  • BinaryOperator = 函数式接口,取两个值产生新的值。BinaryOperator接口用于执行 Lambda 表达式并返回一个 T 类型返回值。
Stream.of(1,2,3).reduce(0,(a,b) -> a+b); -- 求和

public static int sum(int a,int b) { return a + b;}
Stream.of(1,2,3).reduce(0,Integer::sum);

2.3 终端操作

终端操作会触发流的遍历和执行操作。常见包括 forEach、collect、reduce、count等。

list.stream()
    .forEach(System.out::println);
List<String> collectedList = list.stream()
                                .filter(s -> s.length() > 5)
                                .collect(Collectors.toList());
Optional<String> concatenated = list.stream()
                                    .reduce((s1, s2) -> s1 + ", " + s2);

2.4 并行流

使用parallelStream将流转换为并行流。

List<String> parallelList = list.parallelStream()
                                .filter(s -> s.length() > 5)
                                .collect(Collectors.toList());

2.5 Optional和Nullable支持

Optional用于存放T类型值或者Null,它提供一些接口来判空防止空指针异常,更优雅、更安全。配合Stream流通过findFirst、findAny、orElse等方法处理可能为空的情况。

-- 1. 创建Optional对象
Optional<String> nonEmptyOptional = Optional.of("Hello, Optional!");

String value = /* 可能为null的值 */;
Optional<String> nullableOptional = Optional.ofNullable(value);

-- 2. 获取Optional的值

Optional<String> nonEmptyOptional = Optional.of("Hello, Optional!");
String value = nonEmptyOptional.get(); -- 如果Optional包含Null值,会抛出*NoSuchElementException*异常

String value = nullableOptional.orElse("Default Value");
-- 类似 String value = StringUtils.isNotEmpty(value) : value : "Default Value"

-- 3. 判断是否包含空值
if (nonEmptyOptional.isPresent()) {
    // 执行操作
}

-- 4. 条件执行(isPresent()判断是否包含值)
nonEmptyOptional.ifPresent(val -> System.out.println("Value: " + val));

-- 5.默认值和函数处理
-- orElseGet() 方法允许提供一个Supplier,在Option为空时计算默认值
String result = nullableOptional.orElseGet(() -> "Default Value");
-- map() 方法允许对Optional中的值进行转换
Optional<String> upperCaseOptional = nonEmptyOptional.map(String::toUpperCase);

-- 6.过滤
-- filter 方法允许在 Optional 包含值的情况下应用一个条件
Optional<String> filteredOptional = nonEmptyOptional.filter(s -> s.length() > 5);

-- 7.链式调用
-- 可以通过链式调用组合多个操作,使代码更为流畅和可读:
String result = Optional.ofNullable(nullableValue)
                        .map(String::toUpperCase)
                        .orElse("Default Value");

-- 8. 其他用法
Optional<String> firstElement = list.stream().findFirst();

6. 收集器

通过collect方法可以使用各种内置的收集器,将流中的元素汇总到一个集合中,如toList、toSet、toMap等。

Map<Integer, List<String>> groupedByLength = list.stream()
.collect(Collectors.groupingBy(String::length));
Set<String> set = stream.collect(Collectors.toSet());
//将流中的元素按照指定的规则收集到一个Map中。
Map<Integer, String> map = stream.collect(Collectors.toMap(String::length, Function.identity()));
//将流中的元素连接成一个字符串。
String result = stream.collect(Collectors.joining(", "));
//根据某个属性对流中的元素进行分组
Map<Integer, List<String>> groupedByLength = stream.collect(Collectors.groupingBy(String::length));
//根据某个条件对流中的元素进行分区,分区只有两个区域(true和false)
Map<Boolean, List<String>> partitioned = stream.collect(Collectors.partitioningBy(s -> s.length() > 3));
//统计流中的元素个数。
long count = stream.collect(Collectors.counting());
//统计流中的元素的统计信息,包括count、sum、min、average、max
IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(String::length));
//对流中的元素进行归约操作
Optional<String> concatenated = stream.collect(Collectors.reducing((s1, s2) -> s1 + s2));
//对收集器的结果进行转换
List<String> result = stream.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));

3. 函数式接口

4. 默认方法

接口中新增了static和default方法,这两种方法可以拥有方法体。

4.1 static方法
public interface DefalutTest {
    static int a =5;
    default void defaultMethod(){
        System.out.println("DefalutTest defalut 方法");
    }

    int sub(int a,int b);

    static void staticMethod() {
        System.out.println("DefalutTest static 方法");
    }
}

接口里的静态方法,即static方法不会被继承或者实现(即不能被实现类调用,只能被自身调用),但是静态变量会被继承。接口里s

4.2 default方法

default方法可以被子接口继承,被实现类调用、重写。
如果一个类实现了多个接口,且这些接口没有继承关系,如果这些接口有同名同参数的default方法,则接口实现类会报错,必须指定实现了哪个接口的default方法。

5. 新的日期/时间API

Java8引入了全新的日期和时间API,位于 java.time 包下。这个API解决了java.util.Date和java.util.Calendar存在的一些问题,并提供了更加清晰、易用、线程安全的日期和时间处理方式。

5.1 LocalDate

LocalDate表示一个不带时区信息的日期。它提供了许多方法来处理日期,例如计算两个日期之间的天数差异、获取年、月、日等。

//1. 获取当前日期
LocalDate currentDate = LocalDate.now();
System.out.println("当前日期: " + currentDate);
//结果:当前日期: 2023-12-12

//2. 获取年月日
int year = currentDate.getYear();
int month = currentDate.getMonthValue();
int day = currentDate.getDayOfMonth();
//3. 计算两个日期之间天数差异
LocalDate anotherDate = LocalDate.of(2023, 12, 31);
long daysDiff = currentDate.until(anotherDate, ChronoUnit.DAYS);
System.out.println("两个日期之间的天数差异: " + daysDiff);
//4. 检查是否为闰年
boolean isLeapYear = currentDate.isLeapYear();
System.out.println("是否为闰年: " + isLeapYear);
//5. 增加或减少天数、月数、年数
LocalDate newDate = currentDate.plusDays(7);
System.out.println("增加7天后的日期: " + newDate);
LocalDate minusMonth = currentDate.minusMonths(2);
System.out.println("减少2个月后的日期: " + minusMonth);
//6.  比较两个日期
LocalDate earlierDate = LocalDate.of(2023, 6, 1);
LocalDate laterDate = LocalDate.of(2023, 9, 1);
boolean isBefore = earlierDate.isBefore(laterDate);
boolean isAfter = laterDate.isAfter(earlierDate);
System.out.println("前一个日期是否在后一个日期之前: " + isBefore);
System.out.println("后一个日期是否在前一个日期之后: " + isAfter);

5.2 LocalTime

LocalTime表示一个不带时区信息的时间。它提供了获取小时、分钟、秒等信息的方法。

//1. 获取当前时间
LocalTime currentTime = LocalTime.now();
System.out.println("当前时间: " + currentTime);
//2. 获取小时、分钟、秒
int hour = currentTime.getHour();
int minute = currentTime.getMinute();
int second = currentTime.getSecond();

System.out.println("小时: " + hour);
System.out.println("分钟: " + minute);
System.out.println("秒: " + second);
//3. 获取纳秒(nanoseconds)
int nano = currentTime.getNano();
System.out.println("纳秒: " + nano);
//4. 构造新的时间对象
LocalTime specificTime = LocalTime.of(12, 30, 45);
System.out.println("指定时间: " + specificTime);
//5. 增加或减少小时、分钟、秒
LocalTime newTime = currentTime.plusHours(2);
System.out.println("增加2小时后的时间: " + newTime);

LocalTime minusMinutes = currentTime.minusMinutes(15);
System.out.println("减少15分钟后的时间: " + minusMinutes);
//6. 判断时间先后
LocalTime earlierTime = LocalTime.of(10, 30);
LocalTime laterTime = LocalTime.of(15, 45);
boolean isBefore = earlierTime.isBefore(laterTime);
boolean isAfter = laterTime.isAfter(earlierTime);
System.out.println("前一个时间是否在后一个时间之前: " + isBefore);
System.out.println("后一个时间是否在前一个时间之后: " + isAfter);
//7. 格式化时间
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
String formattedTime = currentTime.format(formatter);
System.out.println("格式化后的时间: " + formattedTime);

5.3 LocalDateTime

LocalDateTime表示一个不带时区信息的日期和时间。它是LocalDate和LocalTime的组合。

//1. 获取当前日期和时间:
LocalDateTime currentDateTime = LocalDateTime.now();
System.out.println("当前日期和时间: " + currentDateTime);
//当前日期和时间: 2023-12-12T17:00:15.636

//2. 获取日期和时间的各个部分:
int year = currentDateTime.getYear();
int month = currentDateTime.getMonthValue();
int day = currentDateTime.getDayOfMonth();
int hour = currentDateTime.getHour();
int minute = currentDateTime.getMinute();
int second = currentDateTime.getSecond();
System.out.println("年: " + year);
System.out.println("月: " + month);
System.out.println("日: " + day);
System.out.println("小时: " + hour);
System.out.println("分钟: " + minute);
System.out.println("秒: " + second);

//3. 构造新的日期和时间对象:
LocalDateTime specificDateTime = LocalDateTime.of(2023, 6, 15, 12, 30);
System.out.println("指定的日期和时间: " + specificDateTime);
//4. 增加或减少年、月、日、小时、分钟、秒:
LocalDateTime newDateTime = currentDateTime.plusYears(1)
        .minusMonths(2)
        .plusDays(7)
        .minusHours(3)
        .plusMinutes(15)
        .minusSeconds(30);
System.out.println("修改后的日期和时间: " + newDateTime);
//5. 判断日期和时间先后:

LocalDateTime earlierDateTime = LocalDateTime.of(2023, 6, 1, 10, 30);
LocalDateTime laterDateTime = LocalDateTime.of(2023, 6, 1, 15, 45);

boolean isBefore = earlierDateTime.isBefore(laterDateTime);
boolean isAfter = laterDateTime.isAfter(earlierDateTime);

System.out.println("前一个日期和时间是否在后一个日期和时间之前: " + isBefore);
System.out.println("后一个日期和时间是否在前一个日期和时间之后: " + isAfter);
//6. 格式化日期和时间:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = currentDateTime.format(formatter);
System.out.println("格式化后的日期和时间: " + formattedDateTime);

5.4 Instant

Instant表示时间线上的一个点,通常与协调世界时(UTC)一起使用。它可以用于表示时间戳。

Instant instant = Instant.now();

5.5 Duration和Period

Duration表示两个时间点之间的持续时间,而Period表示两个日期之间的间隔。

LocalDateTime start = LocalDateTime.of(2023, 1, 1, 12, 0);
LocalDateTime end = LocalDateTime.of(2023, 12, 31, 23, 59);

Duration duration = Duration.between(start, end);
Period period = Period.between(LocalDate.of(2023, 1, 1), LocalDate.of(2023, 12, 31));

5.6 ZoneId和ZonedDateTime

ZoneId表示时区,而ZonedDateTime表示带有时区信息的日期和时间。

ZoneId newYorkZone = ZoneId.of("America/New_York");
ZonedDateTime newYorkTime = ZonedDateTime.now(newYorkZone);

5.7 DateTimeFormatter

DateTimeFormatter用于格式化和解析日期时间对象。

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

5.8 扩展:时间对象的格式化和序列化

由于数据库时区问题或者前后端传参等情况,传时间参数时总会出现各种各样的问题,但如果合理使用@DateTimeFormat 和 @JsonFormat 这两个注解,会省去很多麻烦。

@JsonFormat
作用范围:主要用于Jackson库中,作为字段、方法、构造函数的注解,用于指定在将Java对象序列化成JSON字符串或从JSON字符串反序列化为Java对象时的日期和时间格式。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")

@DateTimeFormat
作用范围:主要用于SpringMVC中,作为Controller方法参数的注解,用于指定日期和时间的格式化方式,将请求中时间字符串参数转化成Date 或 LocalDate 类型。

@RequestMapping("/example")
public String example(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
    // 方法体
}
或者也可以用作请求对象的字段上,用于格式化前端传过来的时间对象。

@JSONField
作用范围:主要用在java反序列化时进行属性的匹配。例如数据里key是name,实体类是userName,则使用@JSONField(name=”name”)则可以实现映射关系。

@JsonProperty
作用范围:主要用在java序列化时,使用@JsonProperty注解里value指定的值,反序列化时会将值赋值到属性字段上面

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值