JavaRush /Java 博客 /Random-ZH /初学java程序员常犯的错误。第2部分
articles
第 15 级

初学java程序员常犯的错误。第2部分

已在 Random-ZH 群组中发布
初学java程序员常犯的错误。第1部分

9. 从main()方法中调用非静态类方法

任何 Java 程序的入口点都应该是静态方法main
初学java程序员常犯的错误。 第 2 - 1 部分
public static void main(String[] args) {
  ...
}
由于此方法是静态的,因此您不能从中调用非静态类方法。学生经常忘记这一点,并尝试在不创建类实例的情况下调用方法。这个错误通常发生在训练之初,当学生编写小程序时。 错误示例:
public class DivTest {
    boolean divisible(int x, int y) {
        return (x % y == 0);
    }

    public static void main(String[] args) {
        int v1 = 14;
        int v2 = 9;

        // на следующие строки компилятор выдаст ошибку
        if (divisible(v1, v2)) {
            System.out.println(v1 + " is a multiple of " + v2);
        } else {
            System.out.println(v2 + " does not divide " + v1);
        }
    }
}
有两种方法可以修复错误:将所需的方法设置为静态或创建类的实例。要选择正确的方法,请询问自己该方法是否使用字段或其他类方法。如果是,那么您应该创建该类的实例并调用它的方法,否则您应该将该方法设为静态。 更正示例1:
public class DivTest {
    int modulus;

    public DivTest(int m) {
      modulus = m;
    }

    boolean divisible(int x) {
        return (x % modulus == 0);
    }

    public static void main(String[] args) {
        int v1 = 14;
        int v2 = 9;

        DivTest tester = new DivTest(v2);

        if (tester.divisible(v1) {
            System.out.println(v1 + " is a multiple of " + v2);
        } else {
            System.out.println(v2 + " does not divide " + v1);
        }
    }
}
更正示例2:
public class DivTest {
    static boolean divisible(int x, int y) {
        return (x % y == 0);
    }

    public static void main(String[] args) {
        int v1 = 14;
        int v2 = 9;

        if (divisible(v1, v2)) {
            System.out.println(v1 + " is a multiple of " + v2);
        } else {
            System.out.println(v2 + " does not divide " + v1);
        }
    }
}

10.使用String类对象作为方法参数。

在Java中,类java.lang.String存储字符串数据。然而,Java 中的字符串
  1. 具有永久性(即它们不能改变),
  2. 是对象。
因此,它们不能仅仅被视为字符缓冲区;它们是不可变的对象。有时,学生传递字符串时会错误地期望字符串对象将作为字符数组通过引用传递(如在 C 或 C++ 中)。编译器通常不认为这是一个错误。 错误的例子。
public static void main(String args[]) {
   String test1 = "Today is ";
   appendTodaysDate(test1);
   System.out.println(test1);
}

/* прим. редактора: закомментированный метод должен иметь модификатор
    static (здесь автором допущена ошибка №9)
public void appendTodaysDate(String line) {
    line = line + (new Date()).toString();
}
*/

public static void appendTodaysDate(String line) {
    line = line + (new Date()).toString();
}
在上面的示例中,学生想要通过为方法中的test1参数分配新值来更改局部变量的值。这自然是行不通的。含义会改变,但含义将保持不变。发生此错误是由于以下误解:(1) java 对象始终通过引用传递,(2) java 中的字符串是不可变的。您需要了解字符串对象永远不会更改其值,并且对字符串的所有操作都会创建一个新对象。要修复上面示例中的错误,您需要从该方法返回一个字符串,或者将一个对象作为参数传递给该方法而不是. 更正示例1:lineappendTodaysDatelinetest1StringBufferString
public static void main(String args[]) {
   String test1 = "Today is ";
   test1 = appendTodaysDate(test1);
   System.out.println(test1);
}

public static String appendTodaysDate(String line) {
    return (line + (new Date()).toString());
}
更正示例2:
public static void main(String args[]) {
   StringBuffer test1 = new StringBuffer("Today is ");
   appendTodaysDate(test1);
   System.out.println(test1.toString());
}

public static void appendTodaysDate(StringBuffer line) {
    line.append((new Date()).toString());
}

约。翻译
事实上,要理解错误是什么并不容易。由于对象是通过引用传递的,这意味着line它引用与 相同的位置test1。这意味着通过创建一个新的line,我们就创建了一个新的。在错误的示例中,一切看起来都好像是按值test1传输,而不是按引用传输。String

11. 将构造函数声明为方法

Java 中的对象构造函数在外观上与常规方法类似。唯一的区别是构造函数没有指定返回类型,并且名称与类名相同。不幸的是,Java 允许方法名与类名相同。在下面的示例中,学生希望Vector list在创建班级时初始化班级字段。这不会发生,因为方法'IntList'不是构造函数。 错误的例子。
public class IntList {
    Vector list;

