0

0

Java中构造函数与继承的陷阱:避免无限循环

DDD

DDD

发布时间:2025-10-15 10:43:13

|

212人浏览过

|

来源于php中文网

原创

Java中构造函数与继承的陷阱:避免无限循环

java面向对象编程中,不当的构造函数设计,尤其是在继承体系中包含用户交互或复杂逻辑时,极易引发意料之外的递归调用,导致程序陷入无限循环。本文将深入剖析这种“无循环却循环”的现象,揭示其根源在于子类构造器隐式或显式调用父类构造器时,父类构造器中包含的逻辑被重复执行。我们将提供清晰的解决方案,指导开发者如何重构代码,将复杂的业务逻辑和用户输入从构造函数中分离,从而确保程序行为的正确性和可维护性。

理解问题:为何代码会“无限循环”?

许多开发者在初次接触Java继承时,可能会遇到一个令人困惑的问题:尽管代码中没有显式的 for 或 while 循环,但程序却反复执行某些代码块,尤其是在对象创建阶段。这种现象通常发生在子类构造函数调用父类构造函数(super())时,如果父类构造函数内部包含了用户输入或创建新对象的逻辑,就可能导致意料之外的递归调用。

考虑以下简化后的代码结构:

class Person {
    public Person(String agentId, String password, String address) {
        // ... 其他初始化代码 ...
        Scanner input = new Scanner(System.in);
        System.out.println("[1]AGENT");
        System.out.println("[2]CUSTOMER");
        int choice = input.nextInt(); // 这里会等待用户输入

        if (choice == 1) {
            // 如果选择1,这里可能会创建Agent对象
            Agent agent = new Agent("Niel", "diko alam", "umay");
        } else if (choice == 2) {
            System.out.println("POTANGINA");
        }
        // input.close(); // 重要的资源管理,但此处不是核心问题
    }
}

class Agent extends Person {
    public Agent(String agentId, String password, String address) {
        super(agentId, password, address); // 显式或隐式调用父类Person的构造函数
        // ... Agent特有的初始化代码 ...
    }
}

public class Finals {
    public static void main(String[] args) {
        // 尝试创建一个Person对象,然后创建一个Agent对象
        Person person = new Person("20860132", "h208f32", "San luis");
        Agent agent = new Agent("20860132", "h208f32", "San luis"); // 问题由此开始
    }
}

在上述代码中,当 main 方法执行 new Agent(...) 时,会发生以下步骤:

  1. Agent 类的构造函数被调用。
  2. Agent 构造函数的第一行(隐式或显式地)调用 super(agentId, password, address),即 Person 类的构造函数。
  3. Person 构造函数开始执行,它会打印菜单 [1]AGENT 和 [2]CUSTOMER,并等待用户输入。
  4. 如果用户输入 1,Person 构造函数内部会尝试创建一个新的 Agent 对象:Agent agent = new Agent(...)。
  5. 这个新的 Agent 对象的创建又会重复步骤 1-4,从而形成一个无限递归调用的链条,导致程序看似“无限循环”。

这就是所谓的“构造函数递归陷阱”,它不是由传统的循环语句引起的,而是由不当的构造函数设计和继承机制共同作用的结果。

立即学习Java免费学习笔记(深入)”;

解决方案:将业务逻辑与用户交互移出构造函数

解决此问题的核心原则是:构造函数应专注于初始化对象的状态,而非执行复杂的业务逻辑或用户交互。 用户输入和对象类型选择的逻辑应该放在构造函数之外,例如在 main 方法或专门的工厂方法中。

以下是重构后的代码示例:

PaperFake
PaperFake

AI写论文

下载

1. 修正 Person 类构造函数

将用户输入和对象创建选择逻辑从 Person 构造函数中移除。Person 构造函数现在只负责初始化 Person 对象的属性。

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;

class Person {
    protected String agentId;
    protected String password;
    protected String address;

    // 构造函数只负责初始化属性
    public Person(String agentId, String password, String address) {
        this.agentId = agentId;
        this.password = password;
        this.address = address;
        // 移除用户输入和对象创建逻辑
    }
    // ... 其他方法(如果需要)
}

2. 修正 Agent 类构造函数

Agent 类的构造函数现在可以专注于 Agent 对象的初始化,并调用 super() 来初始化 Person 部分的属性。

class Agent extends Person {

    public Agent(String agentId, String password, String address) {
        super(agentId, password, address); // 调用父类构造函数初始化Person属性
        // 代理特有的登录和操作逻辑可以放在这里,或者更推荐放在Agent对象创建后调用的方法中
        // 为了演示,我们将登录逻辑保留在构造函数中,但实际应用中应考虑进一步分离
        Scanner input2 = new Scanner(System.in);
        System.out.println("[LOGIN]");
        System.out.print("ENTER AGENT ID:");
        // 建议使用 nextLine() 读取整行,然后解析为 int,以避免 Scanner 缓冲区问题
        String idStr = input2.nextLine(); 
        int id = Integer.parseInt(idStr);

        System.out.print("ENTER PASSWORD:");
        String passStr = input2.nextLine();
        int pass = Integer.parseInt(passStr);

        if (id == 20860132 && pass == 20020729) {
            System.out.println("AGENT LOGIN SUCCESSFUL.");
            // 登录成功后的操作菜单可以放在一个单独的方法中,例如 operateAgentMenu()
            // Scanner input = new Scanner(System.in); // 不应在构造函数内频繁创建Scanner
            // ... 菜单逻辑 ...
        } else {
            System.out.println("INCORRECT PLEASE TRY AGAIN.");
        }
        // input2.close(); // 重要的资源管理,但需注意在整个应用生命周期中Scanner的创建和关闭策略
    }

