0

0

Java构造器循环调用陷阱与解决方案

DDD

DDD

发布时间:2025-10-12 11:08:31

|

582人浏览过

|

来源于php中文网

原创

Java构造器循环调用陷阱与解决方案

本文深入探讨了java中因构造器职责混淆导致的无限循环问题。通过分析一个实际案例,揭示了在父类构造器中包含用户输入和子类实例化逻辑,并通过 `super()` 调用形成递归的机制。文章提供了将输入逻辑从构造器中分离的解决方案,并强调了构造器单一职责、分离关注点以及健壮的输入处理等java编程最佳实践,旨在帮助开发者构建更清晰、可维护的代码。

问题现象与分析

在提供的Java代码中,开发者遇到了一个意外的“循环”现象:即使只输入一次选择,程序却反复显示“[1]AGENT”和“[2]CUSTOMER”的提示。

仔细分析代码可以发现问题的根源:

  1. Person 类有一个构造器,其中包含用户输入逻辑,用于选择角色(AGENT 或 CUSTOMER)。如果用户选择 1 (AGENT),它会在 Person 构造器内部创建一个 Agent 对象:Agent agent = new Agent("Niel", "diko alam", "umay");
  2. Agent 类继承自 Person 类,其构造器会通过 super(agentId, password, address); 调用 Person 类的构造器。
  3. 在 main 方法中,首先创建了一个 Person 对象:Person person = new Person("20860132", "h208f32", "San luis"); 这会执行 Person 构造器中的用户输入逻辑。
  4. 如果用户在 Person 构造器中选择 1 (AGENT),那么 Person 构造器会尝试创建一个 Agent 对象。创建 Agent 对象时,又会调用 Agent 的构造器,而 Agent 的构造器又会调用 super(),即再次调用 Person 的构造器。
  5. 这样就形成了一个递归调用链:main -> Person 构造器 -> Agent 构造器 -> Person 构造器 -> ... 导致用户输入提示无限次地重复出现。

这种“循环”并非由显式的 for 或 while 循环引起,而是由构造器之间的递归调用所致。

根本原因:构造器的职责混淆

问题的根本原因在于 Person 类的构造器承担了过多的职责。构造器的主要作用是初始化对象的状态,确保对象在创建后处于一个有效且一致的状态。然而,在示例代码中,Person 构造器不仅初始化了属性,还包含了:

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

  • 用户界面(UI)输出(System.out.println)。
  • 用户输入处理(Scanner)。
  • 基于用户输入的业务逻辑(创建 Agent 或 Customer 子类实例)。

当这些复杂的逻辑被放置在构造器中时,特别是当子类构造器又调用父类构造器时,很容易导致意料之外的行为,例如本例中的无限递归。这违反了软件设计中的“单一职责原则”(Single Responsibility Principle)。

Getimg.ai
Getimg.ai

getimg.ai是一套神奇的ai工具。生成大规模的原始图像

下载

解决方案:重构输入与对象创建逻辑

解决此问题的核心思想是将用户交互和基于用户选择的对象创建逻辑从构造器中分离出来,让构造器回归其初始化对象状态的本职。

核心重构步骤

  1. 将用户选择逻辑移出构造器: 把显示角色选项和获取用户输入的代码从 Person 构造器中移除。
  2. 在 main 方法或工厂方法中处理对象创建: 根据用户在 main 方法中或一个专门的工厂方法中的选择,直接实例化相应的 Person、Agent 或 Customer 对象。
  3. 构造器仅负责初始化: Person、Agent 和 Customer 的构造器只接收必要的参数,并用这些参数初始化对象的属性。

代码示例

以下是修改后的代码,展示了如何将输入逻辑从构造器中分离:

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner; // 确保 Scanner 被正确导入

// Person 类,构造器仅用于初始化属性
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;
        // 构造器中不再包含用户输入逻辑
    }
    // 可以添加 getter/setter 方法
}

// Agent 类,构造器仅用于初始化属性
class Agent extends Person {
    public Agent(String agentId, String password, String address) {
        super(agentId, password, address);
        // 代理登录和操作逻辑应在对象创建后调用,而不是构造器内
    }

    // 登录方法
    public boolean login(int id, int pass) {
        return id == 20860132 && pass == 20020729;
    }