    // Выглядит How конструктор, но на самом деле это метод
    public void IntList() {
        list = new Vector();
    }

    public append(int n) {
        list.addElement(new Integer(n));
    }
}
NullPointerException第一次访问该字段时, 代码将引发异常list。该错误很容易修复:您只需从方法标头中删除返回值即可。 更正示例:
public class IntList {
    Vector list;

    // Это конструктор
    public IntList() {
        list = new Vector();
    }

    public append(int n) {
        list.addElement(new Integer(n));
    }
}

12. 忘记将对象转换为所需类型

与所有其他面向对象的语言一样,在 Java 中,您可以将对象称为其超类。这称为'upcasting',它在 Java 中自动完成。但是,如果将变量、类字段或方法返回值声明为超类,则子类的字段和方法将不可见。将超类引用为子类被调用'downcasting',需要自己注册(即将对象带到想要的子类中)。学生经常忘记对对象进行子类化。当使用包(即集合框架)中的对象数组集合时,最常发生这种情况。下面的示例将一个对象放入数组中,然后将其从数组中删除以将其与另一个字符串进行比较。编译器将检测到错误,并且在显式指定类型转换之前不会编译代码。 错误的例子。java.utilString
Object arr[] = new Object[10];
arr[0] = "m";
arr[1] = new Character('m');

String arg = args[0];
if (arr[0].compareTo(arg) < 0) {
    System.out.println(arg + " comes before " + arr[0]);
}
对于某些人来说,类型转换的含义很困难。动态方法尤其经常引起困难。在上面的示例中,如果使用方法equals而不是compareTo,编译器将不会抛出错误,并且代码将正常工作,因为equals类的方法将被调用String。您需要了解动态链接与downcasting. 更正示例:
Object arr[] = new Object[10];
arr[0] = "m";
arr[1] = new Character('m');

String arg = args[0];
if ( ((String) arr[0]).compareTo(arg) < 0) {
    System.out.println(arg + " comes before " + arr[0]);
}

13.使用接口。

对于许多学生来说,类和接口之间的区别并不完全清楚。因此,一些学生尝试实现接口,例如Observer使用Runnableextends关键字而不是implements。要纠正错误,您只需将关键字更正为正确的关键字即可。 错误示例:
public class SharkSim extends Runnable {
    float length;
    ...
}
更正示例:
public class SharkSim implements Runnable {
    float length;
    ...
}
相关错误:扩展实现块 的顺序不正确。根据 Java 规范,类扩展声明必须位于接口实现声明之前。另外,对于接口, implements关键字只需编写一次;多个接口之间用逗号分隔。 还有一些错误的例子:
// Неправильный порядок
public class SharkSim implements Swimmer extends Animal {
    float length;
    ...
}

// ключевое слово implements встречается несколько раз
public class DiverSim implements Swimmer implements Runnable {
    int airLeft;
    ...
}
更正的例子:
// Правильный порядок
public class SharkSim extends Animal implements Swimmer {
    float length;
    ...
}

// Несколько интерфейсов разделяются запятыми
public class DiverSim implements Swimmer, Runnable {
    int airLeft;
    ...
}

14.忘记使用超类方法的返回值

Java 允许您使用关键字关键字从子类调用类似的超类方法。有时学生必须调用超类方法,但常常忘记使用返回值。这种情况在那些尚未理解方法及其返回值的学生中尤其常见。在下面的示例中,学生想要toString()将超类方法的结果插入到子类方法的结果中toString()。但是,它不使用超类方法的返回值。 错误示例:
public class GraphicalRectangle extends Rectangle {
      Color fillColor;
      boolean beveled;
      ...
      public String toString() {
          super();
          return("color=" + fillColor + ", beveled=" + beveled);
      }
}
要纠正错误,通常只需将返回值分配给局部变量,然后在计算子类方法的结果时使用该变量即可。 更正示例:
public class GraphicalRectangle extends Rectangle {
      Color fillColor;
      boolean beveled;
      ...
      public String toString() {
          String rectStr = super();
          return(rectStr + " - " +
         "color=" + fillColor + ", beveled=" + beveled);
      }
}

15.忘记添加AWT组件

AWT 使用简单的 GUI 设计模型:每个界面组件必须首先使用其自己的构造函数创建,然后使用add()父组件方法放置到应用程序窗口中。因此,AWT 上的接口具有层次结构。学生有时会忘记这两个步骤。他们创建了一个组件,但忘记将其放置在放大窗口中。这不会在编译阶段导致错误;该组件根本不会出现在应用程序窗口中。 错误的例子。
public class TestFrame extends Frame implements ActionListener {
    public Button exit;