    // ... addCar, schedule, records 等方法 ...
    public void addCar(List<String> cars) {
        try (FileWriter fw = new FileWriter("cars.txt", true);
             PrintWriter pw = new PrintWriter(fw)) { // 使用 try-with-resources 自动关闭资源
            pw.println(cars);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 其他方法类似修改为 try-with-resources
}

3. 修正 Customer 类构造函数

与 Agent 类似,Customer 构造函数也应只专注于初始化。

class Customer extends Person {
    private String customerId;

    public Customer(String agentId, String password, String address, String customerId) {
        super(agentId, password, address);
        this.customerId = customerId;
    }

    // ... 其他方法 ...
}

4. 修正 main 方法:集中处理用户交互和对象创建

现在,main 方法将负责处理用户选择是创建 Agent 还是 Customer,并根据选择调用相应的构造函数。

public class Finals {
    public static void main(String[] args) {
        Scanner mainInput = new Scanner(System.in); // 在main方法中创建一次Scanner

        System.out.println("Welcome to the System!");
        System.out.println("[1] AGENT");
        System.out.println("[2] CUSTOMER");
        System.out.print("Please choose your role: ");

        int choice = -1;
        try {
            choice = mainInput.nextInt();
            mainInput.nextLine(); // 消费掉换行符,避免影响后续 nextLine() 调用
        } catch (java.util.InputMismatchException e) {
            System.out.println("Invalid input. Please enter a number.");
            mainInput.nextLine(); // 清除无效输入
            // 可以选择重新提示用户输入或退出
            return; 
        }

        Person user = null; // 定义一个Person引用来存储创建的对象

        if (choice == 1) {
            // 创建 Agent 对象
            Agent agent = new Agent("20860132", "h208f32", "San luis");
            user = agent; // 将agent对象赋给user引用
            // 登录成功后,可以调用 Agent 对象的菜单方法
            // agent.showAgentMenu(mainInput); // 假设Agent有一个显示菜单的方法
        } else if (choice == 2) {
            // 创建 Customer 对象
            Customer customer = new Customer("defaultAgentId", "defaultPass", "defaultAddress", "CUST001");
            user = customer; // 将customer对象赋给user引用
            System.out.println("CUSTOMER role selected.");
            // 登录成功后,可以调用 Customer 对象的菜单方法
            // customer.showCustomerMenu(mainInput); // 假设Customer有一个显示菜单的方法
        } else {
            System.out.println("Invalid choice. Exiting.");
        }

        // 可以在这里根据user的类型执行后续操作
        if (user != null) {
            System.out.println("User created: " + user.getClass().getSimpleName());
            // 例如,如果user是Agent,可以调用其特有方法
            // if (user instanceof Agent) {
            //     ((Agent) user).someAgentSpecificMethod();
            // }
        }

        mainInput.close(); // 在程序结束时关闭Scanner
    }
}

注意事项与进一步优化

  1. Scanner 资源管理: 在原始代码中,Scanner 对象在多个地方被创建,但没有被关闭。这可能导致资源泄露。最佳实践是在程序的入口点(如 main 方法)创建一个 Scanner 对象,并将其传递给需要它的方法,最后在程序结束时关闭它。或者,对于局部使用的 Scanner,使用 try-with-resources 语句确保其被关闭。
  2. nextInt() 和 nextLine() 的混用问题: 当 Scanner 的 nextInt() 或 next() 方法后面紧跟着 nextLine() 方法时,nextLine() 可能会意外地读取到 nextInt() 留下的换行符。解决方法是在 nextInt() 之后立即调用一个空的 nextLine() 来消费掉这个换行符。
  3. 输入验证: 用户的输入可能不是预期的数字,或者超出有效范围。在实际应用中,应加入输入验证和错误处理机制(例如 try-catch 块捕获 InputMismatchException 或 NumberFormatException)。
  4. 职责分离: 进一步地,可以将登录逻辑、菜单显示逻辑等从构造函数中分离出来,作为类的方法。这使得构造函数更加简洁,并提高了代码的可测试性和可维护性。例如,Agent 类可以有一个 login() 方法和一个 showOperationsMenu() 方法。
  5. 工厂模式: 对于根据用户选择创建不同类型的对象(Agent 或 Customer)的场景,可以考虑引入工厂模式,例如创建一个 PersonFactory 类,其中包含一个静态方法 createPerson(int choice),根据选择返回相应的 Person 子类实例。这能更好地封装对象创建的逻辑。

通过遵循这些原则和建议,开发者可以有效避免因构造函数设计不当导致的无限循环问题,并构建出更加健壮、可维护的Java应用程序。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

107

2023.09.25

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

58

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

63

2025.11.27

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1031

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

614

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

334

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

235

2025.08.29

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

25

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

44

2026.03.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.4万人学习

C# 教程
C# 教程

共94课时 | 11.3万人学习

Java 教程
Java 教程

共578课时 | 81.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号