    // 代理操作菜单
    public void showAgentMenu(Scanner input) {
        System.out.println("[1]ADD CAR");
        System.out.println("[2]SCHEDULE");
        System.out.println("[3]RECORDS");
        System.out.print("Enter your choice: ");
        int choice2 = input.nextInt();
        input.nextLine(); // 消费掉换行符

        switch (choice2) {
            case 1:
                addCarWorkflow(input);
                break;
            case 2:
                System.out.print("Enter schedule details: ");
                String scheduleDetails = input.nextLine();
                schedule(scheduleDetails);
                System.out.println("Schedule added.");
                break;
            case 3:
                System.out.print("Enter record details: ");
                String recordDetails = input.nextLine();
                records(recordDetails);
                System.out.println("Record added.");
                break;
            default:
                System.out.println("Invalid choice.");
        }
    }

    // 添加汽车流程
    private void addCarWorkflow(Scanner input) {
        boolean stopFlag = false;
        List cars = new ArrayList<>();
        cars.add("Tayota");
        cars.add("Hillux");
        cars.add("Bugatti");

        do {
            System.out.println("[Current Cars]: " + cars);
            System.out.print("Enter Car to add: ");
            String car = input.nextLine();
            cars.add(car);
            addCar(cars); // 将当前列表写入文件

            System.out.println("Would you like to add more?");
            System.out.println("[1]YES");
            System.out.println("[2]NO");
            System.out.print("Enter your choice: ");
            String choice3 = input.nextLine(); // 注意这里是 String
            if (!choice3.equals("1")) { // 比较字符串
                stopFlag = true;
            }
        } while (!stopFlag);
    }

