文章目录
- 面向对象程序设计概述
- 使用预定义类
- 用户自定义类
- 静态字段与静态方法
- 方法参数
- 对象构造
- 包
- JAR 文件
- 文档注释
- 类设计技巧
面向对象程序设计概述
面向对象程序设计(OOP)在 20 世纪 70 年代出现,是当今主流编程范型,Java 是面向对象语言,需熟悉 OOP 才能用好 Java。OOP 中,每个对象包含公开功能和隐藏实现部分,对象来源有标准库和自定义两种。
传统面向过程程序设计通过一系列过程求解问题,先确定过程再考虑数据存储;OOP 则将数据放在首位,再考虑操作数据的结构,对于大规模问题,OOP 更便于理解和维护。
类
- 类是构造对象的模板或蓝图,由类构造(construct)对象的过程称为创建类的实例(instance)。
- 封装是处理对象的重要概念,即将数据和操作数据的方法封装在类中,使对象使用者隐藏具体实现方式,提高代码的重用性和可靠性。Java 中所有类都源自
Object
类,可通过扩展其他类构建新类。
对象
- 想要使用 OOP,一定清楚对象三个主要特性:
-
对象的行为(behaviour):可以对对象完成那些操作,或者可以对对象应用那些方法?
-
对象的状态(state):当调用那些方法时,对象会如何响应?
-
对象的标识(identity):如何区分具有相同行为与状态的不同对象?
-
识别类
- 在面向对象程序设计中,可从识别类开始编程,识别类的简单经验是分析问题中的名词(对应类)和动词(对应方法) 。
类之间的关系
- 在类之间,最常见关系有
- 依赖(“uses-a”):表示一个类使用另一个类的对象。
- 聚合(“has-a”): 表示一个类使用另一个类的对象。
- 继承(“is-a”):表示特殊类与一般类的关系。
使用预定义类
在 Java 中,不是所有类都有典型的面向对象特征,如 Math 类,只需知道其方法名和参数即可使用,体现了封装特性。下面将会介绍 Date 类,从中可以看出如何构造对象,以及如何调用类的方法。
对象与对象变量
-
Date 类:可描述时间点,但用其描述时间存在不同地区日期表示差异等问题。可通过构造器创建对象,调用
toString
方法获取日期字符串描述。 -
使用预定义类时,需先构造对象,通过构造器(构造函数)完成,如 Date 类的构造器为 Date ()。可使用
new Date()
创建 Date 对象,还能对新创建对象调用方法,如System.out.println(new Date())
。 -
常将构造的对象存于变量中,如
Date birthday = new Date();
。对象变量和对象不同,未初始化的对象变量不能调用对象方法,必须先让其引用一个对象。Java 中对象变量的值是对对象的引用,还可设置变量为null
表示不引用任何对象。
// 引用同一个对象的对象变量
Date birthday = new Date();
deadline = birthday;
C++注释:很多人误以为 Java 中的对象变量等同于 C++ 中的引用,但实际上 C++ 没有
null
引用,且引用不能赋值。Java 的对象变量更类似于 C++ 的对象指针。**例如,Java 中的Date birthday;
,实际等同于 C++ 中的Date* birthday;
,并且 C++ 中Date*
指针需使用new
调用才会初始化,这一点和 Java 创建对象语法类似,如Date* birthday = new Date();
。**如果将一个变量复制到另一个变量,在 Java 和 C++ 中两个变量都指向同一个对象,Java 中的null
引用对应 C++ 中的NULL
指针。Java 对象都存储在堆中,当一个对象包含另一个对象变量时,存储的是指向另一个堆对象的指针。在 C++ 中,指针使用容易出错,可能出现内存管理问题;而 Java 中不存在这些问题,未初始化的指针在运行时系统会产生运行时错误,而非随机结果,且 Java 有垃圾回收器处理内存管理相关事宜。
C++ 通过支持复制构造器和赋值运算符实现对象的自动复制,比如链表复制后是新链表,内容和原链表相同但链接独立;在 Java 中,若要获得对象完整副本,必须使用
clone
方法 。
Java 类库中的 LocalDate 类
- LocalDate 类用于处理日期,其时间原点是 1970 年 1 月 1 日 00:00:00 UTC。创建 LocalDate 对象不建议用构造器,而应使用静态工厂方法,如
LocalDate.now()
获取当前日期,LocalDate.of(1999, 12, 31)
创建指定日期的对象 。
更改器方法与访问器方法
- 可通过
getYear
、getMonthValue
、getDayOfMonth
等方法获取年、月、日信息;plusDays
方法用于根据当前对象生成新的日期对象,且原对象不受影响。 - 只访问对象且修改对象的方法修改器方法(mutator method);只访问对象不修改对象的方法称为访问器方法(accessor method)。
C++注释: C++ 中,带
const
后缀就是访问器方法,没有则是更改器方法;Java 中这两种方法在语法上无明显区别。
用户自定义类
Java 中类的最简定义形式,包含字段(field)、构造器(constructor)和方法(method)。
employee 类
- 给出一个用于工资管理系统的
Employee
类代码示例,包含私有实例字段name
(姓名)、salary
(工资)、hireDay
(雇佣日期);构造器用于初始化这些字段; - 还有
getName
、getSalary
、getHireDay
等公共方法用于获取对应字段值,以及raiseSalary
方法用于给员工涨薪。
import java.time.*;
public class EmployeeTest {
public static void main() {
// fill the staff array with three Employee objects
Employee[] staff = new Employee[3];
staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
// raise everyone's salary by 5%
for (Employee e : staff)
e.raiseSalary(5);
// print out information about all Employee objects
for (Employee e : staff)
System.out.println("name=" + e.getName() + ", salary=" + e.getSalary() + ", hireDay=" + e.getHireDay());
}
}
class Employee {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String n, double s, int year, int month, int day) {
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public LocalDate getHireDay() {
return hireDay;
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
}
多个源文件的使用
- 一个源文件可以包含两个类,如果每个类存放在单独源文件中,如
Employee
类存于Employee.java
,EmployeeTest
类存于EmployeeTest.java
。 - 有两种编译源程序的方法:
- 使用通配符编译所有匹配源文件
javac Employee*.java
; - 直接使用命令
javac EmployeeTest.java
,若EmployeeTest.java
使用了Employee
类,编译器会自动查找编译Employee.java
- 使用通配符编译所有匹配源文件
剖析 Employee 类
Employee
类的所有方法都标记为public
,意味着任何类的方法都可调用这些方法。- 类中有 3 个私有实例字段
name
、salary
、hireDay
,用private
修饰确保只有Employee
类自身的方法能访问,将实例字段标记为private
对数据封装和安全性的重要性,不建议用public
标记实例字段,否则会破坏封装性。 - 指出
name
字段是String
类对象,hireDay
字段是LocalDate
类对象,说明类包含的实例字段通常属于某个类类型 。
从构造器开始
-
以
Employee
类的构造器public Employee(String n, double s, int year, int month, int day)
为例,类的构造器与类同名,在创建类的对象时会运行,用于将实例字段初始化为特定状态。如new Employee("James Bond", 100000, 1958, 1, 1)
会初始化相应的name
、salary
和hireDay
字段。 -
构造器总是结合
new
运算符调用,不能对已存在对象调用构造器重新设置实例字段;每个类可以有一个以上构造器,参数可以有 0 个、1 个或多个,且构造器没有返回值。
警告:不要在构造器中定义与实例字段同名的局部变量,否则会遮蔽实例字段。
C++注释:Java 构造器工作方式与 C++ 类似,但 Java 对象在堆中构造,且必须结合
new
操作符。
用 var 声明局部变量
- 在 Java 10 中,若能从变量初始值推导出类型,可用
var
关键字声明局部变量,如var harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
,这样可避免重复写类型名。 var
只能用于方法中的局部变量,参数和字段的类型仍需声明。
C++注释: C++11 引入了
auto
特性,编译器可根据初始化值自动推导变量类型。它能简化复杂类型声明,增强模板编程灵活性,使用时变量必须初始化。
使用 null 引用
- 对象变量可包含对象引用或特殊值
null
,表示不引用任何对象。 - 对
null
值应用方法会产生NullPointerException
异常,类似 “索引越界” 异常,严重时会终止程序。
隐式参数与显式参数
-
以
Employee
类的raiseSalary
方法为例,调用中的参数分为隐式参数和显式参数,隐式参数是出现在方法名前的对象(即方法调用的目标对象),显式参数是位于方法名后面括号中的值。例如number007.raiseSalary(5)
,number007
是隐式参数,5
是显式参数。 -
在方法中,
this
关键字指示隐式参数。可以通过this
明确区分实例字段和局部变量。
public void raiseSalary(double byPercent) {
double raise = this.salary * byPercent / 100;
this.salary += raise;
}`
C++注释:Java 方法都在类内部定义,而 C++ 在类外定义普通方法,在类内定义自动成为内联方法,Java 中方法是否内联由虚拟机决定 。
封装的优点
- 以
Employee
类的getName
、getSalary
和getHireDay
方法为例,这些是典型的访问器方法(也称为字段访问器),用于返回实例字段值。 - 封装优点:
- 将实例字段标记为私有,通过公共访问器和更改器方法操作,可以保护数据不被随意破坏。
- 可以改变类的内部实现,而不影响其他代码。
- 更改器方法可以进行错误检查。
警告:不要编写返回可变对象引用的访问器方法,否则可能破坏封装性。例如原
Employee
类的getHireDay
方法返回Date
对象引用存在问题,因为Date
类有更改器方法,会导致对象状态被意外改变。正确的做法是对可变对象进行克隆(clone)后返回,如
return (Date) hireDay.clone();
。
基于类的访问权限
- 以
Employee
类的equals
方法为例,在 Java 中,一个类的方法不仅可以访问调用该方法的对象的私有数据,还能访问所属类的所有对象的私有数据。
C++注释:C++也有同样的原则。方法可以访问所属类任何对象的私有特性(feature),而不仅于隐式参数。
私有方法
-
在 Java 中,将方法的访问修饰符从
public
改为private
即可实现私有方法。 -
私有方法的实现方式改变时,类设计者无需保证其可用性;若数据表示改变,私有方法可能受影响,但这并不重要,因为私有方法不会在别处被使用,可根据需要删除。而公共方法不能随意删除,因为可能有其他代码依赖 。
-
实现类时,为保证数据安全,应将数据字段设为私有。多数方法设计为公共的,但在特殊情况下,可将一些仅用于辅助计算、与当前实现关系紧密或需特定调用协议及顺序的方法设为私有方法,这些方法不应成为公共接口的一部分。
final 实例字段
- 可以将实例字段定义为
final
,这种字段必须在构造对象时初始化,且之后不能再修改。 final
修饰符对基本类型或不可变类的字段很有用,如String
类是不可变的。- 对于可变类的字段使用
final
修饰需谨慎,例如private final StringBuilder evaluations;
,final
只是保证evaluations
变量的引用不会指向其他StringBuilder
对象,但对象本身的内容是可以更改的 。
静态字段与静态方法
静态字段
- 用
static
修饰的字段为静态字段,每个类只有一个静态字段,而非静态的实例字段每个对象都有自己的副本。
classe Employee {
private static int nextId = 1;
private int id;
public void setId() {
id = nextId;
nextId++;
}
}
静态常量
-
静态变量使用较少,但静态常量很常用。如
Math
类中的PI
是静态常量,可通过Math.PI
访问。System.out
也是静态常量,它被声明为final
,不能再重新赋值 。 -
System
类中有setOut
方法可修改System.out
,这是因为该方法是原生方法,可绕过 Java 的访问控制机制,在自行编写程序时不应模仿 。
静态方法
- 静态方法是不在对象上执行的方法,如
Math
类的pow
方法,没有隐式参数。 - 静态方法适用于两种情况:一是方法不需要访问对象状态,所有参数通过显式参数提供;二是方法只需要访问类的静态字段 。
C++注释:Java 中静态字段与静态方法的功能和 C++ 相同,但语法有差异。在 C++ 中,要使用
::
操作符访问作用域之外静态字段和静态方法。
工厂方法
- 静态方法的一种常见用途是作为工厂方法来构造对象,像
LocalDate
类的now
和of
方法。 NumberFormat
类不使用构造器而采用工厂方法:- 构造器名字必须与类名相同,无法通过不同名字分别得到货币实例和百分比实例;
- 使用构造器不能改变所构造对象的类型,而工厂方法可返回
NumberFormat
子类
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.gNumberFormat.etPercentInstance();
main 方法
-
静态方法是可以在不创建对象的情况下调用的方法,如调用
Math.pow
无需构造Math
类对象,main
方法也是静态方法 。 -
main
方法在启动程序时执行,此时还没有对象,它的作用是执行并构造程序所需的对象 。 -
每个类可以有一个
main
方法,这常用于类的单元测试。
方法参数
public static void tripleSalary(double x) {
x = 3 * x;
}
tripleValue
方法试图将参数值增至 3 倍,但调用后原始变量值不变。因为按值调用时,方法中的参数是原始值的副本,对副本的修改不会影响原始变量,方法结束后副本不再使用 。
public static void swap(Employee x, Employee y) {
Employee tmp = x;
x = y;
y = tmp;
}
编写swap
方法试图交换两个Employee
对象,但实际无法实现。因为方法中的参数是对象引用的副本,交换的是副本,原始变量引用的对象并未改变,方法结束后副本被丢弃,原始变量不受影响 。
-
Java中对方法参数能做什么和不能做什么:
-
方法不能修改基本数据类型的参数。
-
方法可以改变对象参数的状态。
-
方法不能让一个对象参数引用一个新的对象 。
-
C++注释:C++中有按值调用和按引用调用。引用参数标有&符号。例如,可以轻松地实现
void tripleValue(double&x)
方法或void swap(Employee&x,Employee&y)
方法来修改它
们的引用参数。
对象构造
重载
- 一些类有多个构造器,这种现象称为重载。指定初始字符串来构造对象。多个方法名字相同但参数不同就出现了重载 。
var messages = new StringBuilder();
var todoList = new StringBuilder("To do:\n");
默认字段初始化
- 在构造器中若未显式给字段设置初始值,字段会自动赋默认值,数值为 0、布尔值为
false
、对象引用为null
。 - 很多类包含无参数的构造器,用于创建对象时将对象状态设为适当默认值。
警告:若编写类时未写构造器,系统会提供一个无参数构造器,将实例字段设为默认值。但如果类中提供了至少一个构造器而没有无参数构造器,那么构造对象时不提供参数会产生错误。
只有当类没有其他构造器时,才会有默认的无参数构造器,若想让类使用者能通过无参数方式构造对象,就必须自行提供无参数构造器 。
显式字段初始化
- 通过重载类的构造器,可以采用多种形式设置类实例字段的初始状态。
- 初始值也可以是方法调用的结果。
class Employee {
private static nextId;
private int id = assginId();
private static int assginId() {
int r = nextId;
nextId++;
return r;
}
}
C++注释:从 C++11 开始也支持直接在类的定义中初始化实例字段,也可以通过初始化列表语法来调用字段构造器, Java 无需这种方式 。
参数名
- 编写小型构造器时,常用单个字母作参数名,如
public Employee(String n, double s)
,但这样不易理解参数含义。 - 部分程序员会在参数前加前缀 “a”,如
public Employee(String aName, double aSalary)
,使参数含义更清晰。 - 由于参数变量会遮蔽同名实例字段,可利用
this
关键字访问实例字段。
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
C++注释:在C++中,经常用下划线或某个固定的字母(一般选用m或x)作为实例字段的前缀。例如,
salary
字段可能被命名为_salary
、mSalary
或xSalary
。Java程序员通常不这样做。
调用另一个构造器
this
关键字除指示隐式参数外,若构造器的第一个语句形如this(...)
,则表示调用同一个类的另一个构造器。
public Employee(double s) {
// call Employee(String, double)
this("Employee #" + nextId, s);
nextId++;
}
C++注释:从 C++11 标准开始支持构造函数调用其他构造函数,
ClassName::ClassName(parameters) : ClassName(otherParameters) { // ... }
;
初始化块
-
除了在构造器中赋值和声明时赋值,Java 还有初始化块机制。类声明中的初始化块,在构造对象时会执行。
-
若构造器首行调用其他构造器,按参数执行被调用的构造器;若没有,先将数据字段初始化为默认值,再按声明顺序执行字段初始化和初始化块,最后执行构造器主体。
对象析构与 finalize 方法
- C++ 有显式析构器用于对象不再使用时执行清理,回收存储空间,而 Java 因自动垃圾回收,不支持显式析构器。
- Java 对象若使用文件等内存外资源,资源不再需要时回收很重要。用完需立即关闭的资源,可提供
close
方法清理。
警告:明确不要用
finalize
方法清理,它原在垃圾回收前调用,但调用时间不确定且已被弃用。
包
包名
- Java 允许用包将类组织在一起,方便管理代码并与其他代码库分开。使用包可确保类名唯一性,包名由因特网域名逆序加工程名构成,如
com.horstmann.corejava
。
类的导入
- 一个类可使用所属包及其他包中的公共类。访问方式有两种,一是使用完全限定名,如
java.time.LocalDate today = java.time.LocalDate.now();
;二是使用import
语句,可导入特定类或整个包,import
语句应位于源文件顶部。
C++注释:
import
和#include
二者不同,#include
用于加载外部特性,C++ 编译器无法查看其他文件内部,而 Java 编译器可以;Java 显式给出类名可避免使用import
,C++ 无法避免使用#include
;import
语句使引用类更简洁;Java 的package
和import
类似于 C++ 的命名空间(namespace)和using
指令 。
静态导入
- 有一种
import
语句可用于导入静态方法和静态字段,而非仅导入类。例如,通过import static java.lang.System.*;
,可在使用System
类的静态方法和字段时省略类名前缀,如out.println("Goodbye, World!");
和exit(0);
; - 也可以导入特定的方法或字段,如
import static java.lang.System.out;
。虽然这种写法简洁,但可能影响代码清晰度 。
在包中增加类
-
若要将类放入包中,需在源文件开头添加包名,如
package com.horstmann.corejava;
。若未放置package
语句,类属于无名包。 -
源文件应放在与包名匹配的子目录中,编译器会将类文件也放在相同目录结构下。例如,
com.horstmann.corejava
包中的源文件应放在com\horstmann\corejava
(Windows 系统)目录中 。
包访问
- Java 中用
public
和private
修饰符控制访问权限,public
部分可被任意类使用,private
部分只能由定义类使用。若未指定,相关部分(类、方法或变量)可被同一包中的方法访问。
类路径
- 类存储在文件系统目录或 JAR(Java 归档)文件中,类路径需与包名匹配。JAR 文件用 ZIP 格式,能节省空间和提升性能。为使类可被多个程序共享,需将类文件或 JAR 文件放置在合适目录,并设置类路径(所有包含类文件路径的集合)。
设置类路径
- 最好使用
-classpath
(或-cp
、Java 9 中的--class-path
)选项指定类路径,也可通过设置CLASSPATH
环境变量设置。
警告:将
CLASSPATH
设为固定值或完全绕开类路径设置都可能引发问题,Java 9 中还可从模块路径加载类 。
JAR 文件
JAR(Java 归档)文件用于在打包应用程序时,将大量类文件和其他资源整合为单个文件,采用 ZIP 压缩格式,可包含图像、声音等文件 。
创建 JAR 文件
- 使用
jar
工具(位于jdk/bin
目录下)创建 JAR 文件,常用命令格式为jar cvf jarFileName file1 file2...
。
清单文件
- 每个 JAR 文件包含一个清单文件(manifest),位于
META-INF
子目录中,用于描述归档文件特性。简单的清单文件定义Manifest-Version
,复杂的清单文件包含主节和多个子节,指定文件、包或 URL 等属性。创建和更新清单文件有相应的jar
命令操作方式 。
可执行 JAR 文件
- 通过
jar
命令的e
选项或在清单文件中指定Main-Class
来指定程序入口点,从而创建可执行 JAR 文件。用户可通过java -jar
命令或在特定操作系统下双击图标启动程序。
多版本 JAR 文件
- Java 9 引入多版本 JAR 文件,可包含面向不同 Java 版本的类文件,以解决兼容性问题 。
关于命令行选项的说明
- JDK 命令行选项传统格式是单个短横线加字母,
jar
命令例外。从 Java 9 开始,转向新格式,多字母选项前加两个横线,常用选项有单字母快捷方式。
文档注释
JDK 中的javadoc
工具能根据源文件生成 HTML 文档。在源代码中添加以/**
开始的注释,可方便生成具有专业水平的 文档,且能保证代码和注释的一致性 。
注释的插入
javadoc
从模块、包、公共类与接口、公共和受保护的字段、公共和受保护的构造器及方法中抽取信息。- 注释以
/**
开始,*/
结束,包含标记和自由格式文本,可使用 HTML 修饰符,还可在注释中添加指向其他文件的链接 。
类注释
/**
A <code>Card</code> object represents a playing card, such
as "Queen of Hearts".A card has a suit (Diamond, Heart,
Spade or club) and a value (1 Ace,2...10,11 Jack,
12 Queen,13 King)
*/
public class Card {
// ...
}
方法注释
- 置于所描述方法之前,除通用标记外,还可用
@param
标记方法参数说明,@return
标记返回值说明,@throws
标记可能抛出的异常说明 。
/**
Raises the salary of an employee.
@param byPercent the percentage by which to raise the salary (e.g.,10 means 10%)
@return the amount of the raise
*/
public double raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
return raise;
}
字段注释
- 主要为公共字段(通常是静态常量)建立文档 。
/**
The“Hearts"card suit
*/
public static final int HEARTS = 1;
通用注释
@since
标记用于建立 “since” 条目,描述引入特性的版本;@author
标记生成 “author” 条目记录作者,可多个;@version
标记产生 “version” 条目描述当前版本 。@see
和@link
用于添加超链接,链接到javadoc
文档相关部分或外部文档。
包注释
- 类、方法和变量注释可直接写在 Java 源文件中,而包注释需在每个包目录中添加单独文件。
- 可以提供名为
package-info.java
的 Java 文件,包含以/**
和*/
界定的Javadoc
注释及一个package
语句;也可提供名为package.html
的 HTML 文件,抽取<body>...</body>
之间的文本作为包注释 。
注释抽取
- 若希望生成的 HTML 文档放在
docDirectory
目录下,先切换到源文件目录。 - 对于包,运行
javac -d docDirectory nameOfPackage
(多个包时列出所有包名); - 对于无名包中的文件,运行
javac -d docDirectory *.java
。
类设计技巧
- 保证数据私有:要确保类的数据字段为私有,避免破坏封装性。即便需要访问或修改数据,也应通过访问器或更改器方法,这样能防止数据表示形式变化影响类的使用者,且便于检测错误。
- 显式初始化数据:Java 会自动初始化对象的实例字段,但不初始化局部变量。设计类时,不应依赖系统默认值,而应显式初始化所有数据,可提供默认值或在构造器中设置 。
- 减少基本类型使用:尽量用其他类替换多个相关的基本类型,使类更易理解和修改。如用
Address
类替换Customer
类中与地址相关的多个基本类型字段,便于处理地址相关的变化 。 - 合理设置访问器和更改器:并非所有字段都需要单独的访问器和更改器方法,应根据实际需求决定,例如员工的雇佣日期可能无需更改器方法 。
- 避免职责过多的类:若一个类包含多个独立概念,应将其分解为更简单的类,但也要避免过度分解。以
CardDeck
类为例,将其拆分为表示牌堆和一张牌的两个类,使每个类职责更清晰 。 - 类名和方法名体现职责:类名和方法名应能准确反映其功能和含义,遵循标准惯例,如访问器方法以
get
开头,更改器方法以set
开头 。 - 优先使用不可变类:像
LocalDate
和java.time
包中的很多类是不可变的,不可变类能避免多线程环境下的并发修改问题,保证线程安全。对于表示值的类,如字符串、时间点等,设计成不可变类更为合适 。
例字段,但不初始化局部变量。设计类时,不应依赖系统默认值,而应显式初始化所有数据,可提供默认值或在构造器中设置 。 - 减少基本类型使用:尽量用其他类替换多个相关的基本类型,使类更易理解和修改。如用
Address
类替换Customer
类中与地址相关的多个基本类型字段,便于处理地址相关的变化 。 - 合理设置访问器和更改器:并非所有字段都需要单独的访问器和更改器方法,应根据实际需求决定,例如员工的雇佣日期可能无需更改器方法 。
- 避免职责过多的类:若一个类包含多个独立概念,应将其分解为更简单的类,但也要避免过度分解。以
CardDeck
类为例,将其拆分为表示牌堆和一张牌的两个类,使每个类职责更清晰 。 - 类名和方法名体现职责:类名和方法名应能准确反映其功能和含义,遵循标准惯例,如访问器方法以
get
开头,更改器方法以set
开头 。 - 优先使用不可变类:像
LocalDate
和java.time
包中的很多类是不可变的,不可变类能避免多线程环境下的并发修改问题,保证线程安全。对于表示值的类,如字符串、时间点等,设计成不可变类更为合适 。