有了开头后面就开始简单了,回顾下今天所复习的内容加深印象
面向对象与面向过程的区别
- 面向过程:以函数为核心,数据与操作分离,适合简单任务。
- 面向对象:以对象为核心,封装数据与方法,适合复杂系统。
- 对比维度:编程思想、基本单元、数据与操作关系、核心特性、适用场景等。
没啥可说的,学习java时教员就一直强调面向对象。
默认构造方法
- 类中若未定义任何构造方法,编译器自动提供无参默认构造方法。
- 若已定义有参构造,默认无参构造消失。
- 默认构造方法仅包含
super();调用。
了解即可
构造方法的应用场景
- 对象初始化、保证必要数据必传、实现不可变类、资源加载、依赖注入、单例模式、链式构造、继承配合、工厂模式控制创建。
后续钻研设计模式时细细品味
多态的理解
- 定义:同一父类引用指向不同子类对象,调用同一方法表现不同行为。
- 三要素:继承、重写、父类引用指向子类对象。
- 核心:动态绑定(运行时根据实际对象类型决定调用哪个方法)。
- 注意:属性没有多态,静态方法没有多态。
面向对象的三大特征:封装、继承、多态;其中多态个人理解是最抽象的,但实际项目中照葫芦画瓢多了也能理解其含义。
接口与抽象类的对比
- 相同点:都不能实例化、都可包含抽象方法、都可作为引用类型、都位于继承体系顶端。
- 不同点:设计思想(is-a vs can-do)、关键字、继承/实现数量、成员变量、方法类型、构造方法、访问修饰符、多继承支持、新增方法影响、适用场景。
选择建议
- 如果强调类型的归属,且需要为子类提供公共代码(字段、具体方法),用抽象类。
- 如果强调行为的规范,且希望不相关的类也能拥有相同行为,用接口。
- Java 8 以后,接口通过默认方法也能提供代码复用,但设计初衷仍是行为契约,而抽象类更适合有状态和复杂构造的复用场景。
实际应用场景中最多的还是接口类,抽象类仅仅用过几次(可能自己思想和理解不充分),在最新的DDD项目中也仅想到过一次,最后因理解不足放弃了。
深拷贝、浅拷贝与引用拷贝
- 引用拷贝:复制引用地址,不创建新对象。
- 浅拷贝:创建新对象,基本类型复制值,引用类型复制地址(共享对象)。
- 深拷贝:创建新对象,递归复制所有引用对象,完全独立。
- 实现方式:
clone()方法、序列化、手动递归。
在引用拷贝和浅拷贝上都出现过BUG,在订单优惠计算的第一版代码中采用
A a = new A();
A aa = a;
这种引用拷贝的的结果直接导致订单优惠前价格和优惠后价格一致;
第二版代码中采用clone()的方式,结果仍不对,浅拷贝的方式也仅仅复制了新的对象,其属性仍然是指向同一地址。
最后次采用了org.apache.commons.lang3.SerializationUtils.clone()的方式实现深拷贝,实际上只需将对象序列化下即可(implements Serializable),现在再看序列化这种方式,AI给的建议是不推荐理由有下:
- 性能开销大:序列化和反序列化涉及 I/O 操作(即使是内存流),需要将对象转换为字节流,对于大对象或频繁拷贝的场景性能较差。
- 必须实现
Serializable:所有需要拷贝的对象及其成员都必须实现该接口,否则会抛出NotSerializableException。 transient字段丢失:标记为transient的字段不会被序列化,反序列化后会被赋予默认值(基本类型为 0/false,引用类型为 null)。如果这些字段需要被复制,则不能使用此方法。- 序列化版本号问题:如果类的定义发生了变化(如添加字段),且没有显式声明
serialVersionUID,反序列化可能因版本不匹配而失败。 - 安全性问题:反序列化会执行对象的
readObject()方法(如果有自定义),可能被恶意构造的字节流利用,引发安全漏洞。需要确保反序列化的数据来源可信。 - 静态字段不会被复制:静态变量属于类,不属于对象,因此不会被序列化,反序列化后依然是原来的静态值。
- 某些对象不可序列化:如
Thread、Socket、OutputStream等与 JVM 状态紧密相关的类通常没有实现Serializable,无法通过这种方式深拷贝。
== 和 equals() 的区别
==用于判断“是不是同一个对象”(引用地址相同)或基本类型值相同。equals()默认也是判断地址,但通常被重写为判断“内容是否相同”。- 在开发中,比较基本类型用
==,比较对象内容用equals(),比较对象地址用==(但很少需要)。
在这个知识点上我自认为没什么大的问题,直到看到这句话:
当使用字符串字面量创建 String 类型的对象(如String aa = "ab")时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用;如果没有,就在常量池中创建一个 String 对象并赋给当前引用。但当使用new关键字创建对象(如String a = new String("ab"))时,虚拟机总是会在堆内存中创建一个新的对象并使用常量池中的值(如果没有,会先在字符串常量池中创建字符串对象 "ab")进行初始化,然后赋给当前引用。
著作权归JavaGuide(javaguide.cn)所有 基于MIT协议 原文链接:javaguide.cn/java/basis/…
其中“虚拟机总是会在堆内存中创建一个新的对象并使用常量池中的值”,在以前的的思想中我认为new String("ab")不管怎么字符串常量池都会创建新的char[],实际是:
如果之前没有出现过 "ab" 字面量,第一个 new String("ab") 也会在常量池中创建 "ab" 对象。此后所有通过 new String("ab") 创建的堆对象,其 value 都共享该常量池中的字符数组。
有时候,慢下来补补基础,反而是走得更远的开始。