JavaRush /Blog Java /Random-VI /Sai lầm của người mới lập trình java. Phần 2
articles
Mức độ

Sai lầm của người mới lập trình java. Phần 2

Xuất bản trong nhóm
Sai lầm của người mới lập trình java. Phần 1

9. Gọi các phương thức lớp không tĩnh từ phương thức main()

Điểm vào của bất kỳ chương trình Java nào phải là một phương thức tĩnh main:
Sai lầm của người mới lập trình java.  Phần 2 - 1
public static void main(String[] args) {
  ...
}
Vì phương thức này là tĩnh nên bạn không thể gọi các phương thức lớp không tĩnh từ nó. Học sinh thường quên điều này và cố gắng gọi các phương thức mà không tạo một thể hiện của lớp. Lỗi này thường xảy ra ngay khi bắt đầu đào tạo, khi học sinh viết các chương trình nhỏ. Ví dụ sai:
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);
        }
    }
}
Có 2 cách để sửa lỗi: đặt phương thức mong muốn thành tĩnh hoặc tạo một thể hiện của lớp. Để chọn phương thức phù hợp, hãy tự hỏi liệu phương thức đó sử dụng một trường hay các phương thức lớp khác. Nếu có, thì bạn nên tạo một thể hiện của lớp và gọi một phương thức trên đó, nếu không, bạn nên đặt phương thức đó ở dạng tĩnh. Đã sửa ví dụ 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);
        }
    }
}
Đã sửa ví dụ 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. Sử dụng các đối tượng lớp String làm tham số phương thức.

Trong Java, một lớp java.lang.Stringlưu trữ dữ liệu chuỗi. Tuy nhiên, các chuỗi trong Java
  1. có tính lâu dài (nghĩa là chúng không thể thay đổi),
  2. là những đồ vật
Vì vậy, chúng không thể được coi chỉ như một bộ đệm ký tự; chúng là những đối tượng bất biến. Đôi khi học sinh truyền chuỗi với mong đợi sai lầm rằng đối tượng chuỗi sẽ được truyền dưới dạng mảng ký tự theo tham chiếu (như trong C hoặc C++). Trình biên dịch thường không coi đây là lỗi. Ví dụ sai.
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();
}
Trong ví dụ trên, học sinh muốn thay đổi giá trị của biến cục bộ test1bằng cách gán giá trị mới cho tham số linetrong một phương thức appendTodaysDate. Đương nhiên điều này sẽ không hiệu quả. Ý nghĩa linesẽ thay đổi, nhưng ý nghĩa test1sẽ vẫn như cũ. Lỗi này xảy ra do hiểu lầm rằng (1) các đối tượng java luôn được truyền theo tham chiếu và (2) các chuỗi trong java là bất biến. Bạn cần hiểu rằng các đối tượng chuỗi không bao giờ thay đổi giá trị của chúng và mọi thao tác trên chuỗi đều tạo ra một đối tượng mới. Để sửa lỗi trong ví dụ trên, bạn cần trả về một chuỗi từ phương thức hoặc truyền một đối tượng StringBufferlàm tham số cho phương thức thay vì String. Đã sửa ví dụ 1:
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());
}
Đã sửa ví dụ 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());
}

khoảng dịch
Trên thực tế, không dễ để hiểu lỗi là gì. Vì các đối tượng được truyền bằng tham chiếu, điều đó có nghĩa là linenó đề cập đến cùng một vị trí với test1. Điều này có nghĩa là bằng cách tạo một cái mới line, chúng ta tạo một cái mới test1. Trong ví dụ sai, mọi thứ trông như thể việc chuyển tiền Stringđược thực hiện theo giá trị chứ không phải theo tham chiếu.

11. Khai báo hàm tạo như một phương thức

Các hàm tạo đối tượng trong Java có hình thức tương tự như các phương thức thông thường. Điểm khác biệt duy nhất là hàm tạo không chỉ định kiểu giá trị trả về và tên giống với tên lớp. Thật không may, Java cho phép tên phương thức giống với tên lớp. Trong ví dụ dưới đây, học sinh muốn khởi tạo trường lớp Vector listkhi tạo lớp. Điều này sẽ không xảy ra vì phương thức 'IntList'không phải là hàm tạo. Ví dụ sai.
public class IntList {
    Vector list;

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

