Pitfalls of Constructors and Inheritance in Java: Avoid Infinite Loops

In Java object-oriented programming, improper constructor design, especially when the inheritance system contains user interaction or complex logic, can easily lead to unexpected recursive calls, causing the program to fall into an infinite loop. This article will deeply analyze this "loop without loop" phenomenon and reveal that its root cause is that when the subclass constructor implicitly or explicitly calls the parent class constructor, the logic contained in the parent class constructor is repeatedly executed. We will provide clear solutions to guide developers on how to refactor their code and separate complex business logic and user input from constructors to ensure correctness and maintainability of program behavior.
Understanding the problem: Why does the code "infinite loop"?
Many developers may encounter a confusing problem when they first come into contact with Java inheritance: although there are no explicit for or while loops in the code, the program repeatedly executes certain blocks of code, especially during the object creation phase. This phenomenon usually occurs when a subclass constructor calls the parent class constructor (super()). If the parent class constructor contains user input or logic for creating new objects, it may lead to unexpected recursive calls.
Consider the following simplified code structure:
class Person {
public Person(String agentId, String password, String address) {
// ...other initialization code...
Scanner input = new Scanner(System.in);
System.out.println("[1]AGENT");
System.out.println("[2]CUSTOMER");
int choice = input.nextInt(); // This will wait for user input if (choice == 1) {
// If you choose 1, the Agent object may be created here Agent agent = new Agent("Niel", "diko alam", "umay");
} else if (choice == 2) {
System.out.println("POTANGINA");
}
// input.close(); // Important resource management, but not the core issue here}
}
class Agent extends Person {
public Agent(String agentId, String password, String address) {
super(agentId, password, address); // Explicitly or implicitly call the constructor of the parent class Person // ...Agent-specific initialization code...
}
}
public class Finals {
public static void main(String[] args) {
// Try to create a Person object, and then create an Agent object Person person = new Person("20860132", "h208f32", "San luis");
Agent agent = new Agent("20860132", "h208f32", "San luis"); // This is where the problem starts}
}
In the above code, when the main method executes new Agent(...), the following steps occur:
- The constructor of the Agent class is called.
- The first line of the Agent constructor calls (implicitly or explicitly) super(agentId, password, address), the constructor of the Person class.
- The Person constructor starts executing, it prints the menus [1]AGENT and [2]CUSTOMER, and waits for user input.
- If the user enters 1, the Person constructor will internally try to create a new Agent object: Agent agent = new Agent(...).
- The creation of this new Agent object will repeat steps 1-4, thus forming a chain of infinite recursive calls, causing the program to appear to be in an "infinite loop".
This is the so-called "constructor recursion trap", which is not caused by traditional loop statements, but is the result of a combination of improper constructor design and inheritance mechanisms.
Solution: Move business logic and user interaction out of the constructor
The core principle for solving this problem is that constructors should focus on initializing the state of the object rather than performing complex business logic or user interaction. Logic for user input and object type selection should be placed outside the constructor, such as in the main method or a dedicated factory method.
Here is an example of the refactored code:
1. Modify the Person class constructor
Removed user input and object creation selection logic from the Person constructor. The Person constructor is now only responsible for initializing the properties of the Person object.
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;
// The constructor is only responsible for initializing the properties public Person(String agentId, String password, String address) {
this.agentId = agentId;
this.password = password;
this.address = address;
// Remove user input and object creation logic}
// ... other methods (if needed)
}
2. Modify Agent class constructor
The constructor of the Agent class can now focus on the initialization of the Agent object and call super() to initialize the properties of the Person part.
class Agent extends Person {
public Agent(String agentId, String password, String address) {
super(agentId, password, address); // Call the parent class constructor to initialize the Person property // Agent-specific login and operation logic can be placed here, or it is more recommended to place it in the method called after the Agent object is created // For demonstration, we keep the login logic in the constructor, but in actual applications, further separation of Scanner input2 = new Scanner(System.in);
System.out.println("[LOGIN]");
System.out.print("ENTER AGENT ID:");
// It is recommended to use nextLine() to read the entire line and then parse it into int to avoid Scanner buffer problems 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.");
//The operation menu after successful login can be placed in a separate method, such as operateAgentMenu()
// Scanner input = new Scanner(System.in); // Scanner should not be created frequently within the constructor
// ...menu logic...
} else {
System.out.println("INCORRECT PLEASE TRY AGAIN.");
}
// input2.close(); // Important resource management, but you need to pay attention to the Scanner creation and closing strategy throughout the application life cycle}
// ... addCar, schedule, records and other methods...
public void addCar(List<string> cars) {
try (FileWriter fw = new FileWriter("cars.txt", true);
PrintWriter pw = new PrintWriter(fw)) { // Use try-with-resources to automatically close resources pw.println(cars);
} catch (IOException e) {
e.printStackTrace();
}
}
//Other methods are similarly modified to try-with-resources
}</string>
3. Modify the Customer class constructor
Like Agent, Customer constructor should focus only on initialization.
class Customer extends Person {
private String customerId;
public Customer(String agentId, String password, String address, String customerId) {
super(agentId, password, address);
this.customerId = customerId;
}
// ...other methods...
}
4. Modify the main method: focus on user interaction and object creation
Now, the main method will be responsible for handling the user's selection of whether to create an Agent or a Customer, and calling the appropriate constructor based on the selection.
public class Finals {
public static void main(String[] args) {
Scanner mainInput = new Scanner(System.in); // Create a Scanner once in the main method
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(); // Consume the newline character to avoid affecting subsequent nextLine() calls} catch (java.util.InputMismatchException e) {
System.out.println("Invalid input. Please enter a number.");
mainInput.nextLine(); // Clear invalid input // You can choose to re-prompt the user for input or exit return;
}
Person user = null; // Define a Person reference to store the created object if (choice == 1) {
//Create Agent object Agent agent = new Agent("20860132", "h208f32", "San luis");
user = agent; // Assign the agent object to the user reference // After successful login, you can call the menu method of the Agent object // agent.showAgentMenu(mainInput); // Assume that the Agent has a method to display the menu} else if (choice == 2) {
// Create a Customer object Customer customer = new Customer("defaultAgentId", "defaultPass", "defaultAddress", "CUST001");
user = customer; // Assign the customer object to the user reference System.out.println("CUSTOMER role selected.");
// After successful login, you can call the menu method of the Customer object // customer.showCustomerMenu(mainInput); // Assume that the Customer has a method to display the menu} else {
System.out.println("Invalid choice. Exiting.");
}
// You can perform subsequent operations here based on the type of user if (user != null) {
System.out.println("User created: " user.getClass().getSimpleName());
// For example, if user is Agent, its unique method can be called // if (user instanceof Agent) {
// ((Agent) user).someAgentSpecificMethod();
// }
}
mainInput.close(); // Close the Scanner at the end of the program
}
}
Notes and further optimization
- Scanner resource management: In the original code, Scanner objects were created in multiple places but were not closed. This may lead to resource leakage. Best practice is to create a Scanner object at the entry point of the program (such as the main method), pass it to the method that requires it, and finally close it when the program ends. Alternatively, for a locally used Scanner, use a try-with-resources statement to ensure it is closed.
- Mixing problem of nextInt() and nextLine(): When the Scanner's nextInt() or next() method is followed by the nextLine() method, nextLine() may accidentally read the newline character left by nextInt(). The solution is to call an empty nextLine() immediately after nextInt() to consume the newline character.
- Input validation: The user's input may not be the expected number, or may be outside the valid range. In practical applications, input validation and error handling mechanisms should be added (such as try-catch blocks to capture InputMismatchException or NumberFormatException).
- Separation of responsibilities: Further, login logic, menu display logic, etc. can be separated from the constructor and used as class methods. This makes the constructor more concise and improves the testability and maintainability of the code. For example, the Agent class can have a login() method and a showOperationsMenu() method.
- Factory pattern: For scenarios where different types of objects (Agent or Customer) are created based on user selection, you can consider introducing the factory pattern, such as creating a PersonFactory class that contains a static method createPerson(int choice) that returns the corresponding Person subclass instance based on the selection. This better encapsulates the logic of object creation.
By following these principles and suggestions, developers can effectively avoid infinite loop problems caused by improperly designed constructors and build more robust and maintainable Java applications.
The above is the detailed content of Pitfalls of Constructors and Inheritance in Java: Avoid Infinite Loops. For more information, please follow other related articles on the PHP Chinese website!
Hot AI Tools
Undress AI Tool
Undress images for free
AI Clothes Remover
Online AI tool for removing clothes from photos.
Undresser.AI Undress
AI-powered app for creating realistic nude photos
ArtGPT
AI image generator for creative art from text prompts.
Stock Market GPT
AI powered investment research for smarter decisions
Hot Article
Popular tool
Notepad++7.3.1
Easy-to-use and free code editor
SublimeText3 Chinese version
Chinese version, very easy to use
Zend Studio 13.0.1
Powerful PHP integrated development environment
Dreamweaver CS6
Visual web development tools
SublimeText3 Mac version
God-level code editing software (SublimeText3)
Hot Topics
20518
7
13631
4
How to configure Spark distributed computing environment in Java_Java big data processing
Mar 09, 2026 pm 08:45 PM
Spark cannot run in local mode, ClassNotFoundException: org.apache.spark.sql.SparkSession. This is the most common first step of getting stuck: even the dependencies are not correct. Only spark-core_2.12 is written in Maven, but spark-sql_2.12 is not added. SparkSession crashes as soon as it is built. The Scala version must strictly match the official Spark compiled version - Spark3.4.x uses Scala2.12 by default. If you use spark-sqljar of 2.13, the class loader cannot directly find the main class. Practical advice: Go to mvnre
How to safely map user-entered weekday string to integer value and implement date offset operation in Java
Mar 09, 2026 pm 09:43 PM
This article introduces a concise and maintainable way to map the weekday string (such as "Monday") to the corresponding serial number (1-7), and use the modulo operation to realize the forward and backward offset of any number of days (such as Monday plus 4 days to get Friday), avoiding lengthy if chains and hard-coded logic.
How to generate a list of duplicate elements using Java's Collections.nCopies_Initialization tips
Mar 06, 2026 am 06:24 AM
Collections.nCopies returns an immutable view. Calling add/remove will throw UnsupportedOperationException; it needs to be wrapped with newArrayList() to modify it, and it is disabled for mutable objects.
What is exception masking (Suppressed Exceptions) in Java_Multiple resource shutdown exception handling
Mar 10, 2026 pm 06:57 PM
What is SuppressedException: It is not "swallowed", but actively archived by the JVM. SuppressedException is not an exception loss, but the JVM quietly attaches the secondary exception to the main exception under the premise that "only one exception must be thrown" for you to verify afterwards. It is automatically triggered by the JVM in only two scenarios: one is that the resource closure in try-with-resources fails, and the other is that you manually call addSuppressed() in finally. The key difference is: the former is fully automatic and safe; the latter requires you to keep it to yourself, and it can be written as shadowing if you are not careful. try-
How to use Homebrew to install Java on Mac_A must-have Java tool chain for developers
Mar 09, 2026 pm 09:48 PM
Homebrew installs the latest stable version of openjdk (such as JDK22) by default, not the LTS version; you need to explicitly execute brewinstallopenjdk@17 or brewinstallopenjdk@21 to install the LTS version, and manually configure PATH and JAVA_HOME to be correctly recognized by the system and IDE.
How to correctly implement runtime file writing in Java applications (avoiding JAR internal write failures)
Mar 09, 2026 pm 07:57 PM
After a Java application is packaged as a JAR, data cannot be written directly to the resources in the JAR package (such as test.txt) because the JAR is essentially a read-only ZIP archive; the correct approach is to write variable data to an external path (such as a user directory, a temporary directory, or a configuration-specified path).
What is the underlying principle of array expansion in Java_Java memory dynamic adjustment analysis
Mar 09, 2026 pm 09:45 PM
ArrayList.add() triggers expansion because grow() is called when size is equal to elementData.length. The first add allocates 10 capacity, and subsequent expansion is 1.5 times and not less than the minimum requirement, relying on delayed initialization and System.arraycopy optimization.
How to safely read a line of integer input in Java and avoid Scanner blocking
Mar 06, 2026 am 06:21 AM
This article introduces typical blocking problems when using Scanner to read multiple integers in a single line. It points out that hasNextInt() will wait indefinitely when there is no subsequent input, and recommends a safe alternative with nextLine() string splitting as the core.