    public void addCar(List cars) {
        try (FileWriter fw = new FileWriter("cars.txt", true);
             PrintWriter pw = new PrintWriter(fw)) {
            pw.println(cars);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void schedule(String schedule) {
        try (FileWriter fw = new FileWriter("schedule.txt", true);
             PrintWriter pw = new PrintWriter(fw)) {
            pw.println(schedule);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void records(String record) {
        try (FileWriter fw = new FileWriter("records.txt", true);
             PrintWriter pw = new PrintWriter(fw)) {
            pw.println(record);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 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;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    public String getCustomerId() {
        return customerId;
    }

    // 客户操作菜单
    public void showCustomerMenu(Scanner input) {
        System.out.println("[1]RENT CAR");
        System.out.println("[2]VIEW SCHEDULE");
        System.out.println("[3]EXTEND RENTAL");
        System.out.print("Enter your choice: ");
        int choice2 = input.nextInt();
        input.nextLine(); // 消费掉换行符

        switch (choice2) {
            case 1:
                System.out.print("Enter car to rent: ");
                String carToRent = input.nextLine();
                rentCar(carToRent);
                System.out.println("Car rented.");
                break;
            case 2:
                System.out.print("Enter schedule to view: ");
                String scheduleToView = input.nextLine();
                viewSchedule(scheduleToView);
                System.out.println("Schedule viewed.");
                break;
            case 3:
                System.out.print("Enter record to extend: ");
                String recordToExtend = input.nextLine();
                extend(recordToExtend);
                System.out.println("Rental extended.");
                break;
            default:
                System.out.println("Invalid choice.");
        }
    }

    public void rentCar(String car) {
        try (FileWriter fw = new FileWriter("rented_cars.txt", true); // 使用不同的文件名
             PrintWriter pw = new PrintWriter(fw)) {
            pw.println(car);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void viewSchedule(String schedule) {
        try (FileWriter fw = new FileWriter("viewed_schedules.txt", true); // 使用不同的文件名
             PrintWriter pw = new PrintWriter(fw)) {
            pw.println(schedule);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void extend(String record) {
        try (FileWriter fw = new FileWriter("extended_records.txt", true); // 使用不同的文件名
             PrintWriter pw = new PrintWriter(fw)) {
            pw.println(record);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 主类,包含 main 方法
public class Finals {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in); // 在 main 方法中创建 Scanner

        System.out.println("[1]AGENT");
        System.out.println("[2]CUSTOMER");
        System.out.print("Enter your choice: ");
        int choice = input.nextInt();
        input.nextLine(); // 消费掉 nextInt() 留下的换行符

        Person user = null; // 定义一个 Person 引用

        if (choice == 1) {
            // 代理登录流程
            System.out.println("[AGENT LOGIN]");
            System.out.print("ENTER AGENT ID:");
            int id = input.nextInt();
            input.nextLine(); // 消费掉换行符
            System.out.print("ENTER PASSWORD:");
            int pass = input.nextInt();
            input.nextLine(); // 消费掉换行符

            Agent agent = new Agent("20860132", "h208f32", "San luis"); // 创建 Agent 对象
            if (agent.login(id, pass)) {
                System.out.println("Login successful!");
                user = agent; // 将 agent 赋值给 user
                ((Agent)user).showAgentMenu(input); // 显示代理菜单
            } else {
                System.out.println("INCORRECT PLEASE TRY AGAIN.");
            }
        } else if (choice == 2) {
            // 客户流程 (此处仅为示例,可根据需求添加客户登录/注册逻辑)
            System.out.println("[CUSTOMER SECTION]");
            // 假设直接创建一个客户对象,或者通过某种方式获取客户ID
            Customer customer = new Customer("defaultAgentId", "defaultPassword", "defaultAddress", "CUST001");
            user = customer; // 将 customer 赋值给 user
            ((Customer)user).showCustomerMenu(input); // 显示客户菜单
        } else {
            System.out.println("Invalid choice. Exiting.");
        }

        input.close(); // 关闭 Scanner
    }
}

关键改进点:

  1. 构造器职责清晰: Person、Agent 和 Customer 的构造器现在只负责初始化对象的基本属性,不再处理用户输入或复杂的业务逻辑。
  2. 输入逻辑集中化: 用户选择角色、登录验证等输入和决策逻辑被移到了 main 方法中,使其成为程序的入口和控制流中心。
  3. 避免递归调用: Agent 构造器不再间接导致 Person 构造器递归创建 Agent 对象,从而消除了无限循环。
  4. 操作方法化: 将代理和客户的具体操作(如 showAgentMenu, showCustomerMenu, login, addCarWorkflow 等)封装成独立的方法,提高了代码的可读性和模块化。
  5. 资源管理: 在 main 方法结束时关闭了 Scanner 对象,避免资源泄露。文件写入也使用了 try-with-resources 语句,确保 FileWriter 和 PrintWriter 被正确关闭。
  6. Scanner.nextInt() 后跟 nextLine() 的处理: 在 nextInt() 后立即调用 input.nextLine() 来消费掉 nextInt() 遗留的换行符,避免后续 nextLine() 读取空字符串的问题。
  7. 字符串比较: 将 if(!choice3.equals(1)) 改为 if (!choice3.equals("1")),确保字符串与字符串进行比较。

Java编程最佳实践

此次问题及解决方案引出了几个重要的Java编程最佳实践:

  1. 构造器的单一职责原则: 构造器应专注于创建和初始化对象,使其处于一个有效状态。避免在构造器中执行耗时操作、复杂的业务逻辑、用户交互或文件I/O等。
  2. 分离关注点(Separation of Concerns): 将不同的功能模块(如用户界面、业务逻辑、数据存储、对象创建)分离到不同的类或方法中。这使得代码更易于理解、测试和维护。例如,用户输入和对象创建逻辑应与对象本身的初始化逻辑分离。
  3. 使用工厂方法模式(可选): 对于需要根据不同输入创建不同类型对象的情况,可以考虑使用工厂方法模式。例如,可以创建一个 PersonFactory 类,其中包含一个静态方法 createPerson(int choice, Scanner input),负责根据用户的选择返回 Agent 或 Customer 实例。这进一步解耦了对象创建逻辑。
  4. 健壮的输入处理:
    • 关闭 Scanner: 确保在不再需要 Scanner 对象时调用其 close() 方法,释放系统资源。
    • 处理 nextInt() 后遗留的换行符: 当使用 Scanner.nextInt()、nextDouble() 等方法读取数字后,缓冲区会留下一个换行符。如果紧接着调用 Scanner.nextLine(),它会立即读取这个换行符并返回一个空字符串。为了避免此问题,应在 nextInt() 后立即调用 input.nextLine() 来消费掉这个换行符。
    • 输入验证: 考虑用户输入非预期数据(例如,当期望数字时输入了文本)的情况,使用 try-catch 块处理 InputMismatchException,或使用 hasNextInt() 等方法进行预检查。
  5. 资源管理: 对于文件I/O等需要外部资源的场景,应使用 try-with-resources 语句(Java 7+)确保资源被正确关闭,即使发生异常。

总结

本教程通过一个具体的Java代码案例,详细阐述了因构造器职责混淆导致的递归调用和无限循环问题。核心解决方案在于将用户交互和对象创建的决策逻辑从构造器中剥离,使其专注于对象初始化。通过遵循构造器单一职责、分离关注点等编程最佳实践,并注意输入处理的细节,开发者可以构建出更加健壮、可维护且易于理解的Java应用程序。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

776

2023.08.22

while的用法
while的用法

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

94

2023.09.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1500

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

623

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

613

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

588

2024.04.29

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

31

2026.01.28

热门下载

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

精品课程

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

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.3万人学习

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

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