深圳 网站公司,南昌网站建设加王道下拉,wordpress视频缩略图n,网站开发语言在那看出来一、基础概念
1)JVM是什么#xff1a;
● 概念#xff1a;运行 Java 字节码的虚拟机。针对不同系统有不同的实现#xff0c;保证Java一次编译#xff0c;到处运行
● 作用#xff1a;将Java字节码转换为特定平台的机器码#xff0c;实现跨平台性
● 关键● 概念运行 Java 字节码的虚拟机。针对不同系统有不同的实现保证Java一次编译到处运行● 作用将Java字节码转换为特定平台的机器码实现跨平台性● 关键JVM规范允许多种实现如HotSpot VM是最常用的实现2)JDK和JRE是什么JDK● 概念Java开发工具包用于开发和编译 Java 程序● 包含JRE以及javac编译器、javadoc等开发工具JRE● 概念运行已编译Java字节码的环境● 包含JVM和Java基础类库应用代码底层需要调用Java基础类JDK 9之后的改变【加分-补充点】● 模块化JDK被重构为模块化结构● jlink工具可以创建只包含应用所需模块的自定义运行时镜像减小运行时环境大小● 不再提供单独 JREJDK 11 之后Oracle 不再提供单独的 JRE 下载3)字节码是什么字节码的概念Java 字节码是 .class 文件中的代码是 JVM 可以理解的指令集它不面向任何特定的硬件平台只面向 JVM字节码的作用跨平台性核心优势● 采用字节码最主要的好处是实现了跨平台性。Java 源代码编译成字节码后可以在任何安装了 JVM 的操作系统上运行字节码配合JVM实现性能优化● 字节码给JVM提供了一个中间层通过JIT编译器JVM 可以将热点代码编译成本地机器码从而提高程序的执行效率4)为什么Java是编译与解释并存的语言● 编译型语言如Go、C、C源代码会被一次性编译成目标机器可以直接执行的二进制机器码比如 .exe 或二进制文件程序运行时不再依赖编译器直接由操作系统调用机器码执行● 解释型语言如Python、JavaScript、PHP源代码不会提前编译成机器码而是由解释器逐行读取并执行解释器负责把源码翻译成机器能理解的操作Java 语言同时具有编译型和解释型语言的特点● Java源代码首先被编译成字节码javac编译器然后JVM通过解释器逐行解释执行字节码● 程序运行时对于频繁执行的热点代码JVM会使用JIT编译器将其编译成本地机器码并进行缓存以提高执行效率二、面向对象1)⭐封装是什么举例用Person类封装Po内部封装它的属性age、gender、name内部定义get()、set()方法以供外部调用内部一些方法操作属性概念利用抽象数据类型将数据和基于数据的操作封装在一起构成一个独立的实体(称为对象)。数据被保护在实体中隐藏内部的细节只对外暴露接口以供调用。优点可以减少耦合方便独立开发迭代模块化提高了系统的可复用性2)⭐继承是什么概念java中通过extends关键字让子类可以继承父类。通过继承子类可以复用父类的代码并增加或修改父类的行为优点实现代码的复用避免了重复编写相同的代码提高开发速度注意一个类只能继承一个父类子类不继承父类的构造方法但是可以通过super调用子类继承父类的public、protected属性不能继承父类的private属性3)⭐多态是什么概念多态指同一个引用在不同时刻表现出多种不同的形态编译时多态指方法重载(方法名相同参数列表不同)编译器通过参数列表来确定调用的具体方法运行时多态指方法重写(子类重写父类的方法)初始化时子类对象使用父类引用运行时根据对象具体类型来调用对应的方法编译看左边运行看右边如子类初始化时Animal a1 new Dog();a1使用了父类Animal的引用优点提高灵活性代码复用提高开发速度4)接口和抽象类的区别一个子类只能继承一个抽象类可以实现多个接口抽象类可以有构造方法、普通成员变量接口不可以抽象类和接口都可以有静态成员变量(static)抽象类中静态成员变量的访问类型任意接口只能public static final(默认)抽象类可以有普通方法接口jdk8后可以有default方法jdk9后允许有private私有方法抽象类可以有静态方法接口jdk8后可以有静态方法但只能被该类直接调用5)为什么有了基本类型还需要封装类对象的需求Java是一种面向对象的语言很多时候必须使用对象如集合ArrayList等只能存储对象Java的泛型只能使用对象额外的功能封装类提供了许多有用的方法来处理数据更方便处理数据如Integer.parseInt(“123”);null值的表示基本数据类型不能为null而封装类可以为null6)⭐讲讲内部类和匿名内部类● 内部类在类中再定义一个类这个类叫做内部类作用内部类只在外部类中被使用利于代码的可读性封装内部类可以隐藏实现细节只在外部类内部使用访问特点内部类可以直接访问外部类的成员变量外部类需要先创建内部类对象才可以访问内部类成员使用场景外部类是 LinkedList内部类是 Node节点类只为链表服务没有必要暴露出来● 匿名内部类没有名字的内部类通常用来创建只使用一次的类实例他必须继承一个类或实现一个接口interface Greeting {void sayHello();}public class Main {public static void main(String[] args) {// 匿名内部类实现Greeting接口Greeting anonymousGreeting new Greeting() {Overridepublic void sayHello() {System.out.println(“Hello from anonymous class!”);}};anonymousGreeting.sayHello(); }}作用当只需要一个实现类的实例避免显式地定义一个新实现类简化代码当方法的参数是类或接口时可以使用匿名内部类来传递参数的实现类对象使用场景定义Event接口用于事件监听时在addListener时用匿名内部类实现Event接口传入事件触发的逻辑7)如何实现菱形继承是什么接口B、C继承接口A要求实现类D同时实现接口B、C做法D实现接口A同时将接口B、C的实例定义为属性即可由于 Java 接口允许多重继承因此不会出现传统菱形继承中的二义性问题。类 D 只需要实现接口 A、B 和 C 中定义的所有方法即可。interface A {void methodA();}// 继承自 A 的接口 Binterface B extends A {void methodB();}// 继承自 A 的接口 Cinterface C extends A {void methodC();}// 使用组合的类 Dclass DWithComposition implements A {private B b;private C c;public DWithComposition(B b, C c) { this.b b; this.c c; } Override public void methodA() { // 可以选择调用 B 或 C 的 methodA b.methodA(); // c.methodA(); } public void methodB() { b.methodB(); } public void methodC() { c.methodC(); }}8)⭐创建对象的方式new关键字class MyClass {public MyClass() {System.out.println(“Object created using new keyword”);}}public class Main {public static void main(String[] args) {MyClass obj new MyClass(); // 使用new关键字创建对象}}Class.newInstance()方法获取类的Class对象newInstance()使用类的无参构造方法创建一个新的对象实例。这种方式需要在编译时知道类的类型并且类必须有一个无参数的构造方法从Java 9开始newInstance()方法已被标记为过时推荐使用java.lang.reflect.Constructor类的newInstance()方法class MyClass {public MyClass() {System.out.println(“Object created using Class.newInstance()”);}}public class Main {public static void main(String[] args) throws Exception {Class? clazz MyClass.class; // 获取 MyClass 对应的 Class 对象MyClass obj (MyClass) clazz.newInstance(); // 使用Class.newInstance()创建对象}}Constructor.newInstance()方法反射机制java.lang.reflect.Constructor类的newInstance()方法提供了更大的灵活性可以调用类的任意构造方法包括带参数的构造方法来创建对象。import java.lang.reflect.Constructor;class MyClass {private String message;public MyClass(String message) { this.message message; System.out.println(Object created using Constructor.newInstance() with message: message); }}public class Main {public static void main(String[] args) throws Exception {Class? clazz MyClass.class; Constructor? constructor clazz.getDeclaredConstructor(String.class); // 先获取构造器对象MyClass obj (MyClass) constructor.newInstance(“Hello, Reflection!”); // 使用Constructor.newInstance()创建对象}}clone()方法实现Cloneable接口Cloneable接口是一个标记接口用于告诉JVM实现该接口的类支持clone()操作clone()方法用于创建现有对象的一个副本浅拷贝Shallow Copy浅拷贝复制基本数据类型的值和引用类型的引用● 基本数据类型 如果原始对象的基本数据类型字段发生改变不会影响克隆对象因为它们是独立的值● 引用类型 如果原始对象的引用类型字段指向的对象的状态发生改变会影响克隆对象因为它们共享同一个对象深拷贝Deep Copy深拷贝复制基本数据类型的值和复制引用类型指向的对象要实现深拷贝需要手动复制所有引用类型的字段● 基本数据类型 如果原始对象的基本数据类型字段发生改变不会影响克隆对象● 引用类型 如果原始对象的引用类型字段指向的对象的状态发生改变不会影响克隆对象因为它们指向的是不同的对象好处● 减少时间 克隆可以减少创建对象的时间特别是当对象包含大量字段或者创建过程比较复杂时。● 保持状态一致性 克隆可以确保新对象与原始对象具有相同的状态这在某些场景下非常重要。介绍clone()方法用于创建现有对象的一个副本。要使用clone()方法类必须实现Cloneable接口并重写clone()方法。这种方式创建的对象与原始对象具有相同的状态但它们是不同的对象例子class MyClass implements Cloneable {private String message;public MyClass(String message) { this.message message; } Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public String getMessage() { return message; }}public class Main {public static void main(String[] args) throws CloneNotSupportedException {MyClass obj1 new MyClass(“Original Object”);MyClass obj2 (MyClass) obj1.clone(); // 使用clone()方法创建对象System.out.println(obj1 message: obj1.getMessage()); System.out.println(obj2 message: obj2.getMessage()); }}反序列化实现Serializable接口序列化将数据结构或对象转换成可以存储或传输的形式通常是二进制字节流也可以是 JSON, XML 等文本格式反序列化将在序列化过程中所生成的数据转换为原始数据结构或者对象的过程介绍通过反序列化可以从输入流中读取并创建对象实例。要使用反序列化类必须实现Serializable接口底层实现JVM 通过反射 内存分配创建对象import java.io.*;class MyClass implements Serializable {private String message;public MyClass(String message) { this.message message; } public String getMessage() { return message; }}public class Main {public static void main(String[] args) throws IOException, ClassNotFoundException {// 创建对象并序列化MyClass obj1 new MyClass(“Original Object”);FileOutputStream fileOut new FileOutputStream(“object.ser”);ObjectOutputStream out new ObjectOutputStream(fileOut);out.writeObject(obj1);out.close();fileOut.close();// 反序列化对象 FileInputStream fileIn new FileInputStream(object.ser); ObjectInputStream in new ObjectInputStream(fileIn); MyClass obj2 (MyClass) in.readObject(); // 使用反序列化创建对象 in.close(); fileIn.close(); System.out.println(obj2 message: obj2.getMessage()); }}工厂模式设计模式这是一种设计模式本质上也是使用new关键字进行对象的创建interface Animal {void makeSound();}class Dog implements Animal {Overridepublic void makeSound() {System.out.println(“Woof!”);}}class Cat implements Animal {Overridepublic void makeSound() {System.out.println(“Meow!”);}}class AnimalFactory {public static Animal createAnimal(String type) {if (“dog”.equalsIgnoreCase(type)) {return new Dog();} else if (“cat”.equalsIgnoreCase(type)) {return new Cat();} else {throw new IllegalArgumentException(Invalid animal type: type);}}}public class Main {public static void main(String[] args) {Animal dog AnimalFactory.createAnimal(“dog”); // 使用工厂模式创建对象dog.makeSound();Animal cat AnimalFactory.createAnimal(cat); // 使用工厂模式创建对象 cat.makeSound(); }}9)Object类里有什么方法Object是所有类的根类超类即每个类都默认继承它它定义了一组最基本的方法● equals对象比较● hashCode计算哈希码● toString转换成字符串● getClass获取字节码● clone对象克隆● 线程同步wait / notify系列三、语法基础1)关于的细节在 Java 里整型算术运算 - * /至少是 int所以如下时隐式地进行了类型的强制转换ab的结果会变为int类型导致b ab赋值类型冲突b a其实是一个特殊写法它等价于将b类型转换为int所以成功byte a 127;byte b 127;b a b; // error : cannot convert from int to byteb a; // ok等价于b (byte)(b a);2)⭐浮点数精度问题与BigDecimal解决2.1)丢失精度问题与其原因问题3*0.1 0.3会返回 true 还是 false ?答案false原因计算机底层用二进制来表示数字某些十进制数在二进制是无法精确表示的就比如1/3在十进制是无限循环小数一样// 0.1 的二进制表示是一个无限循环的小数0.1 (十进制) 0.000110011001100110011001100110011…(二进制)所以在计算3 * 0.1时底层得到的是一个二进制的近似值2.2)BigDecimal解决底层大整数BigInteger 小数位数scale的组合可以精确表示BigDecimal a new BigDecimal(“0.1”);BigDecimal b new BigDecimal(“0.2”);System.out.println(a.add(b)); // 0.3System.out.println(a.multiply(b)); // 0.02System.out.println(a.divide(new BigDecimal(“3”), 2, RoundingMode.HALF_UP)); // 0.033)能在switch中用String吗从java7后可以switch语句通过String.equals()方法来比较字符串需要注意大小写敏感null值会出现NPENullPointerException 4)⭐对equals()和hashCode()的理解● equals()作用用于比较两个对象是否相等方法重写通常需要重写equals()方法根据对象的内容来判断是否相等使用场景// 如果不重写相同的对象equals()不相等Person person1 new Person(“Xiaomin”, 20);Person person2 new Person(“Xiaomin”, 20);System.out.println(person1.equals(person2)); // false● hashCode()作用用于生成对象的哈希码方法重写调用父类Object的hash()方法使用场景在对象使用散列数据结构需要计算对象哈希值时要求相等的对象有相等的哈希码// 如果不重写则相等的两个对象存储在Set的不同位置Person person1 new Person(“Xiaomin”, 20);Person person2 new Person(“Xiaomin”, 20);Set set new HashSet();set.add(person1);System.out.println(set.contains(person2); // false5)⭐String、StringBuffer与StringBuilder的区别可变性String对象是不可变的而StringBuffer和StringBuilder是可变字符序列每次对String的操作相当于生成一个新的String对象而对StringBuffer和StringBuilder的操作是对对象本身的操作而不会生成新的对象所以对于频繁改变内容的字符串避免使用String因为频繁的生成对象将会对系统性能产生影响线程安全String由于有final修饰是不可变的StringBuilder不保证同步但效率更高StringBuffer线程安全大部分方法加了synchronized 修饰6)讲讲java中的staticstatic是什么static 是 Java 的静态修饰符用于修饰类的成员变量方法或代码块表示它属于类而不是对象static变量类变量● 所有对象共享同一份内存节省空间● 在类加载时分配内存程序结束释放● 可通过类名直接访问static方法● 可通过类名直接调用● 无法使用 this 或 super● 只能访问静态变量和静态方法不能直接访问实例成员static静态代码块● 类加载时执行只执行一次常用于初始化静态变量或一次性配置7)⭐泛型是什么概念给类、接口或方法预留类型参数在使用时再指定具体类型特点泛型检查在编译时完成如Box只能放字符串否则编译报错编译后生成的字节码中不再保留具体类型信息即“擦除类型”不能直接创建泛型数组T[] arr new T[10];会导致编译报错优点减少了重复代码并提供了编译时类型安全8)Java支不支持运算符重载运算符重载允许开发者为自定义类型重新定义已有运算符的行为如让在自定义类型上也能使用不支持c支持9)⭐讲讲方法重写和方法重载重写(Overriding)和重载(Overloading)是Java中实现多态性的两种重要方式定义● 重写发生在子类和父类之间子类重新定义了父类中已有的方法方法名、参数列表和返回类型必须相同● 重载发生在同一个类中可以有多个方法具有相同的方法名但是参数列表必须不同参数类型、参数个数或参数顺序不同作用范围● 重写只能发生在继承关系的类之间● 重载可以发生在同一个类中其他规则a. 重写ⅰ. 访问修饰符的限制子类方法的访问修饰符的可见性必须大于或等于父类方法的访问修饰符。例如如果父类方法是protected子类方法可以是protected或public但不能是privateⅱ. 异常的限制子类方法不能抛出比父类方法更多或更宽泛的检查型异常。可以抛出更具体的检查型异常或者任何非检查型异常ⅲ. 不能重写final方法ⅳ. Override注解建议在重写方法上使用Override注解。这会告诉编译器你正在尝试重写一个方法如果方法没有正确重写例如方法签名不匹配编译器会报错b. 重载ⅰ. 返回类型可以相同也可以不同重载方法的返回类型可以相同也可以不同如果两个方法的方法名和参数列表完全相同只有返回类型不同Java编译器会认为这是同一个方法的重复定义从而导致编译错误。四、注解1)⭐注解是什么概念注解是JAVA 5版本开始引入的一个特性用于对代码进行说明常见分类● Java自带的标准注解用这些注解标明后编译器就会进行检查。○ Override标明重写某个方法○ Deprecated用于标明类或方法过时○ SuppressWarnings标明要忽略的警告● 元注解元注解是用于定义注解的注解○ Retention用于标明注解被保留的阶段○ Target用于标明注解使用的范围○ Inherited用于标明注解可继承○ Documented用于标明是否生成javadoc文档● 自定义注解可以根据自己的需求定义注解并可用元注解对自定义注解进行定义自定义注解的优点● 减少编写配置文件如Mybatis Plus在Select注解中编写sql语句避免了编写XML配置文件● 简化开发如Lombok的Data等注解自动生成getter/setter、toString等方法● 可以结合反射实现功能如Spring的Service注解在程序启动时会被扫描并进行bean注入2)⭐注解底层如何实现的存储编译器把注解信息写入.class文件的字节码元数据里Retention决定存活阶段如running加载类加载时注解元数据进入JVM读取运行时通过反射API获取注解信息底层会返回一个动态代理对象为什么是动态代理对象注解本质上是一个接口但不是普通对象所以查询时不能直接返回而是要根据注解值实例化一个动态代理对象实现注解这个接口并返回使用框架通过扫描注解 → 解析元数据 → 执行逻辑如Spring扫描Service注册 Bean五、异常1)Java异常类的层级结构● Throwable是所有异常和错误的父类○ ErrorJVM层面的严重错误比如虚拟机崩溃○ Exception程序可处理的异常分两类ⅰ. 编译时异常Checked Exception● 如 IOException、SQLException● 编译器要求必须处理try-catch或throws否则无法编译通过ⅱ. 运行时异常RuntimeException● 典型如 NullPointerException、IndexOutOfBoundsException● 编译器不强制处理通常是代码逻辑错误引起应该通过写好逻辑去避免2)讲讲异常的可查性● 可查异常除了RuntimeException及其子类以外其他的Exception类及其子类都属于可查异常可查异常在一定程度上它的发生是可以预计的特点是Java编译器会检查它● 不可查异常包括运行时异常RuntimeException与其子类和错误Error这些异常在运行时才出现。它们通常是由于编程错误引起的未检查异常不需要在方法签名中声明也不强制要求使用 try-catch 块捕获3)throw和throws● throws异常的申明○ 写在方法签名上用来告诉调用者“这个方法可能会抛出哪些异常”○ 一般用于编译时异常Checked Exception因为编译器要求必须处理○ 可以声明多个异常用逗号隔开public static void method() throws IOException, FileNotFoundException{//something statements}● throw异常的抛出○ 写在方法体内部用来“制造”并抛出一个具体的异常对象○ 可以抛出运行时异常或编译时异常public static double method(int value) {if(value 0) {throw new ArithmeticException(“参数不能为0”); // 抛出一个运行时异常}return 5.0 / value;}4)讲讲try-with-resource机制机制当你在try子句中打开资源资源会在try代码块执行后或异常处理后自动关闭使用如果你的资源实现了AutoCloseable接口你可以使用这个语法。大多数的Java标准资源都实现了这个接口public void automaticallyCloseResource() {File file new File(“./tmp.txt”);try (FileInputStream inputStream new FileInputStream(file) {// use the inputStream to read a file} catch (FileNotFoundException e) {log.error(e);} catch (IOException e) {log.error(e);}}5)讲讲try-catch-finally机制Java 提供了try-catch-finally块来处理异常● try 块用于包含可能抛出异常的代码● catch 块用于捕获并处理特定类型的异常。可以有多个 catch 块来处理不同类型的异常● finally 块用于包含无论是否发生异常都需要执行的代码。通常用于释放资源例如释放数据库连接try {// 可能抛出异常的代码} catch (ArithmeticException e) {// 捕获并处理 ArithmeticException 异常} finally {// 无论是否发生异常都会执行的代码}6)讲讲常见的ErrorOutOfMemoryError 即OOM内存溢出JVM无法再分配内存可能堆空间不足、方法区空间不足引起StackOverflowError栈溢出递归过深或死递归ClassNotFoundError 用Class.forName()加载类时未找到NoSuchMethodError方法没有找到可能JDK版本冲突六、反射1)⭐什么是反射类的私有属性如何访问一般结合2.一起答概念反射就是在运行时动态操作类和对象的能力。比如可以获取类的信息、创建对象如Spring通过通过反射加载Bean、调用方法、访问字段如Jackson等序列化方式就是通过反射将json映射到对象属性等缺点● 性能差因为要动态解析JVM优化用不上● 破坏封装性能访问私有方法和字段可能带来安全问题2)⭐如何使用反射获取Class对象Class? clazz Class.forName(“MyClass”); // 或 MyClass.class / obj.getClass()创建对象先getConstructor()再newInstance()Object obj clazz.getDeclaredConstructor().newInstance();调用方法获取Method对象再invoke()Method m clazz.getMethod(“myMethod”, String.class);m.invoke(obj, “Hello”);访问私有字段获取Field对象setAccessible()设置可访问set()具体值Field f clazz.getDeclaredField(“myField”);f.setAccessible(true);f.set(obj, “New Value”);七、SPI机制1)SPI机制是什么SPIService Provider Interface是JDK提供的一种服务发现机制它的作用是● 接口和实现分离调用方只依赖接口不关心具体实现● 支持扩展和替换实现比如框架可以在运行时加载不同的实现类而不用改调用方代码2)SPI的服务发现流程服务提供者当服务的提供者提供了一种接口的实现之后需要在resource目录下的META-INF/services/目录里创建一个以服务接口命名的文件这个文件里的内容就是这个接口的具体的实现类服务调用者当其他的程序需要这个服务的时候就可以通过查找这个jar包一般都是以jar包做依赖的META-INF/services/中的配置文件配置文件中有接口的具体实现类名可以根据这个类名进行加载实例化就可以使用该服务了3)SPI简单示例我们现在需要使用一个内容搜索接口搜索的实现可能是基于文件系统的搜索也可能是基于数据库的搜索。● 先定义好接口public interface Search {public List searchDoc(String keyword);}● 文件搜索实现public class FileSearch implements Search{Overridepublic List searchDoc(String keyword) {System.out.println(文件搜索 keyword);return null;}}● 数据库搜索实现public class DatabaseSearch implements Search{Overridepublic List searchDoc(String keyword) {System.out.println(数据搜索 keyword);return null;}}● 新建META-INF/services/目录然后新建接口全限定名的文件com.cainiao.ys.spi.learn.Search里面加上我们需要用到的实现类com.cainiao.ys.spi.learn.FileSearch● 测试方法public class TestCase {public static void main(String[] args) {ServiceLoader s ServiceLoader.load(Search.class);Iterator iterator s.iterator();while (iterator.hasNext()) {Search search iterator.next();search.searchDoc(“hello world”); // 可以看到输出结果文件搜索 hello world}}}可加载多个实现如果在com.cainiao.ys.spi.learn.Search文件里写上两个实现类那最后的输出结果就是两行了4)Seata对SPI机制的扩展Seata自定义EnhancedServiceLoader自定义路径扫描机制实现双路径扫描适配不同版本的兼容// 同时扫描标准SPI路径和Seata扩展路径private static final String SERVICES_DIRECTORY “META-INF/services/”;private static final String SEATA_DIRECTORY “META-INF/seata/”;注解驱动扩展在实现类上添加LoadLevel可用于定义优先级、作用域控制LoadLevel(name “nacos”, order 1)public class NacosRegistryProvider implements RegistryProvider {// 实现类}public enum Scope {SINGLETON, // 单例模式PROTOTYPE // 原型模式}5)⭐SPI和API的区别API面向开发者是系统/库/框架对外暴露的接口开发者直接调用即可完成某个功能● 如List.add()SPI面向框架使用者/服务提供者是系统/库/框架留给第三方实现的接口框架只定义接口由外部“服务提供者”来实现● 如JDBC里的Driver接口就是SPI具体由MySQL、Oracle驱动厂商提供不同的实现● Seata中通过SPI机制对同一类场景如注册中心、配置中心定义SPI接口并提供不同的实现加载时根据配置进行对应依赖的加载避免需要同时加载全部支持的注册中心的依赖并允许用户自定义更灵活的实现方案八、JDK新特性1)JDK8新特性支持函数式编程如Lambda表达式引入了Stream API新的日期时间 API如LocalDate接口可以带默认的方法2)JDK17新特性Switch表达式支持返回值record语法public record User(String name, int age) {}自动生成 toString、equals、hashCode、构造器引入新垃圾回收器如ZGC它几乎不会打断程序的运行九、其他1)序列化时如果不想序列化某个字段怎么做可以使用transient关键字标注这个字段如private transient String password;序列化时该字段不会被写入反序列化时该字段为默认值