ข้อผิดพลาดของโปรแกรมเมอร์ Java มือใหม่ ส่วนที่ 1
ผู้แต่ง: A.Grasoff™ ลิงก์ไปยังแหล่งที่มา: ข้อผิดพลาดของโปรแกรมเมอร์ Java มือใหม่
9. การเรียกเมธอดคลาสแบบไม่คงที่จากเมธอด main()
จุดเริ่มต้นของโปรแกรม Java ใด ๆ ควรเป็นวิธีการแบบคงที่main
:
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);
}
}
}
มี 2 วิธีในการแก้ไขข้อผิดพลาด: ทำให้วิธีการที่ต้องการคงที่หรือสร้างอินสแตนซ์ของคลาส ในการเลือกวิธีการที่ถูกต้อง ให้ถามตัวเองว่าวิธีการนั้นใช้ field หรือวิธีการเรียนอื่น ๆ ถ้าใช่ คุณควรสร้างอินสแตนซ์ของคลาสและเรียกใช้เมธอดในคลาสนั้น ไม่เช่นนั้นคุณควรทำให้เมธอดเป็นแบบคงที่ ตัวอย่างที่แก้ไข 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
- มีความคงอยู่ (คือ เปลี่ยนแปลงไม่ได้)
- เป็นวัตถุ
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
การกำหนดค่าใหม่ให้กับพารามิเตอร์line
ในเมธอด appendTodaysDate
โดยธรรมชาติแล้วสิ่งนี้จะไม่ทำงาน ความหมายline
จะเปลี่ยนไปแต่ความหมายtest1
จะคงเดิม ข้อผิดพลาดนี้เกิดขึ้นเนื่องจากความเข้าใจผิดว่า (1) อ็อบเจ็กต์ Java ถูกส่งผ่านโดยการอ้างอิงเสมอ และ (2) สตริงใน Java นั้นไม่เปลี่ยนรูป คุณต้องเข้าใจว่าออบเจ็กต์สตริงไม่เคยเปลี่ยนค่า และการดำเนินการทั้งหมดบนสตริงจะสร้างออบเจ็กต์ใหม่ ในการแก้ไขข้อผิดพลาดในตัวอย่างข้างต้น คุณจะต้องส่งคืนสตริงจากเมธอด หรือส่งออบเจ็กต์StringBuffer
เป็นพารามิเตอร์ไปยังเมธอดString
แทน ตัวอย่างที่แก้ไข 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());
}
ตัวอย่างที่แก้ไข 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());
}
ประมาณ การแปล |
11. การประกาศตัวสร้างเป็นวิธีการ
ตัวสร้างวัตถุใน Java มีลักษณะคล้ายกับวิธีปกติ ข้อแตกต่างเพียงอย่างเดียวคือ Constructor ไม่ได้ระบุประเภทของค่าที่ส่งคืน และชื่อจะเหมือนกับชื่อคลาส น่าเสียดายที่ 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.util
(หมายถึงCollection Framework ) ตัวอย่างด้านล่างString
ใส่วัตถุลงในอาร์เรย์แล้วลบออกจากอาร์เรย์เพื่อเปรียบเทียบกับสตริงอื่น คอมไพเลอร์จะตรวจพบข้อผิดพลาดและจะไม่คอมไพล์โค้ดจนกว่าจะระบุประเภทการส่งอย่างชัดเจน ตัวอย่างที่ไม่ถูกต้อง
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
หรือRunnable
ใช้ คีย์เวิร์ด ขยายแทนการใช้ เพื่อแก้ไขข้อผิดพลาด คุณเพียงแค่ต้องแก้ไขคำหลักให้ถูกต้อง ตัวอย่างที่ไม่ถูกต้อง:
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 อนุญาตให้คุณเรียกใช้เมธอดซูเปอร์คลาสที่คล้ายกันจากคลาสย่อยโดยใช้คีย์เวิร์ดคีย์เวิร์ด บางครั้งนักเรียนต้องเรียกใช้เมธอด superclass แต่มักจะลืมใช้ค่าที่ส่งคืน สิ่งนี้เกิดขึ้นบ่อยครั้งโดยเฉพาะในหมู่นักเรียนที่ยังไม่เข้าใจวิธีการและค่าที่ส่งคืน ในตัวอย่างด้านล่าง นักเรียนต้องการแทรกผลลัพธ์ของ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 จึงได้รับโครงสร้างแบบลำดับชั้น บางครั้งนักเรียนก็ลืม 2 ขั้นตอนนี้ไป พวกเขาสร้างส่วนประกอบแต่ลืมวางไว้ในหน้าต่างขยาย สิ่งนี้จะไม่ทำให้เกิดข้อผิดพลาดในขั้นตอนการคอมไพล์ ส่วนประกอบจะไม่ปรากฏในหน้าต่างแอปพลิเคชัน ตัวอย่างที่ไม่ถูกต้อง
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. การใช้เมธอดreadLine() ที่ต้องห้ามของ คลาสjava.io.DataInputStream
readLine()
ใน Java เวอร์ชัน 1.0 คุณต้องใช้วิธี การเรียนjava.io.DataInputStream
เพื่ออ่านสตริงข้อความ Java เวอร์ชัน 1.1 ได้เพิ่มคลาส I/O ทั้งชุดเพื่อให้การดำเนินการ I/O สำหรับข้อความ: the Reader
และWriter
. ดังนั้นตั้งแต่เวอร์ชัน 1.1 ถึง อ่านบรรทัดข้อความ คุณต้องใช้ เมธอด readLine()
class java.io.BufferedReader
นักเรียนอาจไม่ตระหนักถึงการเปลี่ยนแปลงนี้ โดยเฉพาะอย่างยิ่งหากพวกเขาสอนจากหนังสือเก่าๆ (คำแปลโดยประมาณ: จริงๆ แล้วไม่มีความเกี่ยวข้องอีกต่อไป ไม่น่าจะมีใครเรียนจากหนังสือที่มีอายุ 10 ขวบแล้ว) วิธีการแบบเก่าreadLine()
ยังคงอยู่ใน JDK แต่ได้รับการประกาศว่าผิดกฎหมาย ซึ่งมักทำให้นักเรียนสับสน สิ่งที่คุณต้องเข้าใจก็คือการใช้readLine()
class method 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 เป็น float
เช่นเดียวกับภาษาอื่นๆ ส่วนใหญ่ Java รองรับการดำเนินการกับตัวเลขทศนิยม (ตัวเลขเศษส่วน) Java มีสองประเภทดั้งเดิมสำหรับตัวเลขทศนิยม:double
ความแม่นยำ IEEE 64 บิต และfloat
ความแม่นยำ IEEE 32 บิต ความยากคือเมื่อใช้ตัวเลขทศนิยมเช่น 1.75, 12.9e17 หรือ -0.00003 - คอมไพเลอร์กำหนดให้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 วิธีในการกำจัดข้อผิดพลาดนี้ คุณสามารถใช้ type double
แทนfloat
. นี่เป็นวิธีแก้ปัญหาที่ง่ายที่สุด ในความเป็นจริง มีประโยชน์เพียงเล็กน้อยในการใช้เลขคณิตแบบ 32 บิตแทนที่จะเป็น 64 บิต JVM ยังคงกินความแตกต่างของความเร็ว (นอกจากนี้ในโปรเซสเซอร์สมัยใหม่ ตัวเลขเศษส่วนทั้งหมดจะถูกแปลงเป็นรูปแบบของโปรเซสเซอร์ 80 บิต ลงทะเบียนก่อนดำเนินการใดๆ) ข้อดีเพียงอย่างเดียวของการใช้งานfloat
คือใช้หน่วยความจำน้อยลง ซึ่งมีประโยชน์เมื่อทำงานกับตัวแปรเศษส่วนจำนวนมาก คุณสามารถใช้ตัวแก้ไขประเภทตัวเลขเพื่อบอกคอมไพเลอร์ถึงวิธีการจัดเก็บหมายเลข ตัวแก้ไขสำหรับfloat - 'f'
ประเภท ดังนั้นคอมไพลเลอร์จะ กำหนดประเภท 1.75 ให้กับdouble
และ 1.75f - float
ตัวอย่างเช่น:
float realValue1 = 1.7; /* неправильно! */
float realValue2 = 1.9f; /* правильно */
คุณสามารถใช้การหล่อประเภทที่ชัดเจนได้ นี่เป็นวิธีที่หรูหราน้อยที่สุด แต่จะมีประโยชน์เมื่อแปลงตัวแปร type double
เป็น type float
ตัวอย่าง:
float realValue1 = 1.7f;
double realValue2 = 1.9;
realValue1 = (float)realValue2;
คุณสามารถอ่านเพิ่มเติมเกี่ยวกับตัวเลขทศนิยมได้ ที่นี่ และ ที่นี่
--ความเห็นนักแปล-- |
GO TO FULL VERSION