Java家谱数据硬编码:问题、方案与最佳实践
2025-03-10 08:20:00
Java 中硬编码家谱数据的几种方式
直接把值写死在代码里(硬编码)这种做法,在搞 Java 项目,尤其是要弄一个家谱应用的时候,时不时会遇到。想弄个初始化的家谱数据,又不知道从哪儿下手,这确实挺让人头疼的。下面我来给你掰扯掰扯,分析一下硬编码的各种姿势,再给你支几招,保管你能把这事儿给办漂亮了。
一、 硬编码带来的问题
硬编码一时爽,代码改起来可就麻烦了。想想看,如果你直接把家谱成员的信息(名字、性别、住址啥的)都写死在代码里,会有啥问题?
- 改起来费劲: 要是家谱里某人的信息变了,比如搬家了,或者名字写错了,你得找到代码里对应的地方,一个一个改。家谱小还好,要是家谱大了,几百号人,你改到猴年马月去?
- 数据和逻辑混一块: 把数据和处理数据的逻辑都搅和在一起,代码看起来乱糟糟的,以后想加个新功能,比如搜索某个成员,都不知道从哪儿下手。
- 不好测试: 硬编码的数据,让测试变得很麻烦。你得手动去检查每个成员的信息是不是对的,太不方便了。
二、 硬编码家谱数据的几种方案
既然硬编码有这么多毛病,那我们该怎么处理家谱数据呢?下面我给你介绍几种不同的硬编码方式,你可以根据自己的实际情况选择合适的方案。
1. 直接在 main
方法或者初始化方法里创建对象
这是最直接粗暴的方式。直接用 new
创建 FamilyMember
对象,然后设置各个属性,再把这些对象连接起来,构成一个家谱。
原理: 这种方法的好处是很直观, 容易理解。
坏处上面说了
代码示例:
public class FamilyTreeApp {
public static void main(String[] args) {
// 创建家庭成员对象
FamilyMember grandfather = new FamilyMember("张三", "张", null, "1950-2020", Gender.MALE, new Address("北京"));
FamilyMember grandmother = new FamilyMember("李四", "李", null, "1952-2022", Gender.FEMALE, new Address("北京"));
FamilyMember father = new FamilyMember("张伟", "张", null, "1975-", Gender.MALE, new Address("上海"));
FamilyMember mother = new FamilyMember("王芳", "王", "张", "1978-", Gender.FEMALE, new Address("上海"));
FamilyMember son = new FamilyMember("张小明", "张", null, "2000-", Gender.MALE, new Address("上海"));
// 设置亲属关系
grandfather.setSpouse(grandmother);
grandmother.setSpouse(grandfather);
father.setFather(grandfather);
father.setMother(grandmother);
father.setSpouse(mother);
mother.setSpouse(father);
son.setFather(father);
son.setMother(mother);
grandfather.addChild(father);
grandmother.addChild(father);
father.addChild(son);
mother.addChild(son);
// 初始化家谱树
TreeImpl familyTree = new TreeImpl(grandfather);
// 打印家庭成员信息 (这里只是个例子,你可以根据需要修改)
System.out.println("爷爷: " + familyTree.getRoot().getFirstName());
System.out.println("家庭成员数量 " + familyTree.getAllMembers().size());
System.out.println("儿子: " + familyTree.getDetailsForMember("张小明").getFirstName());
}
}
//Gender 和 Address 需要自己提前定义好. 这里只是示例.
enum Gender {
MALE, FEMALE
}
class Address {
String city;
public Address(String city) {
this.city = city;
}
//为了打印输出好看,可以添加一个tostring
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
'}';
}
}
安全建议:
- 这种方式没啥特别的安全问题,就是记得把代码写规范了。
2. 使用配置文件
把家谱数据放到一个单独的配置文件里,比如 JSON
、XML
或者 properties
文件。然后在程序启动的时候,读取配置文件,把数据加载到内存里。
原理: 这样做的好处是,数据和代码分离了。以后想修改数据,直接改配置文件就行了,不用动代码。
代码示例 (使用 JSON 文件):
首先,创建一个 family_tree.json
文件,内容如下:
[
{
"firstName": "张三",
"surName": "张",
"surNameAfterMarriage": null,
"life": "1950-2020",
"gender": "MALE",
"address": {"city": "北京"},
"spouse": "李四",
"children": ["张伟"]
},
{
"firstName": "李四",
"surName": "李",
"surNameAfterMarriage": null,
"life": "1952-2022",
"gender": "FEMALE",
"address": {"city": "北京"},
"spouse": "张三",
"children": ["张伟"]
},
{
"firstName": "张伟",
"surName": "张",
"surNameAfterMarriage": null,
"life": "1975-",
"gender": "MALE",
"address": {"city": "上海"},
"spouse": "王芳",
"children": ["张小明"],
"father": "张三",
"mother": "李四"
},
{
"firstName": "王芳",
"surName": "王",
"surNameAfterMarriage": "张",
"life": "1978-",
"gender": "FEMALE",
"address": {"city": "上海"},
"spouse": "张伟",
"children": ["张小明"],
"father": null,
"mother": null
},
{
"firstName": "张小明",
"surName": "张",
"surNameAfterMarriage": null,
"life": "2000-",
"gender": "MALE",
"address": {"city": "上海"},
"spouse": null,
"children": [],
"father": "张伟",
"mother": "王芳"
}
]
然后,在 Java 代码中读取这个 JSON 文件,并解析成 FamilyMember
对象:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class FamilyTreeApp {
public static void main(String[] args) throws IOException {
// 使用 Jackson 库读取 JSON 文件
ObjectMapper mapper = new ObjectMapper();
List<FamilyMember> members = mapper.readValue(new File("family_tree.json"), new TypeReference<List<FamilyMember>>() {});
//构建ID Map. 方便后续查找
Map<String, FamilyMember> memberMap = new HashMap<>();
for (FamilyMember member : members) {
memberMap.put(member.getFirstName(), member); // 这里假设FirstName 是唯一的. 实际可能用ID
}
//关联亲属关系
for(FamilyMember member : members){
if (member.getSpouse() != null && !member.getSpouse().getFirstName().isEmpty() ) {
member.setSpouse(memberMap.get(member.getSpouse().getFirstName()));
}
if(member.getFather() !=null && !member.getFather().getFirstName().isEmpty()){
member.setFather(memberMap.get(member.getFather().getFirstName()));
}
if(member.getMother() !=null && !member.getMother().getFirstName().isEmpty()){
member.setMother(memberMap.get(member.getMother().getFirstName()));
}
if (member.getChildren() != null ) {
for(int i = 0; i< member.getChildren().size(); i++){
FamilyMember child = member.getChildren().get(i);
if(child !=null && !child.getFirstName().isEmpty() ){
member.getChildren().set(i, memberMap.get(child.getFirstName()));
}
}
}
}
TreeImpl familyTree = new TreeImpl(members.get(0)); //假设第一个人是根节点
System.out.println("爷爷: " + familyTree.getRoot().getFirstName());
System.out.println("家庭成员数量 " + familyTree.getAllMembers().size());
System.out.println("儿子: " + familyTree.getDetailsForMember("张小明").getFirstName());
}
}
注意: 上面的代码用了 Jackson 库来解析 JSON。你需要先添加 Jackson 的依赖。
在 pom.xml
里加上:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version> </dependency>
安全建议:
- 如果配置文件里有敏感信息,比如身份证号、住址等,要做好加密和权限控制。
3. 使用数据库
如果你的家谱数据比较多,或者需要经常更新,可以考虑把数据放到数据库里,比如 MySQL、PostgreSQL 或者 SQLite。
原理: 数据库能帮你高效地存储和管理大量数据。用 SQL 语句就能方便地查询、修改和删除数据。
代码示例 (使用 SQLite):
这里简单起见用SQLite,省去了安装数据库的麻烦。当然,实际项目中你完全可以根据自己的情况选用MySQL,Oracle等。
首先, 下载 SQLite JDBC 驱动 (例如 sqlite-jdbc-*.jar
),把它加到你的项目里。
然后,创建一个 SQLite 数据库文件 (例如 family_tree.db
)。你可以用 SQLite 的命令行工具或者图形化工具来创建。
接着,创建一个表来存储家谱数据:
CREATE TABLE family_members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
first_name TEXT,
sur_name TEXT,
sur_name_after_marriage TEXT,
life TEXT,
gender TEXT,
city TEXT,
spouse_id INTEGER,
father_id INTEGER,
mother_id INTEGER,
FOREIGN KEY (spouse_id) REFERENCES family_members(id),
FOREIGN KEY (father_id) REFERENCES family_members(id),
FOREIGN KEY (mother_id) REFERENCES family_members(id)
);
然后,插入一些数据:
INSERT INTO family_members (first_name, sur_name, sur_name_after_marriage, life, gender, city, spouse_id, father_id, mother_id) VALUES
('张三', '张', NULL, '1950-2020', 'MALE', '北京', 2, NULL, NULL),
('李四', '李', NULL, '1952-2022', 'FEMALE', '北京', 1, NULL, NULL),
('张伟', '张', NULL, '1975-', 'MALE', '上海', 4, 1, 2),
('王芳', '王', '张', '1978-', 'FEMALE', '上海', 3, NULL, NULL),
('张小明', '张', NULL, '2000-', 'MALE', '上海', NULL, 3, 4);
接下来,在 Java 代码里连接数据库,查询数据,并创建 FamilyMember
对象:
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class FamilyTreeApp {
public static void main(String[] args) throws SQLException {
String url = "jdbc:sqlite:family_tree.db"; //数据库连接地址
List<FamilyMember> allMembers = new ArrayList<>();
try (Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM family_members")) {
//循环每一行记录
while (rs.next()) {
//读取数据
String firstName = rs.getString("first_name");
String surName = rs.getString("sur_name");
String surNameAfterMarriage = rs.getString("sur_name_after_marriage");
String life = rs.getString("life");
String genderStr = rs.getString("gender");
Gender gender = Gender.valueOf(genderStr); // 把字符串转换成枚举
String city = rs.getString("city");
Address address = new Address(city);
FamilyMember member = new FamilyMember(firstName,surName, surNameAfterMarriage,life, gender,address );
member.setMemberID(rs.getInt("id"));
allMembers.add(member);
}
}
//再次循环, 这次构建亲属关系. 这里假设数据库里ID是唯一的.
try (Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement()) {
for(FamilyMember member: allMembers){
int spouseId = getSpouseId(conn, member.getMemberID());
int fatherId = getFatherId(conn, member.getMemberID());
int motherId = getMotherId(conn, member.getMemberID());
if(spouseId>0) member.setSpouse(findMemberById(allMembers, spouseId));
if(fatherId>0) member.setFather(findMemberById(allMembers, fatherId));
if(motherId>0) member.setMother(findMemberById(allMembers, motherId));
List<Integer> childrenIds = getChildrenIds(conn, member.getMemberID());
for(int childId: childrenIds){
member.addChild(findMemberById(allMembers, childId));
}
}
}
TreeImpl familyTree = new TreeImpl(allMembers.get(0)); //假设第一个人是根
System.out.println("爷爷: " + familyTree.getRoot().getFirstName());
System.out.println("家庭成员数量 " + familyTree.getAllMembers().size());
System.out.println("儿子: " + familyTree.getDetailsForMember("张小明").getFirstName());
}
//辅助方法: 根据成员ID, 在列表中查找成员.
private static FamilyMember findMemberById(List<FamilyMember> members, int id) {
for (FamilyMember member : members) {
if (member.getMemberID() == id) {
return member;
}
}
return null; //没找到
}
private static int getSpouseId(Connection conn, int memberId) throws SQLException {
String sql = "SELECT spouse_id FROM family_members WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, memberId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt("spouse_id");
}
}
}
return -1; // 没有配偶
}
private static int getFatherId(Connection conn, int memberId) throws SQLException {
String sql = "SELECT father_id FROM family_members WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, memberId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt("father_id");
}
}
}
return -1; //
}
private static int getMotherId(Connection conn, int memberId) throws SQLException {
String sql = "SELECT mother_id FROM family_members WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, memberId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt("mother_id");
}
}
}
return -1;
}
private static List<Integer> getChildrenIds(Connection conn, int memberId) throws SQLException{
List<Integer> childrenIds = new ArrayList<>();
String sql = "SELECT id FROM family_members WHERE father_id = ? OR mother_id = ?";
try(PreparedStatement pstmt = conn.prepareStatement(sql)){
pstmt.setInt(1, memberId);
pstmt.setInt(2, memberId);
try(ResultSet rs = pstmt.executeQuery()){
while (rs.next()){
childrenIds.add(rs.getInt("id"));
}
}
}
return childrenIds;
}
}
安全建议:
- 连接数据库的用户名和密码要妥善保管,不要直接写在代码里。可以用环境变量或者专门的配置管理工具。
- 使用预编译语句 (PreparedStatement),防止 SQL 注入攻击。
- 对数据库做好备份。
进阶使用技巧
以上三种方法都可以实现硬编码初始化家谱树, 下面有一些更高级的玩法:
-
构建器模式 (Builder Pattern): 如果
FamilyMember
对象的创建过程比较复杂,比如有很多可选属性,或者属性之间有依赖关系,可以考虑使用构建器模式。这样可以让对象的创建过程更清晰,更易于维护。 -
工厂模式 (Factory Pattern): 如果你需要创建不同类型的家庭成员,比如普通成员、VIP 成员等,可以考虑使用工厂模式。
总而言之呢, 选哪种方法取决于你的实际情况和需求。数据量小、改动不频繁,直接在代码里创建对象就行;数据量大、经常改,就用数据库。关键是,要让你的代码干净、好维护、易扩展。