最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Java 8 核心新特性
时间:2026-08-03 10:18:57 编辑:袖梨 来源:一聚教程网
前置认知:Java8是Java里程碑版本,核心革新是引入函数式编程,核心作用是将业务逻辑作为参数传递,彻底简化匿名内部类、集合遍历、空值处理等冗余代码,大幅提升代码简洁度与开发效率。

一、Lambda 表达式
Lambda表达式是Java8核心语法糖,核心作用是简化匿名内部类写法,实现行为参数化,把业务逻辑像变量一样传递给方法,大幅精简代码。
Java8之前,传递简单业务逻辑必须编写冗长的匿名内部类,有效代码占比极低。而Lambda可直接精简核心逻辑,代码更直观。核心使用前提(新手必记):Lambda表达式仅能配合函数式接口使用,非函数式接口无法使用Lambda。
1.1 核心语法结构
通用格式:(参数列表) -> { 方法逻辑代码 }
语法拆解(零基础秒懂):
(参数列表):和普通方法的参数列表一致,无参数写空括号,单个参数可省略括号->:Lambda专属符号,固定写法,相当于“指向、执行”的意思{}:方法体,存放具体逻辑,单行代码可省略大括号和return
简化规则(日常开发90%场景都用简化写法):
参数只有1个:可省略参数括号
方法体只有1行代码:可省略大括号,有返回值时自动省略return
无参数、多行代码:必须保留括号和大括号
1.2 函数式接口(前置概念)
函数式接口定义:有且仅有一个抽象方法的接口。Lambda表达式本质就是对这个唯一抽象方法的快速实现。
Java8提供 @FunctionalInterface 注解,用于编译期校验接口是否符合函数式接口规范。该注解可省略,只要接口仅有一个抽象方法,就是函数式接口,只是缺少编译校验。
1.3 完整可运行代码示例
复制代码import java.util.ArrayList;import java.util.List;/** * Lambda表达式零基础演示 * 包含普通写法、简化写法、集合遍历实战 */public classLambdaDemo {public staticvoidmain(String[] args) {// 1. 无参数Lambda演示(Runnable是典型函数式接口)// 旧写法:匿名内部类,代码冗余new Thread(newRunnable() {@Overridepublic voidrun() { System.out.println("旧写法:线程执行任务"); } }).start(); // Lambda简化写法(无参数,单行逻辑)new Thread(() -> System.out.println("Lambda写法:线程执行任务")).start();// 2. 单参数Lambda演示(集合遍历forEach) List<String> nameList = new ArrayList<>(); nameList.add("张三"); nameList.add("李四"); nameList.add("王五");// 旧写法:增强for循环for (String name : nameList) { System.out.println("普通循环:" + name); } // Lambda简化遍历(单参数省略括号,单行省略大括号) nameList.forEach(name -> System.out.println("Lambda遍历:" + name));// 3. 多参数、多行逻辑Lambda演示// 自定义计算逻辑,两个参数,多行代码 calculate(10, 20, (a, b) -> {int sum= a + b; System.out.println("两数之和:" + sum);return sum; }); } // 自定义方法,参数为函数式接口,支持Lambda传参public staticvoidcalculate(int x, int y, Calc calc) { calc.calc(x, y); }}// 自定义函数式接口@FunctionalInterfaceinterface Calc {int calc(int a, int b);}1.4 本节高频面试题(含标准答案)
题1:简述Lambda表达式的核心作用与使用前提
答:核心作用是简化匿名内部类代码,实现行为参数化,让代码更简洁优雅。使用前提是必须依托函数式接口,接口有且仅有一个抽象方法,Lambda用于对该抽象方法做逻辑实现。
题2:Lambda表达式的语法简化规则有哪些?
答:单参数可省略参数括号;单行方法体可省略大括号,有返回值自动省略return;无参数、多参数、多行逻辑需保留对应括号和大括号,无特殊简化。
题3:Lambda表达式能否访问外部局部变量?有什么限制?
答:可以访问外部局部变量,但变量必须是隐式final(不可二次赋值)。Java8无需手动加final,只要变量未被二次修改,编译器自动判定为常量,修改会直接编译报错。
二、函数式接口
上章节讲解了函数式接口的基础概念,本章重点讲解Java8内置的四大核心函数式接口。这四个接口是官方封装的通用接口,无需开发者自定义,是Lambda、Stream流的核心基础,适配绝大多数业务逻辑场景。
所有内置函数式接口均位于 java.util.function 包下,无需死记硬背,重点区分入参、返回值、适用场景即可,其余衍生接口原理完全一致。
2.1 Predicate 断言型接口
核心特点:入参1个,返回值boolean(true/false)
核心用途:做条件判断、数据筛选,接收参数后返回真假结果,是Stream过滤操作的底层依赖接口。
抽象方法:boolean test(T t)
复制代码import java.util.function.Predicate;/** * Predicate断言接口演示:条件判断、数据筛选 */public classPredicateDemo {public staticvoidmain(String[] args) {// 场景:判断数字是否大于10 Predicate<Integer> predicate = num -> num > 10;// 执行判断 System.out.println(predicate.test(5)); // false System.out.println(predicate.test(15)); // true// 进阶:多条件拼接(且、或、非)// 判断:大于5 且 小于20 Predicate<Integer> p1 = num -> num > 5; Predicate<Integer> p2 = num -> num < 20; System.out.println(p1.and(p2).test(10)); // true }}2.2 Consumer 消费型接口
核心特点:入参1个,无返回值
核心用途:消费处理数据,接收参数后执行业务逻辑(遍历、打印、修改),无返回值,常用于集合遍历、数据批量处理。
抽象方法:void accept(T t)
复制代码import java.util.function.Consumer;/** * Consumer消费接口演示:处理数据、无返回值 */public classConsumerDemo {public staticvoidmain(String[] args) {// 场景:接收姓名,格式化打印 Consumer<String> consumer = name -> System.out.println("用户姓名:" + name); consumer.accept("张三"); consumer.accept("李四");// 进阶:andThen 连续处理数据 Consumer<String> c1 = s -> System.out.print("原始姓名:" + s); Consumer<String> c2 = s -> System.out.println(",姓名长度:" + s.length());// 先执行c1,再执行c2 c1.andThen(c2).accept("王五"); }}2.3 Function 函数型接口
核心特点:入参1个,返回值1个(入参和返回值类型可不同)
核心用途:数据转换、类型映射,接收参数后加工处理,返回全新数据,入参和返回值类型可不同,是Stream数据转换的核心接口。
抽象方法:R apply(T t)
复制代码import java.util.function.Function;/** * Function函数接口演示:数据转换、映射 */public classFunctionDemo {public staticvoidmain(String[] args) {// 场景:字符串转整数 Function<String, Integer> function = str -> Integer.parseInt(str); Integer num= function.apply("123"); System.out.println("转换后的数字:" + num);// 进阶:链式转换 字符串转数字,再计算平方 Function<String, Integer> f1 = s -> Integer.parseInt(s); Function<Integer, Integer> f2 = n -> n * n; // 先执行f1,再执行f2Integer result= f1.andThen(f2).apply("10"); System.out.println("链式转换结果:" + result); // 100 }}2.4 Supplier 供给型接口
核心特点:无入参,有返回值
核心用途:生成数据,无需传入参数,直接返回数据,常用于默认值生成、随机数据创建、对象实例化。
抽象方法:T get()
复制代码import java.util.Random;import java.util.function.Supplier;/** * Supplier供给接口演示:生成数据、无参有返回 */public classSupplierDemo {public staticvoidmain(String[] args) {// 场景:生成随机100以内整数 Supplier<Integer> supplier = () -> new Random().nextInt(100); System.out.println("随机数:" + supplier.get()); System.out.println("随机数:" + supplier.get());// 场景:获取默认字符串 Supplier<String> strSupplier = () -> "默认用户"; System.out.println(strSupplier.get()); }}2.5 本节高频面试题(含标准答案)
题1:简述Java8四大核心函数式接口的区别与适用场景
答:Predicate(断言):有参无返,返回布尔,用于条件筛选;Consumer(消费):有参无返,用于数据处理;Function(函数):有参有返,用于数据转换;Supplier(供给):无参有返,用于数据生成。
题2:@FunctionalInterface注解的作用是什么?可以省略吗?
答:作用是编译期校验接口是否只有一个抽象方法,避免定义错误。可以省略,只要接口满足函数式接口规范,依然可配合Lambda使用,只是失去编译校验功能。
题3:函数式接口中可以有默认方法和静态方法吗?
答:可以。函数式接口仅限制抽象方法只能有一个,默认方法、静态方法属于非抽象方法,数量不限,不影响函数式接口的判定。
三、Stream API
Stream API是Java8优化集合操作的核心工具,专门用于简化List、Set、Map的遍历、筛选、转换、聚合、分组等操作,替代传统繁琐的for循环+嵌套判断写法。
核心区分:集合用于存储数据,Stream用于处理数据。Stream不存储任何数据,仅对集合数据做流水线处理,操作完成后自动销毁。
Stream核心特性:惰性求值。中间操作仅记录处理逻辑,不会立即执行,只有触发终端操作后,所有逻辑才会一次性执行,减少集合遍历次数,提升处理性能。
Stream操作分为两类(新手必记):
中间操作:filter、map、sorted等,返回新Stream,可链式拼接,惰性执行
终端操作:collect、forEach、reduce、count等,触发执行,结束流水线
3.1 核心操作详解+完整代码示例
包含五大高频操作:filter过滤、map转换、collect收集、reduce聚合、完整流式流程
复制代码import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;/** * Stream API 零基础完整实战 * 包含filter、map、collect、reduce核心操作 */public classStreamDemo {public staticvoidmain(String[] args) {// 初始化测试集合 List<Integer> numList = new ArrayList<>(); numList.add(1); numList.add(2); numList.add(3); numList.add(4); numList.add(5); numList.add(6);// 1. filter 中间操作:过滤数据(保留符合条件的数据)// 场景:保留所有偶数 List<Integer> evenList = numList.stream() .filter(num -> num % 2 == 0) // 筛选条件:能被2整除 .collect(Collectors.toList()); // 终端操作:流转集合 System.out.println("过滤后的偶数:" + evenList);// 2. map 中间操作:数据转换、映射// 场景:所有数字乘以2 List<Integer> doubleList = numList.stream() .map(num -> num * 2) // 数据加工:每个数*2 .collect(Collectors.toList()); System.out.println("翻倍后的数字:" + doubleList);// 3. reduce 终端操作:聚合计算(求和、求最值、累积运算)// 场景:集合所有数字求和Integer sum= numList.stream() .reduce(0, Integer::sum); // 初始值0,累加所有元素 System.out.println("集合总和:" + sum);// 4. 完整流式链式操作(开发最常用)// 需求:筛选偶数 -> 数字翻倍 -> 收集为新集合 List<Integer> resultList = numList.stream() .filter(num -> num % 2 == 0) // 过滤偶数 .map(num -> num * 2) // 数据翻倍 .sorted() // 自然排序 .collect(Collectors.toList()); // 收集结果 System.out.println("链式处理最终结果:" + resultList);// 5. 统计操作:计数long count= numList.stream().count(); System.out.println("集合元素总数:" + count); }}3.2 进阶核心操作(分组、分区、多条件处理)+ 通俗总结
上文讲解了Stream基础的过滤、转换、聚合操作,实际开发中,数据分组、分区归类是高频刚需(订单分组、用户分层、数据统计)。本节重点讲解单条件分组、多条件多级分组、布尔分区三种核心进阶用法,覆盖主流业务数据归类场景。
3.2.1 单条件分组(groupingBy 基础用法)
根据单个字段或单个自定义条件对数据归类,相同条件的元素归为一组,最终返回 Map<分组键, 元素集合>,是日常开发最常用的分组方式。
复制代码import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.stream.Collectors;/** * Stream 单条件分组演示 * 场景:根据学生性别分组 */public classStreamGroupSingleDemo {public staticvoidmain(String[] args) {// 初始化学生测试数据 List<Student> studentList = new ArrayList<>(); studentList.add(new Student("张三", "男", 18)); studentList.add(new Student("李四", "女", 17)); studentList.add(new Student("王五", "男", 19)); studentList.add(new Student("赵六", "女", 18));// 单条件分组:根据性别分组 Map<String, List<Student>> genderGroup = studentList.stream() .collect(Collectors.groupingBy(Student::getGender)); // 遍历分组结果 genderGroup.forEach((gender, students) -> { System.out.println("性别【" + gender + "】分组数据:" + students); }); }}// 学生实体类class Student {private String name;private String gender;private Integer age;public Student(String name, String gender, Integer age) {this.name = name;this.gender = gender;this.age = age; } // getter方法public String getGender() { return gender; }public String getName() { return name; }public Integer getAge() { return age; }@Overridepublic String toString() {return "Student{name='" + name + "', age=" + age + "}"; }}3.2.2 多条件分组(多级分组)
针对复杂业务场景,支持两层及以上条件嵌套分组。先通过第一级条件粗分组,再在每组内通过第二级条件细分组,返回嵌套Map结构,适配多维数据统计。
复制代码import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.stream.Collectors;/** * Stream 多条件多级分组演示 * 场景:先按性别分组,再按是否成年(18岁)二次分组 */public classStreamGroupMultiDemo {public staticvoidmain(String[] args) { List<Student> studentList = new ArrayList<>(); studentList.add(new Student("张三", "男", 18)); studentList.add(new Student("李四", "女", 17)); studentList.add(new Student("王五", "男", 19)); studentList.add(new Student("赵六", "女", 18)); studentList.add(new Student("孙七", "男", 16));// 多条件分组:先性别、再成年状态 Map<String, Map<Boolean, List<Student>>> multiGroup = studentList.stream() .collect(Collectors.groupingBy( // 第一级分组条件:性别 Student::getGender, // 第二级分组条件:年龄是否≥18 Collectors.groupingBy(student -> student.getAge() >= 18) )); // 遍历多级分组结果 multiGroup.forEach((gender, ageGroup) -> { System.out.println("一级分组:性别【" + gender + "】"); ageGroup.forEach((isAdult, students) -> { String status= isAdult ? "成年" : "未成年"; System.out.println(" 二级分组:" + status + ":" + students); }); }); }}3.2.3 分区操作(partitioningBy)
partitioningBy是特殊的分组方式,仅支持布尔类型条件,强制将数据划分为 true、false 两组。相较于groupingBy,二分类场景下性能更优,适用于合格/不合格、达标/不达标等场景。
复制代码import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.stream.Collectors;/** * Stream 分区操作演示 * 场景:区分成年、未成年学生 */public classStreamPartitionDemo {public staticvoidmain(String[] args) { List<Student> studentList = new ArrayList<>(); studentList.add(new Student("张三", "男", 18)); studentList.add(new Student("李四", "女", 17)); studentList.add(new Student("王五", "男", 19));// 分区:分为≥18岁、<18岁两组 Map<Boolean, List<Student>> partitionMap = studentList.stream() .collect(Collectors.partitioningBy(student -> student.getAge() >= 18)); System.out.println("成年学生:" + partitionMap.get(true)); System.out.println("未成年学生:" + partitionMap.get(false)); }}3.2.4 核心操作通俗总结
filter:筛选过滤,保留符合条件的元素,依赖Predicate接口
map:元素转换、数据映射,依赖Function接口
collect:终端收集,将Stream流转为List、Set、Map等集合,支持分组、分区、统计
reduce:聚合汇总,将集合元素合并为单个结果(求和、最值、累积运算)
groupingBy单分组:单条件数据归类,是日常开发最常用的分组方式
groupingBy多分组:多级嵌套分组,适配复杂多维数据统计场景
partitioningBy分区:布尔二分类专用,性能优于普通groupingBy分组
新手重点踩坑:Stream流为一次性资源,执行终端操作后自动关闭、销毁,重复调用中间或终端操作会抛出 IllegalStateException 异常。
3.3 本节高频面试题(含标准答案)
题1:Stream的中间操作和终端操作有什么区别?
答:中间操作是惰性执行,仅记录逻辑不运行,可链式拼接;终端操作触发所有中间操作执行,结束流生命周期。无终端操作时,中间操作代码永远不会执行。
题2:简述Stream的惰性求值原理及优势
答:惰性求值即延迟执行,中间操作只封装逻辑,终端操作才统一执行。优势是减少循环次数,避免无效计算,链式操作一次性遍历集合,大幅提升数据处理性能。
题3:Stream和普通for循环相比,优缺点是什么?
答:优点:代码简洁、可读性强、支持链式编程、天然支持并行流;缺点:基础循环场景性能略低于for循环,复杂逻辑调试难度稍高,新手易出现流复用报错。
题4:Stream流可以重复使用吗?为什么?
答:不可以。Stream是一次性资源,触发终端操作后流会自动关闭、销毁,再次调用中间或终端操作会抛出IllegalStateException异常。
四、接口默认方法与静态方法
Java8之前,接口仅支持抽象方法,一旦接口新增方法,所有实现类必须强制重写,否则编译报错,严重影响项目迭代扩展。Java8新增默认方法、静态方法,核心目的是解决接口版本升级兼容问题,接口新增方法无需改动原有实现类。
说白了,Java8新增接口默认方法和静态方法,核心目的就是解决接口升级兼容问题(接口新增方法,旧实现类无需修改,直接兼容),完美适配项目迭代、框架版本更新场景。
4.1 默认方法(default)
被default关键字修饰的方法,拥有方法体,属于接口实例方法。实现类可直接继承使用,也可按需重写,无需强制实现。
易错点:若一个类同时实现多个接口,且多个接口存在同名默认方法,必须手动重写该方法,否则编译器无法识别调用逻辑,直接编译报错。
复制代码/** * 接口默认方法演示 * 解决接口新增方法的兼容问题 */public interfaceDefaultMethodInterface {// 传统抽象方法(必须被实现类重写)void sayHello();// Java8新增默认方法(有方法体,实现类可直接使用)default voidshowMsg() { System.out.println("这是接口的默认方法,无需强制重写"); }}// 接口实现类class DefaultDemoImplimplementsDefaultMethodInterface {// 必须重写抽象方法@Overridepublic voidsayHello() { System.out.println("实现类重写抽象方法"); } // 可选:重写默认方法,不重写则使用接口默认逻辑/* @Override public void showMsg() { System.out.println("实现类重写接口默认方法"); } */}class TestDefault {public staticvoidmain(String[] args) {DefaultDemoImpl impl=newDefaultDemoImpl(); impl.sayHello(); // 直接调用接口默认方法 impl.showMsg(); }}4.2 接口静态方法(static)
Java8允许接口定义static静态方法,静态方法属于接口本身,和实现类无关。仅能通过「接口名.方法名」调用,不被实现类继承、不可被重写。多用于封装接口专属工具方法。
适用场景:通用工具逻辑封装,无需单独创建工具类,简化代码结构。
复制代码/** * 接口静态方法演示 */public interfaceStaticMethodInterface {// 接口静态方法static voidstaticToolMethod() { System.out.println("这是接口的静态工具方法,只能通过接口名调用"); }}class TestStatic {public staticvoidmain(String[] args) {// 正确调用:接口名.静态方法 StaticMethodInterface.staticToolMethod(); // 错误调用:实现类、对象均无法调用静态方法// StaticDemoImpl.staticToolMethod(); }}4.3 本节高频面试题(含标准答案)
题1:Java8为什么要引入接口默认方法?
答:为了解决接口迭代兼容问题。旧版本接口新增抽象方法,所有实现类必须重写,代码改动量大;默认方法可保留默认逻辑,旧实现类无需修改,完美兼容新版本接口。
题2:接口默认方法和静态方法的核心区别是什么?
答:默认方法通过实现类对象调用,可被继承、重写;静态方法通过接口名调用,不被继承、不可重写。默认方法面向实例,静态方法面向接口工具逻辑。
题3:类实现多个接口,存在同名默认方法会报错吗?怎么解决?
答:会编译报错,因为编译器无法判定调用哪个接口的默认逻辑。解决方案是手动重写该同名默认方法,自定义实现逻辑。
五、Optional 类
Optional是Java8专为解决空指针异常(NPE)设计的工具类,替代传统繁琐的 if (obj != null) 空值判断,从语法层面规范空值处理逻辑,提升代码健壮性。
Optional本质是对象容器,内部封装目标对象,强制开发者主动处理空值场景,避免直接调用null对象方法导致程序崩溃。
核心设计思想:将被动空指针报错,转变为主动空值处理,标准化空值逻辑。
5.1 常用核心方法+完整代码演示
涵盖新手必备方法:ofNullable、isPresent、orElse、orElseGet、orElseThrow
复制代码import java.util.Optional;/** * Optional空值处理工具类零基础实战 * 彻底解决空指针异常 */public classOptionalDemo {public staticvoidmain(String[] args) {// 1. 创建Optional对象// ofNullable:支持null和非null数据(日常开发99%用这个)String str1=null; Optional<String> optionalNull = Optional.ofNullable(str1); String str2="Java8学习"; Optional<String> optionalStr = Optional.ofNullable(str2); // of:仅支持非null数据,传null直接报错(不推荐新手使用)// Optional<String> errorOpt = Optional.of(null);// 2. isPresent():判断是否有非空值(true=非空,false=空)if (optionalNull.isPresent()) { System.out.println("数据非空:" + optionalNull.get()); } else { System.out.println("当前数据为空"); } // 3. orElse():为空时返回默认值,非空返回原数据String result1= optionalNull.orElse("默认空值数据"); System.out.println("orElse结果:" + result1);// 4. orElseGet():为空时执行Lambda生成默认值(性能优于orElse)String result2= optionalNull.orElseGet(() -> "Lambda生成默认数据"); System.out.println("orElseGet结果:" + result2);// 5. orElseThrow():为空时抛出异常(参数校验常用)try {String result3= optionalNull.orElseThrow(() -> newRuntimeException("数据不能为空!")); } catch (RuntimeException e) { System.out.println("异常提示:" + e.getMessage()); } // 非空数据获取 System.out.println("非空数据:" + optionalStr.orElse("空值")); }}5.2 新手必懂方法区别
orElse:无论数据是否为空,默认值都会提前初始化,适合简单常量默认值场景
orElseGet:仅当数据为空时,才通过Lambda生成默认值,复杂场景性能更优
高危踩坑:禁止直接使用get()方法取值!若Optional封装null,调用get()会直接抛出空指针异常,必须先判空或使用orElse等兜底方法。
5.3 本节高频面试题(含标准答案)
题1:Optional类的设计初衷是什么?解决了什么问题?
答:初衷是优雅处理空值,杜绝空指针异常。替代传统繁琐的if空判断,将空值处理标准化,强制开发者处理空场景,提升代码健壮性和可读性。
题2:of()和ofNullable()的区别是什么?日常开发用哪个?
答:of()传入null会直接空指针报错,仅支持非空数据;ofNullable()支持null和非空数据。日常开发统一使用ofNullable,安全无风险。
题3:orElse和orElseGet的核心区别?
答:orElse无论空与否都会创建默认值;orElseGet仅空值时才生成默认值。复杂默认场景优先用orElseGet,性能更优,简单常量场景可通用orElse。
六、新日期时间 API
Java8之前的Date、Calendar API存在诸多缺陷:线程不安全、月份从0开始计数、API繁琐、时间对象可修改、格式化工具并发报错,日常开发极易产生bug。
Java8全新推出java.time 包日期API,核心优势:不可变、线程安全、语法直观、API简洁。所有时间修改操作均返回新对象,原对象不发生改变,彻底解决旧API并发问题。
核心常用类:LocalDate(纯日期)、LocalTime(纯时间)、LocalDateTime(日期+时间,最常用)、Period(日期间隔)、Duration(时间间隔)、DateTimeFormatter(线程安全格式化)。
6.1 核心类完整代码演示
复制代码import java.time.Duration;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.Period;import java.time.format.DateTimeFormatter;/** * Java8新日期时间API零基础全演示 * 包含日期、时间、间隔、格式化、时间计算 */public classDateTimeDemo {public staticvoidmain(String[] args) {// 1. LocalDate:获取当前日期(年-月-日)LocalDate nowDate= LocalDate.now(); System.out.println("当前日期:" + nowDate);// 自定义日期LocalDate customDate= LocalDate.of(2026, 8, 1); System.out.println("自定义日期:" + customDate);// 2. LocalTime:获取当前时间(时:分:秒)LocalTime nowTime= LocalTime.now(); System.out.println("当前时间:" + nowTime);// 3. LocalDateTime:日期+时间(最常用)LocalDateTime nowDateTime= LocalDateTime.now(); System.out.println("当前日期时间:" + nowDateTime);// 4. 日期时间格式化(线程安全,替代SimpleDateFormat)DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String formatTime= nowDateTime.format(formatter); System.out.println("格式化后时间:" + formatTime);// 字符串转日期时间LocalDateTime parseTime= LocalDateTime.parse("2026-08-01 14:00:00", formatter); System.out.println("字符串解析时间:" + parseTime);// 5. Period:计算年月日间隔(仅日期)Period period= Period.between(customDate, nowDate); System.out.println("日期间隔:" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "日");// 6. Duration:计算时分秒间隔(仅时间)LocalTime time1= LocalTime.of(10, 0, 0);LocalTime time2= LocalTime.of(12, 30, 0);Duration duration= Duration.between(time1, time2); System.out.println("时间间隔:" + duration.toHours() + "小时" + duration.toMinutesPart() + "分钟");// 7. 日期时间加减计算LocalDateTime nextDay= nowDateTime.plusDays(1); // 加1天LocalDateTime lastHour= nowDateTime.minusHours(1); // 减1小时 System.out.println("明天此时:" + nextDay); System.out.println("一小时前:" + lastHour); }}6.2 核心类通俗区分 + 高频API详解 + 新旧日期转换
6.2.1 五大核心类通俗区分
LocalDate:仅存储年月日,适用于生日、下单日期、归档日期等无时间场景
LocalTime:仅存储时分秒,适用于打卡时间、时段统计等无日期场景
LocalDateTime:存储年月日+时分秒,绝大多数业务表单、日志、订单场景首选
Period:仅用于LocalDate,计算年月日级别的日期间隔
Duration:用于LocalTime/LocalDateTime,计算时分秒毫秒级时间间隔
6.2.2 开发高频API详解(增删改查/判断/格式化)
以下为开发高频复用的日期API,包含时间加减、前后对比、字段截取、合法性校验、格式化解析,覆盖所有日常时间操作场景。
复制代码import java.time.Duration;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.Period;import java.time.format.DateTimeFormatter;import java.time.temporal.ChronoUnit;/** * Java8新日期 高频API全套实战 * 包含加减、对比、判断、截取、格式化通用操作 */public classDateTimeApiFullDemo {public staticvoidmain(String[] args) {LocalDateTime now= LocalDateTime.now(); System.out.println("当前时间:" + now);// 1. 时间加减API(精准单位控制)LocalDateTime plus3Day= now.plusDays(3); // 加3天LocalDateTime minus2Hour= now.minusHours(2); // 减2小时LocalDateTime plus1Year= now.plus(1, ChronoUnit.YEARS); // 通用单位加1年 System.out.println("3天后时间:" + plus3Day); System.out.println("2小时前时间:" + minus2Hour);// 2. 时间对比API(前后、相等判断)LocalDateTime targetTime= LocalDateTime.of(2026, 8, 1, 12, 0);boolean isBefore= now.isBefore(targetTime); // 当前时间是否早于目标时间boolean isAfter= now.isAfter(targetTime); // 当前时间是否晚于目标时间boolean isEqual= now.isEqual(targetTime); // 时间是否相等 System.out.println("是否早于2026-08-01:" + isBefore);// 3. 时间截取、单独获取年月日时分秒int year= now.getYear();int month= now.getMonthValue(); // 直接返回1-12,无需+1int day= now.getDayOfMonth();int hour= now.getHour(); System.out.printf("当前:%d年%d月%d日 %d时%n", year, month, day, hour);// 4. 日期合法性自动校验(新API自带校验,杜绝非法日期)// 非法日期会直接抛出异常,无需手动判断// LocalDate errorDate = LocalDate.of(2026, 2, 30); // 2月30日非法,编译运行报错// 5. 格式化与解析(全局通用格式)DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String formatTime= now.format(formatter);LocalDateTime parseTime= LocalDateTime.parse(formatTime, formatter); System.out.println("格式化时间:" + formatTime); }}6.2.3 传统Date/Calendar 与 新日期API 互转(重点刚需)
老旧项目改造、第三方接口对接时,经常需要新旧日期类型互转。以下为生产环境通用的无坑互转代码,完美兼容旧项目Date、Calendar类型。
复制代码import java.time.LocalDate;import java.time.LocalDateTime;import java.time.ZoneId;import java.time.format.DateTimeFormatter;import java.util.Calendar;import java.util.Date;/** * 新旧日期API互转工具(生产可用) * Date、Calendar 与 LocalDateTime/LocalDate 双向转换 */public classDateTimeConvertDemo {public staticvoidmain(String[] args) {// 1. 旧Date 转 新LocalDateTime、LocalDateDate oldDate=newDate();LocalDateTime dateToDateTime= oldDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();LocalDate dateToDate= oldDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); System.out.println("Date转LocalDateTime:" + dateToDateTime); System.out.println("Date转LocalDate:" + dateToDate);// 2. 新LocalDateTime 转 旧DateLocalDateTime nowDateTime= LocalDateTime.now();Date dateFromDateTime= Date.from(nowDateTime.atZone(ZoneId.systemDefault()).toInstant()); System.out.println("LocalDateTime转Date:" + dateFromDateTime);// 3. 旧Calendar 转 新日期APICalendar calendar= Calendar.getInstance();LocalDateTime calendarToDateTime= calendar.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); System.out.println("Calendar转LocalDateTime:" + calendarToDateTime);// 4. 新日期 转 CalendarCalendar newCalendar= Calendar.getInstance(); newCalendar.setTime(Date.from(nowDateTime.atZone(ZoneId.systemDefault()).toInstant())); }}6.2.4 新旧日期API核心痛点对比总结
旧API(Date/Calendar)核心痛点:
线程不安全:SimpleDateFormat多线程并发会数据错乱
设计反人类:月份0-11,获取真实月份需要+1,极易出错
对象可变:set方法修改时间,会改变原对象数据,存在并发风险
API繁琐:日期加减、对比、间隔计算需要大量冗余代码
新API(java.time)核心优势:
天然线程安全:不可变对象,修改返回新对象,无并发问题
符合日常认知:月份1-12,日期时间规则直观
API极简:一行代码实现加减、对比、间隔计算
自带合法性校验、线程安全格式化工具
核心总结:新日期对象为不可变类,修改操作会生成新对象,不修改原数据,天然规避多线程并发数据错乱问题。
6.3 本节高频面试题(含标准答案)
题1:Java8新日期API相比旧Date/Calendar有什么优势?
答:新API线程安全、对象不可变,无并发报错问题;月份从1开始,符合日常认知;API简洁易懂,格式化工具线程安全,时间计算、间隔统计更便捷,彻底规避旧版本各类bug。
题2:Period和Duration的区别是什么?
答:Period用于日期间隔(年月日),基于LocalDate计算;Duration用于时间间隔(时分秒毫秒),基于LocalTime/LocalDateTime计算,二者适用场景严格区分。
题3:新日期API为什么是线程安全的?
答:所有日期时间对象都是不可变类,修改时间不会改变原对象,只会生成新对象。无共享变量修改操作,多线程并发场景下不会出现数据错乱,天然线程安全。
七、方法引用
方法引用是Lambda表达式的进阶简化语法。当Lambda体仅调用一个已有方法、无任何自定义业务逻辑时,可使用方法引用替代,让代码更简洁紧凑。
本质:复用现有方法逻辑,无额外功能,底层执行原理与Lambda完全一致,仅为语法简化。
方法引用分为四种固定形式,覆盖所有使用场景,可直接套用。
7.1 四种方法引用形式(全示例)
复制代码import java.util.ArrayList;import java.util.List;/** * 方法引用四种形式完整演示 * 是Lambda的极简简化写法 */public classMethodReferenceDemo {public staticvoidmain(String[] args) { List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C");// 1. 对象::实例方法 (对象调用普通实例方法)// Lambda写法:s -> System.out.println(s)// 方法引用简化 list.forEach(System.out::println); // 2. 类::静态方法 (类调用静态方法)// Lambda写法:n -> Integer.parseInt(n) List<String> numStrList = List.of("11", "22", "33"); numStrList.stream().map(Integer::parseInt).forEach(System.out::println); // 3. 类::实例方法 (类名调用实例方法,参数为调用对象)// Lambda写法:s -> s.toUpperCase() list.stream().map(String::toUpperCase).forEach(System.out::println); // 4. 类::new (构造方法引用,创建对象)// Lambda写法:() -> new User()UserFactory userFactory= User::new;User user= userFactory.createUser(); System.out.println("创建用户对象:" + user); }}// 自定义工厂函数式接口@FunctionalInterfaceinterface UserFactory { User createUser();}// 自定义实体类class User {@Overridepublic String toString() {return "用户对象"; }}7.2 四种形式通俗总结
对象::实例方法:通过实例对象调用普通方法(例:System.out::println)
类::静态方法:调用工具类静态方法,用于类型转换、数值计算
类::实例方法:通过类名调用实例方法,Lambda入参为方法调用对象
类::new:构造方法引用,快速创建对象,适配工厂模式
使用前提:Lambda体仅有一行代码,且仅调用已有方法、无自定义逻辑,才可使用方法引用。
7.3 本节高频面试题(含标准答案)
题1:什么是方法引用?它和Lambda的关系是什么?
答:方法引用是Lambda的极简语法糖,用于简化仅调用已有方法的Lambda场景。无额外业务逻辑时,用方法引用替代Lambda,代码更简洁,底层执行原理与Lambda完全一致。
题2:方法引用有哪四种形式?分别适用什么场景?
答:1.对象::实例方法:对象调用普通方法;2.类::静态方法:调用静态工具方法;3.类::实例方法:参数对象调用实例方法;4.类::new:构造方法创建对象。
题3:所有Lambda表达式都能改成方法引用吗?
答:不能。仅当Lambda方法体只有一行代码,且只是调用已有方法、无任何自定义逻辑、无参数修改时,才能使用方法引用,复杂逻辑无法替换。
八、重复注解与类型注解
Java8之前注解存在两大限制:同一位置无法重复使用同一注解、注解仅能标记类、方法、字段,无法标记变量、泛型等类型。Java8新增重复注解、类型注解,大幅拓展注解使用场景。
主要用于框架参数校验、权限标记、代码检测等场景。
8.1 重复注解(完整自定义实现)
重复注解允许在同一个类、方法、字段上多次使用同一注解。实现必须依赖两个组件:可重复注解、对应容器注解。
复制代码import java.lang.annotation.Repeatable;import java.lang.annotation.Target;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;/** * 1. 定义可重复注解 * Repeatable:标记该注解支持重复使用,绑定容器注解 */@Repeatable(RoleContainer.class)@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Role {// 注解参数:角色名称 String value();}/** * 2. 定义注解容器(存储重复的注解) * 必须和可重复注解绑定 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@interface RoleContainer {// 存储多个Role注解 Role[] value();}/** * 3. 重复注解使用测试 */@Role("管理员")@Role("超级用户")@Role("运维人员")public classRepeatAnnotationDemo {public staticvoidmain(String[] args) {// 获取所有重复注解 Role[] roles = RepeatAnnotationDemo.class.getAnnotationsByType(Role.class); for (Role role : roles) { System.out.println("拥有角色:" + role.value()); } }}8.2 类型注解
类型注解将注解标记范围拓展至所有类型场景,包含变量类型、方法返回值、泛型、强制类型转换等。
核心作用:实现精细化代码校验与类型检测,是Spring、Hibernate框架参数校验的核心基础。
复制代码import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;// 定义类型注解:可标记任意类型@Target(ElementType.TYPE_USE)@Retention(RetentionPolicy.RUNTIME)@interface CheckNull {}// 类型注解使用场景public classTypeAnnotationDemo {// 标记变量类型private @CheckNull String name;// 标记返回值类型public static@CheckNull String getStr() {return "测试类型注解"; } public staticvoidmain(String[] args) {// 标记强制转换类型Object obj="测试";String str= (@CheckNull String) obj; System.out.println(str); }}8.3 本节高频面试题(含标准答案)
题1:简述Java8重复注解的实现原理
答:通过@Repeatable注解标记可重复注解,并绑定对应的容器注解。重复使用的注解会统一存入容器注解的数组中,通过反射getAnnotationsByType方法可获取所有重复注解。
题2:类型注解相比传统注解的优势是什么?
答:传统注解仅能标记类、方法、字段;类型注解可标记所有类型场景(变量、返回值、泛型、强转),支持更精细化的代码校验,适配框架参数校验、代码检测场景。
题3:重复注解的容器注解必须满足什么条件?
答:容器注解必须定义value数组,数组类型为可重复注解类型;作用域、生命周期需大于等于可重复注解,保证注解能正常存储和获取。
九、Base64 编码支持
Java8之前无原生Base64工具类,开发者需依赖第三方工具或私有API,存在兼容性、安全性问题。Java8新增 java.util.Base64 原生工具类,线程安全、性能高效,无需额外引入依赖。
常用于字符串、文件、图片的编码传输、参数脱敏场景。
原生提供三种编码模式:基础编码、URL安全编码、MIME编码,适配不同业务场景。
9.1 完整编码解码代码示例
复制代码import java.nio.charset.StandardCharsets;import java.util.Base64;/** * Java8原生Base64编码解码演示 * 无需第三方依赖,原生线程安全 */public classBase64Demo {public staticvoidmain(String[] args) {// 原始字符串String content="Java8新特性学习!123abc";// 1. 基础Base64编码 Base64.Encoder encoder= Base64.getEncoder();byte[] encodeBytes = encoder.encode(content.getBytes(StandardCharsets.UTF_8));String encodeStr=newString(encodeBytes); System.out.println("基础编码结果:" + encodeStr);// 2. 基础Base64解码 Base64.Decoder decoder= Base64.getDecoder();byte[] decodeBytes = decoder.decode(encodeStr);String decodeStr=newString(decodeBytes, StandardCharsets.UTF_8); System.out.println("基础解码结果:" + decodeStr);// 3. URL安全编码(解决URL传输特殊字符报错问题)String urlEncode= Base64.getUrlEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8)); System.out.println("URL安全编码:" + urlEncode);String urlDecode=newString(Base64.getUrlDecoder().decode(urlEncode), StandardCharsets.UTF_8); System.out.println("URL安全解码:" + urlDecode);// 4. MIME编码(适配文件、大文本传输)String mimeEncode= Base64.getMimeEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8)); System.out.println("MIME编码:" + mimeEncode); }}9.2 三种编码场景区分
基础编码:普通字符串编码,适用于常规数据传输
URL编码:替换+、/等URL特殊非法字符,适配HTTP地址参数传输
MIME编码:自动换行处理,适配大文本、文件、邮件传输场景
9.3 本节高频面试题(含标准答案)
题1:Java8 Base64相比旧版本实现有什么优势?
答:Java8提供原生API,无需第三方依赖;底层优化,性能更高;天然线程安全,支持多线程并发;提供基础、URL、MIME三种模式,适配全业务场景,兼容性更强。
题2:Base64编码是加密吗?为什么?
答:不是加密,是编码格式转换。Base64可双向无损解码,无密钥保护,仅用于数据传输脱敏、格式统一,不能保障数据安全,敏感数据需配合加密算法使用。
题3:URL安全Base64编码的作用是什么?
答:标准Base64包含+、/、=等URL特殊字符,传输会出错。URL安全编码会替换非法字符,适配HTTP地址参数传输,避免参数解析异常。
十、Nashorn JavaScript 引擎
Java8废弃老旧的Rhino JS引擎,全新推出Nashorn引擎,大幅提升Java执行JS代码的性能,支持Java与JS双向交互。
核心作用:作为Java内置JS解释器,可直接执行JS代码、调用JS方法、双向传递参数,实现简单的跨脚本交互。
版本重点:Nashorn为过渡特性,Java8新增、Java11标记过期、Java15正式移除,生产环境不建议重度使用。
10.1 简单调用完整示例
复制代码import javax.script.Invocable;import javax.script.ScriptEngine;import javax.script.ScriptEngineManager;import javax.script.ScriptException;/** * Nashorn JavaScript引擎演示 * Java调用JS代码、执行JS方法 */public classNashornDemo {public staticvoidmain(String[] args) {// 1. 获取Nashorn JS引擎ScriptEngineManager manager=newScriptEngineManager();ScriptEngine nashorn= manager.getEngineByName("nashorn");try {// 2. 直接执行JS单行代码 nashorn.eval("var a = 10; var b = 20; print('JS计算结果:' + (a+b))");// 3. 执行JS自定义函数String jsFunc="function add(x,y){return x+y;}"; nashorn.eval(jsFunc); // 4. Java调用JS函数并传参、获取返回值Invocable invocable= (Invocable) nashorn;Object result= invocable.invokeFunction("add", 100, 200); System.out.println("Java调用JS函数结果:" + result);// 5. Java向JS传递变量 nashorn.put("javaMsg", "Java传递给JS的参数"); nashorn.eval("print(javaMsg)"); } catch (ScriptException | NoSuchMethodException e) { e.printStackTrace(); } }}10.2 核心应用场景说明
1. 动态脚本执行:业务规则通过JS脚本配置,无需重启服务即可更新逻辑
2. 脚本复用:前后端通用JS校验规则,后端可直接复用,避免校验逻辑不一致
3. 动态计算:适配自定义公式、表达式动态解析计算场景
10.3 本节高频面试题(含标准答案)
题1:Nashorn引擎的作用是什么?相比Rhino有什么优势?
答:Nashorn是Java8内置JS引擎,用于Java调用执行JS代码。相比旧Rhino引擎,执行速度更快、兼容性更好、语法贴合现代JS,大幅提升跨脚本交互性能。
题2:Nashorn引擎的版本迭代状态是什么?
答:Java8新增、替代Rhino;Java11标记为过时不推荐使用;Java15正式彻底移除。属于Java8过渡特性,生产环境不建议重度依赖。
题3:Java通过Nashorn可以实现哪些JS交互操作?
答:可直接执行JS代码、调用JS自定义函数、Java向JS传递变量、获取JS执行返回值,实现Java与JavaScript双向数据交互和脚本调用。
相关文章
- Ubuntu Java内存管理优化 08-03
- Ubuntu Java Web应用开发实用技巧 08-03
- Ubuntu Java图形界面开发指南 08-03
- 哪种海洋动物以"再生重塑"的能力而闻名 神奇海洋1月10日答案 08-03
- Ubuntu Java兼容性问题探讨 08-03
- 阿里云99元服务器搭建Z-Blog博客操作步骤:2核2G+3M带宽,成本超低 08-03