
在uml类图中,构造器通常被表示为一个操作(operation),其名称与类名相同,并带有«create»的构造型(stereotype),返回类型为该类本身。例如,一个接收学生姓名的构造器可能表示为 + «create» student(name:string):student。然而,在许多面向对象语言(如java)的实践中,更常见且被广泛接受的约定是,构造器的方法名与类名相同,且不指定返回类型。
将UML设计转换为Java代码时,我们需要根据其意图实现对应的构造器。如果UML图中存在一个 Student(name:String) 的操作,其在Java中的实现将是一个公共的(public)构造方法,如下所示:
public class Student {
private String name;
// ... 其他成员变量
public Student(String name) {
this.name = name;
// ... 其他初始化逻辑
}
}构造器的核心职责是确保对象在创建时处于一个有效且一致的状态。对于类中的数组类型成员变量,如果它们需要在对象创建时被初始化为特定大小的空数组,那么这个初始化逻辑就应该放在构造器中。
例如,根据需求,每个学生有6个作业分数和3个考试分数。在构造Student对象时,我们应该为这些分数数组分配内存并初始化它们。
public class Student {
private String name;
private int[] homeworkScores; // 存储6个作业分数
private int[] examScores; // 存储3个考试分数
public Student(String name) {
this.name = name;
// 初始化作业分数数组,默认值为0
this.homeworkScores = new int[6];
// 初始化考试分数数组,默认值为0
this.examScores = new int[3];
}
// ... 其他方法
}通过这种方式,任何新创建的Student对象都将自动拥有初始化好的分数数组,避免了空指针异常,并确保了对象状态的完整性。
立即学习“Java免费学习笔记(深入)”;
除了构造器和基本的Getter/Setter方法,类通常还会包含一些业务逻辑方法,用于计算或处理其内部数据。例如,计算作业平均分和最终成绩的方法:
public class Student {
// ... 成员变量和构造器
/**
* 计算并返回学生的作业平均分。
* @return 作业平均分,如果无作业则返回0.0。
*/
public double getHomeworkAverage() {
if (homeworkScores.length == 0) {
return 0.0;
}
int sum = 0;
for (int score : homeworkScores) {
sum += score;
}
return (double) sum / homeworkScores.length;
}
/**
* 计算并返回学生的最终成绩。
* 评分标准:考试1占15%,考试2占25%,考试3占30%,作业平均分占30%。
* @return 最终成绩。
*/
public double getFinalScore() {
if (examScores.length != 3) {
// 考试分数数量不正确,可抛出异常或返回特定值
throw new IllegalStateException("Exam scores array must contain exactly 3 elements.");
}
double exam1Weight = 0.15;
double exam2Weight = 0.25;
double exam3Weight = 0.30;
double homeworkWeight = 0.30;
double examScorePart = (examScores[0] * exam1Weight) +
(examScores[1] * exam2Weight) +
(examScores[2] * exam3Weight);
double homeworkAveragePart = getHomeworkAverage() * homeworkWeight;
return examScorePart + homeworkAveragePart;
}
// ... 其他方法
}当类中包含数组类型的成员变量时,直接通过Getter方法返回数组引用或通过Setter方法直接接收外部数组引用,会带来严重的封装性问题,即“表示暴露”(representation exposure)。
问题描述:
int[] scores = student.getHomeworkScores(); scores[0] = 1000; // 内部的homeworkScores数组被修改了!
int[] externalScores = {90, 85, 70, 95, 80, 75};
student.setHomeworkScores(externalScores);
externalScores[0] = 0; // 内部的homeworkScores数组被修改了!解决方案:防御性复制(Defensive Copying) 为了避免表示暴露,我们应该在Getter和Setter方法中进行防御性复制。
Getter方法: 返回内部数组的一个副本,而不是原始引用。
public int[] getHomeworkScores() {
return homeworkScores.clone(); // 返回数组的副本
}
public int[] getExamScores() {
return examScores.clone(); // 返回数组的副本
}Setter方法: 接收外部数组时,创建该数组的副本并存储,而不是直接存储外部引用。同时,可以添加校验逻辑,确保传入数组的合法性。
public void setHomeworkScores(int[] homeworkScores) {
if (homeworkScores == null || homeworkScores.length != 6) {
throw new IllegalArgumentException("Homework scores array must contain exactly 6 elements.");
}
this.homeworkScores = homeworkScores.clone(); // 存储传入数组的副本
}
public void setExamScores(int[] examScores) {
if (examScores == null || examScores.length != 3) {
throw new IllegalArgumentException("Exam scores array must contain exactly 3 elements.");
}
this.examScores = examScores.clone(); // 存储传入数组的副本
}结合上述所有原则,一个健壮的Student类实现如下:
public class Student {
private String name;
private int[] homeworkScores; // 存储6个作业分数
private int[] examScores; // 存储3个考试分数
/**
* 构造一个新的Student对象。
*
* @param name 学生的姓名。
*/
public Student(String name) {
this.name = name;
this.homeworkScores = new int[6]; // 初始化6个作业分数的数组
this.examScores = new int[3]; // 初始化3个考试分数的数组
}
// --- Getter 和 Setter 方法 ---
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 获取作业分数数组的副本。
*
* @return 包含学生作业分数的数组副本。
*/
public int[] getHomeworkScores() {
return homeworkScores.clone(); // 返回副本,防止外部直接修改内部状态
}
/**
* 设置作业分数。
*
* @param homeworkScores 包含6个作业分数的数组。
* @throws IllegalArgumentException 如果传入数组为空或长度不为6。
*/
public void setHomeworkScores(int[] homeworkScores) {
if (homeworkScores == null || homeworkScores.length != 6) {
throw new IllegalArgumentException("Homework scores array must contain exactly 6 elements.");
}
this.homeworkScores = homeworkScores.clone(); // 存储副本,防止外部修改后影响内部状态
}
/**
* 获取考试分数数组的副本。
*
* @return 包含学生考试分数的数组副本。
*/
public int[] getExamScores() {
return examScores.clone(); // 返回副本,防止外部直接修改内部状态
}
/**
* 设置考试分数。
*
* @param examScores 包含3个考试分数的数组。
* @throws IllegalArgumentException 如果传入数组为空或长度不为3。
*/
public void setExamScores(int[] examScores) {
if (examScores == null || examScores.length != 3) {
throw new IllegalArgumentException("Exam scores array must contain exactly 3 elements.");
}
this.examScores = examScores.clone(); // 存储副本,防止外部修改后影响内部状态
}
// --- 业务逻辑方法 ---
/**
* 计算并返回学生的作业平均分。
*
* @return 作业平均分,如果无作业则返回0.0。
*/
public double getHomeworkAverage() {
if (homeworkScores.length == 0) {
return 0.0;
}
int sum = 0;
for (int score : homeworkScores) {
sum += score;
}
return (double) sum / homeworkScores.length;
}
/**
* 计算并返回学生的最终成绩。
* 评分标准:考试1占15%,考试2占25%,考试3占30%,作业平均分占30%。
*
* @return 最终成绩。
* @throws IllegalStateException 如果考试分数数量不正确。
*/
public double getFinalScore() {
if (examScores.length != 3) {
throw new IllegalStateException("Exam scores array must contain exactly 3 elements.");
}
double exam1Weight = 0.15;
double exam2Weight = 0.25;
double exam3Weight = 0.30;
double homeworkWeight = 0.30;
double examScorePart = (examScores[0] * exam1Weight) +
(examScores[1] * exam2Weight) +
(examScores[2] * exam3Weight);
double homeworkAveragePart = getHomeworkAverage() * homeworkWeight;
return examScorePart + homeworkAveragePart;
}
}将UML类图转换为Java对象是一个将设计概念具象化的过程。在这个过程中,理解和正确实现构造器至关重要,它决定了对象初始状态的有效性。对于包含数组等可变对象作为成员变量的类,必须高度重视封装性,并采取防御性复制的策略。这不仅能防止外部代码意外或恶意地修改对象的内部状态,还能提高代码的健壮性和可维护性,是编写高质量Java代码的重要实践。始终牢记,一个设计良好的类应该能够完全控制其内部数据的访问和修改。
以上就是优化UML类图到Java对象转换:构造器、数组初始化与封装实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号