    public append(int n) {
        list.addElement(new Integer(n));
    }
}
Mã sẽ đưa ra một ngoại lệ NullPointerExceptionkhi trường này được truy cập lần đầu tiên list. Lỗi rất dễ sửa: bạn chỉ cần xóa giá trị trả về khỏi tiêu đề phương thức. Ví dụ đã sửa:
public class IntList {
    Vector list;

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

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

12. Quên ép kiểu đối tượng theo yêu cầu

Giống như tất cả các ngôn ngữ hướng đối tượng khác, trong Java bạn có thể gọi một đối tượng là siêu lớp của nó. Cái này được gọi là 'upcasting', nó được thực hiện tự động trong Java. Tuy nhiên, nếu một biến, trường lớp hoặc giá trị trả về của phương thức được khai báo là siêu lớp thì các trường và phương thức của lớp con sẽ ẩn. Gọi superclass là subclass được gọi 'downcasting', bạn cần phải tự mình đăng ký (tức là đưa đối tượng đến subclass mong muốn). Học sinh thường quên việc phân lớp một đối tượng. Điều này thường xảy ra nhất khi sử dụng mảng Đối tượng bộ sưu tập từ một gói java.util(có nghĩa là Khung sưu tập ). Ví dụ dưới đây Stringđặt một đối tượng vào một mảng rồi xóa nó khỏi mảng để so sánh nó với một chuỗi khác. Trình biên dịch sẽ phát hiện lỗi và sẽ không biên dịch mã cho đến khi kiểu truyền được chỉ định rõ ràng. Ví dụ sai.
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]);
}
Ý nghĩa của việc truyền kiểu là khó khăn đối với một số người. Phương pháp động đặc biệt thường gây khó khăn. Trong ví dụ trên, nếu phương thức này được sử dụng equalsthay vì compareTo, trình biên dịch sẽ không đưa ra lỗi và mã sẽ hoạt động chính xác vì phương thức equalscủa lớp sẽ được gọi String. Bạn cần hiểu rằng liên kết động khác với downcasting. Ví dụ đã sửa:
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. Sử dụng giao diện.

Đối với nhiều sinh viên, sự khác biệt giữa lớp học và giao diện không hoàn toàn rõ ràng. Do đó, một số sinh viên cố gắng triển khai các giao diện như Observerhoặc Runnablesử dụng từ khóa mở rộng thay vì thực hiện . Để sửa lỗi, bạn chỉ cần sửa từ khóa cho đúng. Ví dụ sai:
public class SharkSim extends Runnable {
    float length;
    ...
}
Ví dụ đã sửa:
public class SharkSim implements Runnable {
    float length;
    ...
}
Lỗi liên quan: Thứ tự các khối mở rộngtriển khai không chính xác . Theo đặc tả Java, các khai báo mở rộng lớp phải đến trước các khai báo triển khai giao diện. Ngoài ra, đối với giao diện, từ khóa thực hiện chỉ cần được viết một lần; nhiều giao diện được phân tách bằng dấu phẩy. Một số ví dụ sai lầm hơn:
// Неправильный порядок
public class SharkSim implements Swimmer extends Animal {
    float length;
    ...
}

// ключевое слово implements встречается несколько раз
public class DiverSim implements Swimmer implements Runnable {
    int airLeft;
    ...
}
Ví dụ đã sửa:
// Правильный порядок
public class SharkSim extends Animal implements Swimmer {
    float length;
    ...
}

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

14. Quên sử dụng giá trị trả về của phương thức siêu lớp