    public TestFrame() {
        super("Test Frame");
        exit = new Button("Quit");
    }
}
要修复此错误,您只需将组件添加到其父组件即可。下面的示例展示了如何执行此操作。应该注意的是,忘记将组件添加到应用程序窗口的学生通常也会忘记为该组件分配事件侦听器。 更正示例:
public class TestFrame extends Frame implements ActionListener {
    public Button exit;

    public TestFrame() {
        super("Test Frame");

        exit = new Button("Quit");

        Panel controlPanel = new Panel();
        controlPanel.add(exit);

        add("Center", controlPanel);

        exit.addActionListener(this);
    }

    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }
}

17.忘记启动直播

Java 中的多线程是使用java.lang.Thread. 线程的生命周期由4个阶段组成:初始化、启动、阻塞和停止。新创建的线程处于初始化状态。要将其置于运行状态,您需要调用其start(). 有时学生创建线程但忘记启动它们。通常,当学生对并行编程和多线程知识不足时,就会发生该错误。(大约翻译:我没有看到连接)要修复错误,您只需启动线程即可。在下面的例子中,一个学生想要使用界面创建图片的动画Runnable,但是他忘记启动线程。 错误的例子
public class AnimCanvas extends Canvas implements Runnable {
        protected Thread myThread;
        public AnimCanvas() {
                myThread = new Thread(this);
        }

        // метод run() не будет вызван,
        // потому что поток не запущен.
        public void run() {
                for(int n = 0; n < 10000; n++) {
                   try {
                     Thread.sleep(100);
                   } catch (InterruptedException e) { }

                   animateStep(n);
                }
        }
        ...
}
更正示例:
public class AnimCanvas extends Canvas implements Runnable {
        static final int LIMIT = 10000;
        protected Thread myThread;

        public AnimCanvas() {
                myThread = new Thread(this);
                myThread.start();
        }

        public void run() {
                for(int n = 0; n < LIMIT; n++) {
                        try {
                          Thread.sleep(100);
                        } catch (InterruptedException e) { }

                        animateStep(n);
                }
        }
        ...
}
线程的生命周期以及线程与实现接口的类之间的关系Runnable是Java编程中非常重要的一部分,关注这一点也未尝不可。

