个人主页代码不加冰欢迎来访作者简介java后端学习者❄️个人专栏LeetCode刷题日记 苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言大家好我是代码不加冰这一章节主要给大家带来的是java基础八股部分的内容现在每天需要持续坚持的就是算法和八股算法现在是养成习惯了但是八股还是没有好几天没看了继续努力吧。摘要本文主要介绍了Java中的浅拷贝与深拷贝、序列化与反序列化以及泛型三个核心概念。浅拷贝仅复制基本类型值和引用地址而深拷贝会递归复制整个对象结构可通过重写clone()、序列化或第三方工具实现。序列化是将对象转换为字节流的过程用于网络传输和持久化存储Java原生序列化需实现Serializable接口。泛型是JDK5引入的类型参数化特性通过类型擦除在编译期实现类型安全支持泛型类、方法和接口使用通配符(?, ? extends T, ? super T)增强灵活性。文章还对比了不同实现方式的优缺点并解答了相关面试常见问题。什么是浅拷贝和深拷贝浅拷贝Shallow Copy创建一个新对象但只复制基本数据类型的值对于引用类型只复制其内存地址引用不复制对象本身。新旧对象共享引用类型的数据。深拷贝Deep Copy创建一个新对象完全复制整个对象结构包括基本类型和所有引用类型指向的对象。新旧对象完全不共享任何数据。对比图示text 浅拷贝 原对象 —— 基本类型字段独立 —— 引用类型字段 → 同一个对象A 新对象 —— 基本类型字段独立 —— 引用类型字段 → 同一个对象A 深拷贝 原对象 —— 基本类型字段独立 —— 引用类型字段 → 对象A 新对象 —— 基本类型字段独立 —— 引用类型字段 → 对象A全新复制1.1浅拷贝的实现方式java // 1. 重写clone()方法默认实现是浅拷贝 Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } // 2. 拷贝构造函数 public Person(Person person) { this.name person.name; this.address person.address; // 引用复制 }1.2深拷贝的三种实现方式方式一重写clone()方法java public class Address implements Cloneable { private String city; Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Person implements Cloneable { private String name; private Address address; // 引用类型 Override protected Object clone() throws CloneNotSupportedException { Person person (Person) super.clone(); // 手动复制引用类型字段 person.address (Address) this.address.clone(); return person; } } // 使用 Person p1 new Person(张三, new Address(北京)); Person p2 (Person) p1.clone();优点性能好完全控制缺点需要所有引用类型都实现Cloneable嵌套层数多时代码冗长方式二序列化推荐java // 使用序列化实现深拷贝 public class Person implements Serializable { private static final long serialVersionUID 1L; private String name; private Address address; public Person deepCopy() { try { // 写入字节流 ByteArrayOutputStream baos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(baos); oos.writeObject(this); // 从字节流读取 ByteArrayInputStream bais new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois new ObjectInputStream(bais); return (Person) ois.readObject(); } catch (Exception e) { e.printStackTrace(); return null; } } } // 使用 Person p2 p1.deepCopy();优点不需要手动处理每个引用不需要实现Cloneable缺点性能相对较差要求所有类实现Serializable方式三使用第三方工具类java // 1. Apache Commons Lang的SerializationUtils import org.apache.commons.lang3.SerializationUtils; Person p2 SerializationUtils.clone(p1); // 2. Gson/Jackson利用JSON作为中间格式 Gson gson new Gson(); Person p2 gson.fromJson(gson.toJson(p1), Person.class); // 3. Spring的BeanUtils注意这是浅拷贝 // BeanUtils.copyProperties(p1, p2); // 这个是浅拷贝不要搞混1.3总结对比特性浅拷贝深拷贝基本类型复制值复制值引用类型复制引用共享对象复制整个对象独立性能快慢实现复杂度简单较复杂使用场景对象只包含基本类型或不可变对象需要完全独立的对象副本选择建议简单场景或确定引用对象不会被修改 → 浅拷贝对象有复杂的引用关系需要独立副本 → 序列化方式深拷贝对性能要求高且嵌套层次少 → 重写clone方式深拷贝由此引入什么是序列化和反序列化核心定义序列化将Java对象转换成字节序列字节流/JSON/XML等的过程。反序列化将字节序列恢复成Java对象的过程。text 序列化 对象 → 字节流 (写出去) 反序列化 字节流 → 对象 (读进来)通俗理解把序列化想象成寄快递序列化 把物品对象打包成包裹字节流反序列化 拆开包裹取出物品对象为什么要序列化主要用途场景说明例子网络传输对象无法在网络中直接传输需转成字节RPC调用、HTTP请求持久化存储把对象保存到文件或数据库保存游戏进度、缓存数据深拷贝通过序列化/反序列化实现深拷贝复制复杂对象分布式通信不同JVM间传递对象Dubbo、RMIJava序列化基本使用1. 实现Serializable接口标记接口java import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID 1L; // 版本号 private String name; private int age; private transient String password; // transient字段不会被序列化 }2. 序列化与反序列化示例java public class SerializeDemo { public static void main(String[] args) throws Exception { User user new User(张三, 25, 123456); // 序列化对象 → 文件 ObjectOutputStream oos new ObjectOutputStream( new FileOutputStream(user.obj) ); oos.writeObject(user); oos.close(); // 反序列化文件 → 对象 ObjectInputStream ois new ObjectInputStream( new FileInputStream(user.obj) ); User newUser (User) ois.readObject(); ois.close(); System.out.println(newUser.getName()); // 张三 System.out.println(newUser.getAge()); // 25 System.out.println(newUser.getPassword()); // nulltransient } }关键知识点1. serialVersionUID 的作用java private static final long serialVersionUID 123456789L;作用验证序列化版本一致性反序列化时会检查UID是否匹配不匹配会抛InvalidClassException建议显式声明避免JVM自动生成导致的不兼容2. transient 关键字java private transient String password; // 该字段不会被序列化标记不需要序列化的字段敏感信息、可重新计算的字段等反序列化后transient字段会被初始化为默认值对象→nullint→03. 静态变量不会被序列化java private static String company ABC; // 静态变量属于类不属于对象序列化保存的是对象状态静态变量属于类级别不会被序列化常见序列化方式对比方式特点适用场景Java原生序列化性能一般仅Java能用简单的本地持久化JSONJackson/Gson可读性好跨语言RESTful API、配置文件XML可读性好规范严格旧系统集成、WebServiceProtobuf性能极高体积小高性能RPC、大数据场景Hessian跨语言比Java快Dubbo默认协议面试常问Q为什么不推荐使用Java原生序列化性能较差序列化后体积大只能Java使用不跨语言安全风险反序列化漏洞Q序列化和反序列化深拷贝的原理java // 对象 → 字节流序列化→ 新对象反序列化 ByteArrayOutputStream baos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(baos); oos.writeObject(original); // 序列化 ByteArrayInputStream bais new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois new ObjectInputStream(bais); Object copy ois.readObject(); // 反序列化因为写入了完整的对象图读出来时创建了全新的对象实现了深拷贝。什么是泛型核心定义泛型Generics是JDK 5引入的特性本质是参数化类型即把类型作为参数传递让代码可以适用于多种数据类型。简单理解类型作为变量在使用时再指定具体类型。生活中的类比无泛型造三个容器分别只能装苹果、香蕉、橘子有泛型造一个通用容器标记“这里装什么类型”使用时再决定装什么为什么需要泛型泛型之前的问题没有泛型java // 没有泛型使用Object List list new ArrayList(); list.add(hello); list.add(123); // 可以加不同类型 String str (String) list.get(0); // 需要强制转换 Integer num (Integer) list.get(1); // 但容易出错 String error (String) list.get(1); // 运行时ClassCastException问题❌ 类型不安全 - 可以混入不同类型❌ 需要强制类型转换 - 代码冗长❌ 编译期无法发现错误 - 运行时才崩溃有泛型之后java // 使用泛型明确指定类型 ListString list new ArrayList(); list.add(hello); // list.add(123); // 编译错误无法添加Integer String str list.get(0); // 无需类型转换优势✅ 类型安全 - 编译期检查✅ 无需强制转换 - 代码简洁✅ 编译期发现问题 - 提前报错泛型的基本使用1. 泛型类java // 定义一个泛型类 public class BoxT { private T content; public void set(T content) { this.content content; } public T get() { return content; } } // 使用时指定具体类型 BoxString stringBox new Box(); stringBox.set(hello); String str stringBox.get(); // 无需强转 BoxInteger intBox new Box(); intBox.set(123); Integer num intBox.get();2. 泛型方法java public class Util { // 泛型方法T在返回值前声明 public static T T getMiddle(T[] array) { return array[array.length / 2]; } } // 调用 String[] strs {a, b, c}; String mid Util.getMiddle(strs); // 类型推断为String Integer[] nums {1, 2, 3}; Integer num Util.getMiddle(nums); // 类型推断为Integer3. 泛型接口java // 泛型接口 public interface GeneratorT { T generate(); } // 实现方式1保留泛型 public class GenericImplT implements GeneratorT { Override public T generate() { return null; } } // 实现方式2指定具体类型 public class StringImpl implements GeneratorString { Override public String generate() { return hello; } }通配符通配符语法含义类比无边界?未知类型只能读不能写一个盒子不知道装的什么只能看不能放上边界? extends TT或T的子类装的是T或T的“儿子”只能取不能存下边界? super TT或T的父类装的是T或T的“爸爸”能存但不能精确取代码示例java // 无边界只读不写 public void printList(List? list) { for (Object obj : list) { // 只能当作Object读 System.out.println(obj); } // list.add(hello); // 编译错误不能添加 } // 上边界只能取不能存除了null public double sum(List? extends Number list) { double sum 0; for (Number num : list) { // 可以读 sum num.doubleValue(); } // list.add(123); // 编译错误不能添加 return sum; } // 下边界能存但取出来是Object public void addNumbers(List? super Integer list) { list.add(123); // 可以添加Integer list.add(456); // 可以添加 Object obj list.get(0); // 取出来是Object // Integer num list.get(0); // 编译错误不能直接赋值给Integer }PECS原则面试高频PECS Producer Extends, Consumer Super生产者Producer只读不写 → 用? extends T消费者Consumer只写不读 → 用? super Tjava // 生产者从集合中读取数据 public void copy(List? extends Number src, List? super Number dest) { for (Number num : src) { // src只读Producer dest.add(num); // dest只写Consumer } }类型擦除核心原理泛型是编译期的概念运行时会被擦除java ListString list1 new ArrayList(); ListInteger list2 new ArrayList(); // 编译后运行时都是List类型 list1.getClass() list2.getClass(); // true都是ArrayList.class擦除规则无限定类型替换为Object有上边界替换为第一个边界类型java // 原始代码 public class BoxT { private T data; } // 编译后类型擦除后 public class Box { private Object data; } // 有边界的情况 public class BoxT extends Number { private T data; } // 擦除后 public class Box { private Number data; }常见面试题QListT和List?的区别ListTT是类型参数在整个方法中保持一致List?未知类型不能添加元素除了nullQ泛型中T和?有什么区别T声明一个类型参数可以多次使用?通配符使用一个未知类型只能使用一次Q为什么不能用泛型数组new T[10]为什么不行java // 错误示例 T[] array new T[10]; // 编译错误 // 原因类型擦除后变成Object[]但实际类型检查会有冲突 // 解决方案使用ArrayListT代替具体问题演示java // 假设Java允许这样写 T[] array new T[10]; // 假设合法 // 类型擦除后变成 Object[] array new Object[10]; // 实际创建的是Object数组 // 但代码意图是 String[] result (String[]) array; // 期望是String数组 // 运行时ClassCastExceptionObject[]不能转成String[]java public static T T[] createArray(T... elements) { // 假设可以 new T[10] T[] array new T[10]; // 实际创建了Object[]但表面类型是T[] return array; } // 调用 String[] result createArray(a, b, c); // 问题createArray内部创建的是Object[]但外层期望String[] // 运行时Object[] cannot be cast to String[]因此数组的运行时检查会失败java // 数组在创建时必须知道具体类型 String[] arr1 new String[5]; // ✅ Object[] arr2 new String[5]; // ✅ 协变允许 arr2[0] 123; // ✅ 编译通过 // 但运行时ArrayStoreException因为arr2实际是String[]不能放Integer // 如果允许 new T[5] T[] arr new T[5]; // T的实际类型是什么运行时不知道 // 创建时无法确定数组的实际类型是String[]? Integer[]?结语如果对你有帮助请点赞关注收藏我会持续更新对大家有用的文章