Java cho phép bạn gọi một phương thức siêu lớp tương tự từ một lớp con bằng cách sử dụng từ khóa. Đôi khi học sinh phải gọi các phương thức của lớp cha nhưng thường quên sử dụng giá trị trả về. Điều này đặc biệt thường xảy ra với những sinh viên chưa hiểu phương pháp và giá trị trả về của chúng. Trong ví dụ dưới đây, một sinh viên muốn chèn kết quả của toString()phương thức lớp cha vào kết quả của toString()phương thức lớp con. Tuy nhiên, nó không sử dụng giá trị trả về của phương thức siêu lớp. Ví dụ sai:
public class GraphicalRectangle extends Rectangle {
      Color fillColor;
      boolean beveled;
      ...
      public String toString() {
          super();
          return("color=" + fillColor + ", beveled=" + beveled);
      }
}
Để sửa lỗi, thông thường chỉ cần gán giá trị trả về cho một biến cục bộ, sau đó sử dụng biến đó khi tính kết quả của phương thức lớp con. Ví dụ đã sửa:
public class GraphicalRectangle extends Rectangle {
      Color fillColor;
      boolean beveled;
      ...
      public String toString() {
          String rectStr = super();
          return(rectStr + " - " +
         "color=" + fillColor + ", beveled=" + beveled);
      }
}

15. Quên thêm thành phần AWT

AWT sử dụng mô hình thiết kế GUI đơn giản: trước tiên, mỗi thành phần giao diện phải được tạo bằng cách sử dụng hàm tạo riêng của nó, sau đó được đặt vào cửa sổ ứng dụng bằng add()phương thức thành phần cha. Như vậy, giao diện trên AWT nhận được cấu trúc phân cấp. Học sinh đôi khi quên mất 2 bước này. Họ tạo một thành phần nhưng lại quên đặt nó vào cửa sổ phóng to. Điều này sẽ không gây ra lỗi ở giai đoạn biên dịch; đơn giản là thành phần sẽ không xuất hiện trong cửa sổ ứng dụng. Ví dụ sai.
public class TestFrame extends Frame implements ActionListener {
    public Button exit;

    public TestFrame() {
        super("Test Frame");
        exit = new Button("Quit");
    }
}
Để khắc phục lỗi này, bạn chỉ cần thêm các thành phần vào cha mẹ của chúng. Ví dụ dưới đây cho thấy cách thực hiện việc này. Cần lưu ý rằng thường một học sinh quên thêm một thành phần vào cửa sổ ứng dụng cũng quên gán trình xử lý sự kiện cho thành phần đó. Ví dụ đã sửa:
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. Quên bắt đầu phát trực tiếp

Đa luồng trong Java được triển khai bằng cách sử dụng java.lang.Thread. Vòng đời của một thread bao gồm 4 giai đoạn: khởi tạo, bắt đầu, chặn và dừng. Chuỗi mới được tạo ở trạng thái khởi tạo. Để đưa nó vào trạng thái chạy, bạn cần gọi start(). Đôi khi học sinh tạo chủ đề nhưng lại quên khởi động chúng. Thông thường lỗi xảy ra khi học sinh chưa có đủ kiến ​​thức về lập trình song song và đa luồng. (ước chừng dịch: Tôi không thấy kết nối) Để sửa lỗi, bạn chỉ cần bắt đầu chuỗi. Trong ví dụ bên dưới, một học sinh muốn tạo một hình ảnh động bằng giao diện Runnablenhưng lại quên bắt đầu chuỗi. Ví dụ sai
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);
                }
        }
        ...
}
Ví dụ đã sửa:
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);
                }
        }
        ...
}
Vòng đời của một luồng và mối quan hệ giữa các luồng và các lớp triển khai giao diện Runnablelà một phần rất quan trọng của lập trình Java và sẽ không phải là một ý tưởng tồi nếu tập trung vào vấn đề này.

18. Sử dụng phương thức readLine() bị cấm của lớp java.io.DataInputStream

readLine()Trong phiên bản Java 1.0, bạn phải sử dụng một phương thức lớp để đọc một chuỗi văn bản java.io.DataInputStream. Phiên bản Java 1.1 đã thêm một tập hợp đầy đủ các lớp I/O để cung cấp các thao tác I/O cho văn bản: the ReaderWriter. Như vậy, từ phiên bản 1.1 để đọc một dòng văn bản, bạn phải sử dụng phương thức readLine()lớp java.io.BufferedReader. Học sinh có thể không nhận thức được sự thay đổi này, đặc biệt nếu họ được dạy từ sách cũ. (ước chừng. Bản dịch: thực sự không còn phù hợp nữa. Giờ đây khó có ai có thể học từ những cuốn sách cách đây 10 năm). Phương thức cũ readLine()vẫn còn trong JDK nhưng bị tuyên bố là bất hợp pháp, điều này thường khiến học sinh bối rối. Điều bạn cần hiểu là việc sử dụng phương thức readLine()lớp java.io.DataInputStreamkhông có gì sai, nó chỉ lỗi thời mà thôi. Bạn phải sử dụng lớp BufferedReader. Ví dụ sai:
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;
    }
}
Ví dụ đã sửa:
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;
    }
}
Có các phương pháp bị cấm khác trong các phiên bản sau 1.0, nhưng đây là phương pháp phổ biến nhất.