18.使用java.io.DataInputStream类禁止的readLine()方法

readLine()在 Java 1.0 版本中,您必须使用类方法来读取文本字符串java.io.DataInputStream。Java 1.1 版本添加了一整套 I/O 类来提供文本的 I/O 操作:ReaderWriter。因此,从1.1版本开始,要读取一行文本,必须使用readLine()类方法java.io.BufferedReader。学生可能没有意识到这种变化,尤其是如果他们是从旧书本上学的。(大约翻译:实际上不再相关。现在不太可能有人会从 10 年前的书籍中学习)。旧方法readLine()仍然保留在 JDK 中,但被声明为非法,这常常让学生感到困惑。您需要了解的是,使用readLine()类方法java.io.DataInputStream并没有错,它只是过时了。您必须使用该类BufferedReader错误示例:
public class LineReader {
    private DataInputStream dis;

    public LineReader(InputStream is) {
        dis = new DataInputStream(is);
    }

    public String getLine() {
        String ret = null;

        try {
          ret = dis.readLine();  // Неправильно! Запрещено.
        } catch (IOException ie) { }

        return ret;
    }
}
更正示例:
public class LineReader {
    private BufferedReader br;

    public LineReader(InputStream is) {
        br = new BufferedReader(new InputStreamReader(is));
    }

    public String getLine() {
        String ret = null;

        try {
          ret = br.readLine();
        } catch (IOException ie) { }

        return ret;
    }
}
1.0以后的版本还有其他禁止的方法,但这一种是最常见的。

19. 使用 double 作为浮点数

与大多数其他语言一样,Java 支持浮点数(小数)运算。Java 有 2 种浮点数的基本类型:double根据 IEEE 标准的 64 位精度的数字,以及float根据 IEEE 标准的 32 位精度的数字。困难在于,当使用 1.75、12.9e17 或 -0.00003 等十进制数时,编译器会将它们分配给 type double。Java 不会在可能发生精度损失的操作中执行类型转换。这种类型转换必须由程序员完成。例如,Java 不允许您在没有类型转换的情况下将类型值分配给类型int变量byte,如下例所示。
byte byteValue1 = 17; /* неправильно! */
byte byteValue2 = (byte)19; /* правильно */
由于小数由 类型 表示double,并且double对类型变量的赋值float可能会导致精度损失,因此编译器将抱怨任何将小数用作 的尝试float。因此,使用下面的分配将阻止该类编译。
float realValue1 = -1.7;          /* неправильно! */
float realValue2 = (float)(-1.9); /* правильно */
这个赋值在 C 或 C++ 中可以工作,但在 Java 中则要严格得多。有 3 种方法可以消除此错误。您可以使用 typedouble来代替float. 这是最简单的解决方案。事实上,使用 32 位算术而不是 64 位没有什么意义;速度上的差异仍然被 JVM 吞噬(此外,在现代处理器中,所有小数都转换为 80 位的格式)任何操作之前处理器寄存器)。使用它们的唯一优点float是它们占用更少的内存,这在处理大量小数变量时非常有用。您可以使用数字类型修饰符来告诉编译器如何存储数字。类型的修饰符float - 'f'。因此,编译器会将类型 1.75 分配给double、 和1.75f - float。例如:
float realValue1 = 1.7;    /* неправильно! */
float realValue2 = 1.9f;   /* правильно */
您可以使用显式类型转换。double这是最不优雅的方式,但在将类型变量转换为 type时很有用float。例子:
float realValue1 = 1.7f;
double realValue2 = 1.9;
realValue1 = (float)realValue2;
您可以在此处和此处阅读有关浮点数的更多信息。

——译者评——
就是这样。
例10中,确实出现了错误9,我立刻注意到了,但忘记写注释了。但没有更正它,以免与原始来源不存在差异。

作者:A.Grasoff™ 来源链接:Java 初学者的错误
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION