1.类的名称与存储它的文件的名称不同
我使用过的所有 Java 框架(包括 Javasoft JDK)都假设带有 public 修饰符的类的源代码存储在与类名完全相同且扩展名为 .java 的文件中。不遵循此约定可能会导致编译期间出现的许多问题。Lab6.java
public class Airplane extends Vehicle
Seat pilot;
public Airplane() {
pilot = new Seat();
}
}
更正示例:文件名Airplane.java
public class Airplane extends Vehicle
Seat pilot;
public Airplane() {
pilot = new Seat();
}
}
请注意:假定类名以大写字母开头。文件名区分大小写的操作系统可能会带来额外的问题,特别是对于习惯了 DOS 文件命名系统的在 Unix 上学习 Java 的学生来说。该类MotorVehicle
应该存储在文件中MotorVehicle.java
,而不是motorvehicle.java
.
2. 比较使用==
在Java中,字符串是类的对象java.lang.String
。==
应用于对象的运算符检查对象引用的相等性!有时学生不理解运算符的语义==
而尝试使用它来比较字符串。 错误示例:
// проверим, equals ли первый аргумент "-a"
if (args[0] == "-a") {
optionsAll = true;
}
比较 2 个字符串是否相等的正确方法是使用equals()
类方法java.lang.String
。true
如果字符串长度相同且包含相同字符,则返回。(注意:实际上这并不能保证相等。事实上,equals
它逐个字符地检查 2 个字符串是否相等) 更正示例:
// проверим, equals ли первый аргумент "-a"
if ("-a".equals(args[0])) {
optionsAll = true;
}
这个错误很愚蠢,因为事实上Java代码在语法上是正确的,但最终却没有按预期工作。一些学生还尝试使用比较运算符>
来代替类<=
方法。此错误更容易检测,因为它会在编译阶段引起错误。 compareTo()
java.lang.String
3. 忘记初始化作为数组元素的对象。
在Java中,对象数组实际上是对象引用的数组。创建数组只是创建一组不指向任何内容的引用(即它们为空)。要实际创建一个“完整”对象数组,您需要初始化数组的每个元素。很多同学不明白这一点;他们相信,通过创建对象数组,他们会自动创建对象本身。(在大多数情况下,学生从 C++ 中引入了这个概念,在 C++ 中创建对象数组会导致通过调用默认构造函数来创建对象本身。)在下面的示例中,学生想要创建 3 个类的对象StringBuffer
。代码编译时不会出现错误,但在最后一行会出现异常NullPointerException
,因为该行访问了不存在的对象。 错误示例:
// Создаем массив из StringBuffer
StringBuffer [] myTempBuffers;
myTempBuffers = new StringBuffer[3];
myTempBuffers[0].add(data);
为了避免此错误,您必须记住初始化数组元素。 更正示例:
// Создаем массив из StringBuffer и инициализируем элементы
StringBuffer [] myTempBuffers;
myTempBuffers = new StringBuffer[3];
for (int ix = 0; ix < myTempBuffers.length; ix++)
myTempBuffers[ix] = new StringBuffer();
myTempBuffers[0].add(data);
4. 同时将多个带有修饰符的类放入一个文件中public
Java 源文件以某种方式与这些文件中包含的类相关联。这种关系的特征如下:任何 Java 类都存储在不超过一个文件中。在任何源代码文件中,您最多可以放置 1 个带有修饰符的类public
。如果源代码文件中存在带有修饰符的类public
,则文件名和类名必须严格相同(翻译注:根据情况,参见第1点)有时学生会忘记第二条规则,从而导致错误在编译阶段。第二条和第三条规则的错误消息将是相同的(这实际上使得识别此错误变得困难)。
5. 用局部变量替换类字段。
Java 允许您在方法内声明变量,其名称与类的字段相匹配。在这种情况下,局部变量将优先并代替字段使用。如果具有相同名称的变量属于不同类型,编译器将抛出错误。如果是相同的类型,就不会出现编译错误,程序运行不正确的原因也不清楚。 错误示例:public class Point3 {
int i = 0;
int j = 0;
int k = 0;
public boolean hits(Point[] p2list) {
for(int i = 0; i < p2list.length; i++) {
Point p2 = p2list[i];
if (p2.x == i && p2.y == j)
return true;
}
return false;
}
}
有多种方法可以修复此错误。最简单的是使用隐式this
:指针访问类字段this.Name_поля
。最好的方法是重命名类字段或局部变量,这样就不会发生替换。(大约翻译:第二种方法不是我们的方法。而且,它并不能保证我有一天不会意外地替换变量的字段。当我根本看不到什么字段时,继承会产生更大的困难该类有) 更正的示例:
// One way to fix the problem
int i = 0;
int j = 0;
int k = 0;
public boolean hits(Point[] p2list) {
for(int i = 0; i < p2list.length; i++) {
Point p2 = p2list[i];
if (p2.x == this.i && p2.y == this.j)
return true;
}
return false;
}
// *****************************
// Лучший способ
int x = 0;
int y = 0;
int z = 0;
public boolean hits(Point[] p2list) {
for(int i = 0; i < p2list.length; i++) {
Point p2 = p2list[i];
if (p2.x == x && p2.y == y)
return true;
}
return false;
}
发生此错误的另一个可能的地方是将方法参数的名称设置为与类字段的名称相同。这在构造函数中看起来不错,但不适合普通方法。
约。翻译 有点混乱,但这就是要点
也就是说,构造函数中的一切看起来都很漂亮,但这不应该用于普通方法。 |
6.忘记调用父类(超类)构造函数
当一个类扩展另一个类时,每个子类构造函数必须调用某个超类构造函数。这通常是通过调用超类构造函数并将方法super(x)
放在构造函数第一行来实现的。如果构造函数的第一行没有调用super(x)
,编译器本身会插入此调用,但不带参数:super()
。(大约译:x...se,但我不知道)有时学生会忘记这个要求。通常这不是问题:编译器插入对超类构造函数的调用,一切正常。但是,如果超类没有默认构造函数,编译器将抛出错误。在下面的示例中,所有超类构造函数java.io.File
都有 1 或 2 个参数: 错误示例:
public class JavaClassFile extends File {
String classname;
public JavaClassFile(String cl) {
classname = cl;
}
}
问题的解决方案是插入对正确的超类构造函数的显式调用: 更正的示例:
public class JavaClassFile extends File {
String classname;
public JavaClassFile(String cl) {
super(cl + ".class");
classname = cl;
}
}
当超类有默认构造函数,但它没有完全初始化对象时,会发生更令人不快的情况。在这种情况下,代码可以编译,但程序的输出可能不正确或者可能出现异常。
7. 错误捕获异常
Java的异常处理系统相当强大,但对于初学者来说很难理解。精通 C++ 或 Ada 的学生通常不会遇到与 C 和 Fortran 程序员相同的困难。下面的示例显示了一些常见错误。在此示例中,异常未命名。编译器会在编译阶段指出这个错误,因此很容易自行修复。 错误示例:try {
stream1 = new FileInputStream("data.txt");
} catch (IOException) {
message("Could not open data.txt");
}
更正示例:
try {
stream1 = new FileInputStream("data.txt");
} catch (IOException ie) {
message("Could not open data.txt: " + ie);
}
块的顺序catch
决定了捕获异常的顺序。必须考虑到每个这样的块将捕获指定类或其任何子类的所有异常。如果不考虑这一点,最终可能会出现无法访问的 catch 块,编译器会指出这一点。下面的示例SocketException
是 的子类IOException
。 错误示例:
try {
serviceSocket.setSoTimeout(1000);
newsock = serviceSocket.accept();
} catch (IOException ie) {
message("Error accepting connection.");
} catch (SocketException se) {
message("Error setting time-out.");
}
更正示例:
try {
serviceSocket.setSoTimeout(1000);
newsock = serviceSocket.accept();
} catch (SocketException se) {
message("Error setting time-out.");
} catch (IOException ie) {
message("Error accepting connection.");
}
如果代码中可能发生未被任何块捕获的异常try-catch
,则应在方法头中声明此异常。(这对于异常 - 类的子类来说RuntimeException
不是必需的)。学生有时会忘记调用方法可能会引发异常。解决此问题的最简单方法是将方法调用放在块中try-catch
。 错误示例:
public void waitFor(int sec) {
Thread.sleep(sec * 1000);
}
更正示例:
public void waitFor(int sec) throws InterruptedException {
Thread.sleep(sec * 1000);
}
8. 访问方法有类型void
这是一个非常简单的错误。学生创建一个方法来访问变量,但指定该方法不返回任何内容(void
在方法头中放置修饰符)。要修复此错误,您必须指定正确的返回类型。 错误示例:
public class Line {
private Point start, end;
public void getStart() {
return start;
}
}
更正示例:
public class Line {
private Point start, end;
public Point getStart() {
return start;
}
}
指定错误的返回类型会产生一整类错误。通常,编译器会识别这些错误并报告它们,以便学生可以自行更正它们。作者:A. Grasoff™ 阅读续篇 源 代码链接:Java 初学者的错误
GO TO FULL VERSION