19. Sử dụng double làm float

Giống như hầu hết các ngôn ngữ khác, Java hỗ trợ các thao tác trên số dấu phẩy động (số phân số). Java có 2 loại nguyên thủy cho số dấu phẩy động: doubledành cho số có độ chính xác 64 bit theo tiêu chuẩn IEEE và floatdành cho số có độ chính xác 32 bit theo tiêu chuẩn IEEE. Khó khăn là khi sử dụng các số thập phân như 1,75, 12,9e17 hoặc -0,00003 - trình biên dịch gán chúng cho loại double. Java không thực hiện ép kiểu trong các hoạt động có thể xảy ra mất độ chính xác. Việc truyền kiểu này phải được thực hiện bởi người lập trình. Ví dụ: Java sẽ không cho phép bạn gán giá trị kiểu cho một intbiến kiểu bytemà không có kiểu truyền, như trong ví dụ bên dưới.
byte byteValue1 = 17; /* неправильно! */
byte byteValue2 = (byte)19; /* правильно */
Vì các số phân số được biểu thị bằng loại doublevà việc gán doublecho một biến loại floatcó thể dẫn đến mất độ chính xác, trình biên dịch sẽ phàn nàn về mọi nỗ lực sử dụng số phân số dưới dạng float. Vì vậy việc sử dụng các bài tập dưới đây sẽ ngăn cản việc biên dịch lớp.
float realValue1 = -1.7;          /* неправильно! */
float realValue2 = (float)(-1.9); /* правильно */
Bài tập này sẽ hoạt động được trong C hoặc C++, nhưng trong Java thì nó chặt chẽ hơn nhiều. Có 3 cách để thoát khỏi lỗi này. Bạn có thể sử dụng loại doublethay vì float. Đây là giải pháp đơn giản nhất. Trên thực tế, có rất ít ý nghĩa khi sử dụng số học 32 bit thay vì 64 bit; sự khác biệt về tốc độ vẫn bị JVM ăn mất (ngoài ra, trong các bộ xử lý hiện đại, tất cả các số phân số đều được chuyển đổi sang định dạng của bộ xử lý 80 bit đăng ký trước khi thực hiện bất kỳ hoạt động nào). Ưu điểm duy nhất của việc sử dụng chúng floatlà chiếm ít bộ nhớ hơn, điều này rất hữu ích khi làm việc với một số lượng lớn các biến phân số. Bạn có thể sử dụng công cụ sửa đổi kiểu số để cho trình biên dịch biết cách lưu trữ số đó. Công cụ sửa đổi cho loại float - 'f'. Do đó, trình biên dịch sẽ gán kiểu 1.75 cho double, và 1.75f - float. Ví dụ:
float realValue1 = 1.7;    /* неправильно! */
float realValue2 = 1.9f;   /* правильно */
Bạn có thể sử dụng kiểu truyền rõ ràng. Đây là cách ít tao nhã nhất, nhưng nó rất hữu ích khi chuyển đổi một biến kiểu doublethành một kiểu float. Ví dụ:
float realValue1 = 1.7f;
double realValue2 = 1.9;
realValue1 = (float)realValue2;
Bạn có thể đọc thêm về số dấu phẩy động tại đây và tại đây.

-- lời bình của người dịch --
Vậy đó.
Ở ví dụ 10, thực ra có lỗi 9. Mình nhận ra ngay nhưng lại quên ghi chú. nhưng không sửa lại để không sai lệch với nguồn gốc.

Tác giả: A.Grasoff™ Link to source: Sai lầm của người mới lập trình java
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION