
在java开发中,初学者常会在控制流结构(如do-while循环和switch语句)以及面向对象特性(如继承和构造器)的使用上遇到挑战。本文将深入探讨两个常见的错误场景:变量作用域问题导致的cannot find symbol错误,以及继承中子类构造器调用父类构造器不当导致的constructor cannot be applied错误,并提供详细的解决方案和优化建议。
1. 理解变量作用域与do-while循环
在使用do-while循环时,一个常见的错误是将循环控制变量声明在do块内部。由于Java的作用域规则,在代码块({})内部声明的变量,其生命周期和可见性仅限于该代码块。这意味着,如果choice变量在do块内部声明,那么while条件语句就无法访问它,从而导致cannot find symbol编译错误。
错误示例分析:
do {
// ...
Scanner input = new Scanner(System.in);
int choice = input.nextInt(); // choice 在 do 块内部声明
// ...
} while (choice != 0); // 错误:choice 在此处不可见解决方案: 要解决这个问题,只需将循环控制变量(例如choice)的声明移到do-while循环的外部。这样,choice就拥有了更广的作用域,可以在整个do-while结构中被访问和修改。同时,Scanner对象也应在循环外部创建一次,以避免不必要的资源开销和潜在的资源泄漏。
修正后的代码片段(Main类):
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in); // Scanner 在循环外部声明一次
int choice; // choice 声明在 do-while 循环外部
do {
System.out.println("\nWhat size dog do you want to get!:\n" +
"1) small\n" + "2) medium\n" + "3) large\n" + "0) quit\n" +
"and any other number for non dog options!");
choice = input.nextInt(); // 在循环内部读取用户输入
switch (choice) {
case 1:
// ... 处理小型犬逻辑 ...
break;
case 2:
// ... 处理中型犬逻辑 ...
break;
case 3:
// ... 处理大型犬逻辑 ...
break;
case 0:
// 用户选择退出,无需额外操作,循环条件会处理
break;
default:
System.out.println("We are only giving dogs up for adoption today!");
}
} while (choice != 0); // choice 在此处可见
System.out.println("Goodbye\n");
input.close(); // 关闭 Scanner 资源
}
}2. Java继承中构造器的正确使用
在Java的继承体系中,子类构造器在执行前必须先调用其父类的构造器。这是因为子类会继承父类的成员变量,而这些成员变量的初始化通常由父类的构造器负责。如果子类构造器没有显式调用父类构造器,编译器会尝试插入一个默认的无参父类构造器调用(super())。然而,如果父类只定义了带参数的构造器而没有无参构造器,就会导致constructor Animal in class Animal cannot be applied to given types的编译错误。
立即学习“Java免费学习笔记(深入)”;
此外,子类不应重复声明父类中已经存在的成员变量。子类通过继承自动拥有父类的公共和受保护成员。如果子类再次声明同名成员,会造成“隐藏”父类成员的现象,这通常不是期望的行为,且会增加代码的复杂性和出错的可能性。
错误示例分析:
Animal 类:
public class Animal {
private String name;
private Integer age;
private String breed;
private String behavior;
public Animal(String name, Integer age, String breed, String behavior) {
this.name = name;
this.age = age;
this.breed = breed;
this.behavior = behavior;
}
// ... 其他方法 ...
}Dog 类(错误):
public class Dog extends Animal{
// 冗余地重复声明了父类已有的成员变量
public String name;
public int age;
public String breed;
public String behavior;
public Dog(String name, Integer age, String breed, String behavior) {
// 没有调用父类构造器,且试图重新初始化父类已有的成员
this.name = name;
this.age = age;
this.breed = breed;
this.behavior = behavior;
}
}解决方案:
- 在子类构造器的第一行,使用super()关键字显式调用父类的构造器,并传入相应的参数来初始化父类的成员。
- 移除子类中与父类重复的成员变量声明。子类会自动继承这些成员。
修正后的代码片段(Dog类):
public class Dog extends Animal {
// 无需在此重复声明 name, age, breed, behavior 等父类已有的成员变量
// 如果 Dog 类有 Animal 类没有的特有属性,则在此声明。
public Dog(String name, Integer age, String breed, String behavior) {
super(name, age, breed, behavior); // 显式调用父类 Animal 的构造器
}
// ... Dog 类特有的方法或属性 ...
}3. 优化用户交互与程序控制流
根据用户需求,“如果用户输入5表示同意领养,则显示再见消息;如果输入6或更高,则循环返回主菜单。”这要求我们对程序的控制流进行更精细的设计。原始的do-while (choice != 0)只处理了0退出,没有考虑到5的退出逻辑。
优化思路:
- 主循环的退出条件应包含用户选择“退出”(0)和“领养”(5)两种情况。
- 在用户选择具体犬种(case 1, 2, 3)后,再提示用户是否领养,并根据用户的二次输入来更新主循环的控制变量。
修正后的Main类代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int choice; // 主菜单选择
final int QUIT_OPTION = 0;
final int ADOPT_OPTION = 5;
final int NO_ADOPT_OPTION_MIN = 6;
do {
System.out.println("\nWhat size dog do you want to get!:\n" +
"1) small\n" + "2) medium\n" + "3) large\n" +
QUIT_OPTION + ") quit\n" +
"and any other number for non dog options!");
choice = input.nextInt();
switch (choice) {
case 1:
System.out.println("We have one small breeded dog. A Chihuahua is up for adoption!");
System.out.println("\nHere is more information!");
Chihuahua goliath = new Chihuahua("Goliath", 4, "Chihuahua", "aggressive");
goliath.printDogInfo();
System.out.println("\nTo take me home press " + ADOPT_OPTION + ", or " + NO_ADOPT_OPTION_MIN + " or higher for no!");
int takeHomeChoice1 = input.nextInt();
if (takeHomeChoice1 == ADOPT_OPTION) {
choice = ADOPT_OPTION; // 设置 choice 为 5,使主循环终止
}
// 如果 takeHomeChoice1 >= 6,choice 保持不变,主循环会继续
break;
case 2:
System.out.println("We have one medium breeded dog. A Labrador is up for adoption!");
System.out.println("\nHere is more information!");
Labrador hendrix = new Labrador("Hendrix", 8, "Labrador", "not aggressive");
hendrix.printDogInfo();
System.out.println("\nTo take me home press " + ADOPT_OPTION + ", or " + NO_ADOPT_OPTION_MIN + " or higher for no!");
int takeHomeChoice2 = input.nextInt();
if (takeHomeChoice2 == ADOPT_OPTION) {
choice = ADOPT_OPTION;
}
break;
case 3:
System.out.println("We have one large breeded dog. A Great dane is up for adoption!");
System.out.println("\nHere is more information!");
Greatdane toodles = new Greatdane("Toodles", 2, "Greatdane", "not aggressive");
toodles.printDogInfo();
System.out.println("\nTo take me home press " + ADOPT_OPTION + ", or " + NO_ADOPT_OPTION_MIN + " or higher for no!");
int takeHomeChoice3 = input.nextInt();
if (takeHomeChoice3 == ADOPT_OPTION) {
choice = ADOPT_OPTION;
}
break;
case QUIT_OPTION:
// 用户直接选择退出,choice 已经是 0,循环条件会处理
break;
default:
System.out.println("We are only giving dogs up for adoption today!");
// 其他无效输入,循环继续
}
} while (choice != QUIT_OPTION && choice != ADOPT_OPTION); // 循环直到选择退出 (0) 或领养 (5)
System.out.println("\nGoodbye!"); // 统一的退出消息
input.close(); // 关闭 Scanner 资源
}
}注意事项与总结:
- 变量作用域: 始终确保变量在需要访问它的代码块中声明,或者声明在更广阔的作用域中。
- 继承与构造器: 子类构造器必须通过super()显式调用父类构造器来完成父类部分的初始化。避免在子类中重复声明父类已有的成员变量。
- 程序控制流: 精心设计循环条件和内部逻辑,以准确反映用户的意图和程序的预期行为。对于复杂的交互,可以考虑使用嵌套循环或更复杂的条件判断。
- 资源管理: 对于像Scanner这样的资源,在使用完毕后务必调用close()方法释放资源,避免资源泄漏。
- 代码可读性: 使用常量(final关键字)代替“魔术数字”(Magic Numbers),如QUIT_OPTION、ADOPT_OPTION,可以显著提高代码的可读性和可维护性。
通过遵循这些原则,您可以编写出更健壮、更易于理解和维护的Java代码。










