1 Java 不可变对象
1.1 什么是不可变类
- 定义:一个类的对象在通过构造方法创建后,其状态(成员变量值)不会再被改变,这样的类称为不可变(immutable)类。
- 特点:
- 所有成员变量的赋值仅在构造方法中完成。
- 不提供任何 setter 方法供外部类修改成员变量。
- 优点:
- 对象状态不可变,每次修改状态都会产生新对象,避免并发问题。
1.2 常见的不可变类
- String 类:
- 常量池:字符串常量池是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串在常量池中不存在,那么就创建一个;假如已经存在,就不会再创建了,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。
- hashCode:因为字符串是不可变的,所以在它创建的时候,其 hashCode 就被缓存了,因此非常适合作为哈希值(比如作为HashMap的键),多次调用只返回同一个值,来提高效率。
- 线程安全:如果对象的状态是可变的,那么在多线程环境下,就很容易造成不可预期的结果。而 String 是不可变的,就可以在多个线程之间共享,不需要同步处理。
- 方法返回新对象:当我们调用 String 类的任何方法(比如说 trim()、substring()、toLowerCase())时,总会返回一个新的对象,而不影响之前的值。
- 包装类
- 除了 String 类,包装器类 Integer、Long 等也是不可变类
1.3 手撸一个不可变类
1.3.1 条件
- 类声明为 final:防止子类修改。
- 成员变量声明为 final:确保初始化后不可更改。
- 不提供 setter 方法:防止外部修改状态。
- 修改状态返回新对象:例如,String 的 substring() 方法。
1.3.2 代码示例
- 来自定义一个简单的不可变类 Writer
/**
* @package: com.yunyang.javabetter.oop.immutabledemo
* @description: 定义一个简单的不可变类 Writer
* @author: Yunyang
* @date: 2024/10/16 9:21
* @version:1.0
**/
public final class Writer {
private final String name;
private final int age;
private final Book book;
public Writer(String name, int age, Book book) {
this.name = name;
this.age = age;
this.book = book;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Book getBook() {
Book clone = new Book();
clone.setPrice(this.book.getPrice());
clone.setName(this.book.getName());
return clone;
}
}
Writer 类是 final 的,name、age 和 book 也是 final 的,没有 setter 方法
- Book 类是这样定义的
/**
* @package: com.yunyang.javabetter.oop.immutabledemo
* @description: Book类
* @author: Yunyang
* @date: 2024/10/16 9:23
* @version:1.0
**/
public class Book {
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
2 个字段,分别是 name 和 price,以及 getter 和 setter,重写后的 toString() 方法
- 测试类
/**
* @package: com.yunyang.javabetter.oop.immutabledemo
* @description: 测试类
* @author: Yunyang
* @date: 2024/10/16 9:26
* @version:1.0
**/
public class WriteDemo {
public static void main(String[] args) {
Book book = new Book();
book.setName("Java进阶之路");
book.setPrice(79);
Writer writer = new Writer("沉默王二", 18, book);
System.out.println("定价: " + writer.getBook());
writer.getBook().setPrice(59);
System.out.println("促销价:" + writer.getBook());
}
- 注意:如果一个不可变类中包含了可变类的对象,那么就需要确保返回的是可变对象的副本。所以,Writer 类中的 getBook() 方法被修改为:
public Book getBook() {
Book clone = new Book();
clone.setPrice(this.book.getPrice());
clone.setName(this.book.getName());
return clone;
}
- 运行结果:
定价: Book{name='Java进阶之路', price=79}
促销价:Book{name='Java进阶之路', price=79}
1.4 小结
不可变对象是 Java 中一种重要的设计模式,能够提高代码的安全性、可维护性和性能。掌握不可变类的概念和设计原则,对于编写高质量的 Java 代码至关重要。
2 Java 方法重写和方法重载
2.1 引入
方法重载:
- 定义:一个类中可以有多个名字相同但参数个数或类型不同的方法。
- 目的:提高程序的可读性,避免为相似功能的方法取不同的名字。
方法重写:
- 定义:子类中定义与父类相同的方法(方法名、参数、返回类型相同,但方法体可能不同)。
- 目的:提供父类方法的特殊实现,实现多态。
2.2 方法重载
实现方式:
- 改变参数数目:例如,
add(int a, int b)
和add(int a, int b, int c)
。 - 改变参数类型:例如,
add(int a, int b)
和add(double a, double b)
。
重载 main 方法:
- 尽管可以重载
main()
方法,但程序只认标准写法public static void main(String[] args)
。
重载与类型转换:
- 当传递的参数类型不匹配时,会发生隐式类型转换。
- 例如,
byte
可以转换为short
、int
、long
、float
、double
。
2.3 方法重写
规则:
- 方法名相同。
- 参数相同。
- 继承关系(is-a 关系)。
重写时应当遵守的规则:
- 只能重写继承的方法:只能重写
public
、protected
或default
修饰的方法,private
方法无法被重写。 - final、static 方法不能被重写:
final
方法无法被子类继承,因此无法重写。static
方法属于类,而不是对象,因此无法重写。
- 参数列表必须相同。
- 返回类型必须相同。
- 权限修饰符不能更严格:
default
方法可以重写为default
、protected
或public
。protected
方法可以重写为protected
或public
。public
方法只能重写为public
。
- 不能抛出更高级别的异常:只能抛出父类方法抛出异常的子类或不抛出异常。
- 使用 super 调用父类方法。
- 构造方法不能被重写:构造方法与类名相同,子类无法重写父类的构造方法。
- 抽象方法必须在子类中重写。
- synchronized 不影响重写规则:
synchronized
关键字用于多线程环境,不影响重写规则。 - strictfp 不影响重写规则:
strictfp
关键字用于精确浮点运算,不影响重写规则。
2.4 小结
方法重载:
- 两同:在同一个类,方法名相同。
- 一不同:参数不同。
方法重写:
- 两同:方法名相同,参数相同。
- 一小:子类方法声明的异常类型要比父类小或相等。
- 一大:子类方法的访问权限要比父类大或相等。
3 思维导图
4 参考链接
- 聊聊Java中的不可变对象
- 方法重写 Override 和方法重载 Overload 有什么区别