JavaRush /בלוג Java /Random-HE /טעויות של מתכנתי ג'אווה מתחילים. חלק 2
articles
רָמָה

טעויות של מתכנתי ג'אווה מתחילים. חלק 2

פורסם בקבוצה
טעויות של מתכנתי ג'אווה מתחילים. חלק 1

9. קריאה לשיטות מחלקות לא סטטיות מהשיטה main()

נקודת הכניסה של כל תוכנית Java צריכה להיות שיטה סטטית main:
טעויות של מתכנתי ג'אווה מתחילים.  חלק 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);
        }
    }
}
ישנן 2 דרכים לתקן את השגיאה: להפוך את השיטה הרצויה לסטטית או ליצור מופע של המחלקה. כדי לבחור את השיטה הנכונה, שאל את עצמך האם השיטה משתמשת בשדה או בשיטות מחלקות אחרות. אם כן, אז אתה צריך ליצור מופע של המחלקה ולקרוא על זה מתודה, אחרת אתה צריך להפוך את השיטה סטטית. דוגמה 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על ידי הקצאת ערך חדש לפרמטר 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());
}

משוער. תִרגוּם
למעשה, זה לא כל כך קל להבין מהי השגיאה. מכיוון שאובייקטים מועברים באמצעות הפניה, זה אומר 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. עם זאת, אם משתנה, שדה מחלקה או ערך החזרת מתודה מוכרזים כעל מחלקה, השדות והשיטות של תת המחלקה יהיו בלתי נראים. התייחסות ל- superclass בתור תת-מחלקה נקראת 'downcasting', אתה צריך לרשום אותה בעצמך (כלומר, להביא את האובייקט לתת-מחלקה הרצויה). תלמידים שוכחים לעתים קרובות מחלוקת משנה של אובייקט. זה קורה לרוב בעת שימוש במערכים של אובייקטים ואוספים מחבילה java.util(כלומר, מסגרת האוסף ). הדוגמה למטה Stringמכניסה אובייקט למערך ולאחר מכן מסירה אותו מהמערך כדי להשוות אותו למחרוזת אחרת. המהדר יזהה שגיאה ולא יקמפל את הקוד עד שיצוין במפורש cast type. דוגמה לא נכונה.
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באמצעות מילת המפתח extends במקום implements . כדי לתקן את השגיאה, אתה רק צריך לתקן את מילת המפתח למילת המפתח הנכונה. דוגמה שגויה:
public class SharkSim extends Runnable {
    float length;
    ...
}
דוגמה מתוקנת:
public class SharkSim implements Runnable {
    float length;
    ...
}
שגיאה קשורה: סדר שגוי של הרחבת ויישום בלוקים . על פי מפרט Java, הצהרות הרחבות מחלקות חייבות לבוא לפני הצהרות הטמעת ממשק. כמו כן, עבור ממשקים, מילת המפתח מיישמת צריכה להיכתב פעם אחת בלבד; ממשקים מרובים מופרדים בפסיקים. עוד כמה דוגמאות שגויות:
// Неправильный порядок
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. שכחת להשתמש בערך ההחזר של שיטת superclass

Java מאפשרת לך לקרוא לשיטת superclass דומה מתת-מחלקה באמצעות מילת המפתח. לפעמים התלמידים צריכים לקרוא לשיטות סופר-class, אבל לעתים קרובות שוכחים להשתמש בערך ההחזר. זה קורה לעתים קרובות במיוחד בקרב אותם תלמידים שעדיין לא הבינו את השיטות ואת ערכי ההחזר שלהן. בדוגמה שלהלן, תלמיד רוצה להכניס את התוצאה של toString()שיטת superclass לתוצאה של toString()שיטת subclass. עם זאת, הוא אינו משתמש בערך ההחזר של שיטת superclass. דוגמה שגויה:
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(). לפעמים תלמידים יוצרים שרשורים אבל שוכחים להתחיל אותם. בדרך כלל השגיאה מתרחשת כאשר לתלמיד אין ידע מספיק על תכנות מקביל ו-multithreading. (תרגום בקירוב: אני לא רואה את הקשר) כדי לתקן את השגיאה, אתה רק צריך להתחיל את השרשור. בדוגמה למטה, תלמיד רוצה ליצור אנימציה של תמונה באמצעות הממשק 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()בגרסה 1.0 של Java, היית צריך להשתמש בשיטת מחלקה כדי לקרוא מחרוזת טקסט java.io.DataInputStream. Java גרסה 1.1 הוסיפה סט שלם של מחלקות קלט/פלט כדי לספק פעולות קלט/פלט עבור טקסט: ה- Readerו Writer. לפיכך, מגרסה 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. שימוש כפול כצף

כמו רוב השפות האחרות, Java תומכת בפעולות על מספרי נקודה צפה (מספרים חלקיים). ל-Java יש שני סוגים פרימיטיביים למספרי נקודה צפה: doubleדיוק IEEE 64 סיביות ודיוק floatIEEE 32 סיביות. הקושי הוא כאשר משתמשים במספרים עשרוניים כגון 1.75, 12.9e17 או -0.00003 - המהדר מקצה אותם לסוג double. ג'אווה אינה מבצעת יציקות סוגים בפעולות שבהן עלול להתרחש אובדן דיוק. ליהוק מסוג זה חייב להתבצע על ידי המתכנת. לדוגמה, ג'אווה לא תאפשר לך להקצות ערך טיפוס למשתנה טיפוס intללא bytecast type, כפי שמוצג בדוגמה למטה.
byte byteValue1 = 17; /* неправильно! */
byte byteValue2 = (byte)19; /* правильно */
מכיוון שמספרים שברים מיוצגים על ידי סוג double, והקצאה doubleלמשתנה של סוג floatעלולה להוביל לאובדן דיוק, המהדר יתלונן על כל ניסיון להשתמש במספרים שברים כ float. אז שימוש במטלות למטה ימנע מהכיתה להרכיב.
float realValue1 = -1.7;          /* неправильно! */
float realValue2 = (float)(-1.9); /* правильно */
המטלה הזו תעבוד ב-C או C++, אבל ב-Java זה הרבה יותר מחמיר. ישנן 3 דרכים להיפטר מהשגיאה הזו. אתה יכול להשתמש בסוג doubleבמקום float. זהו הפתרון הפשוט ביותר. למעשה, אין טעם להשתמש בחשבון של 32 סיביות במקום 64 סיביות; ההבדל במהירות עדיין נאכל על ידי ה-JVM (חוץ מזה, במעבדים מודרניים כל המספרים השברים מומרים לפורמט של מעבד 80 סיביות להירשם לפני כל פעולה). היתרון היחיד בשימוש בהם floatהוא שהם תופסים פחות זיכרון, וזה שימושי כשעובדים עם מספר רב של משתנים שברים. אתה יכול להשתמש במשנה סוג מספר כדי לומר למהדר כיצד לאחסן את המספר. משנה לסוג float - 'f'. לפיכך, המהדר יקצה את הסוג 1.75 ל- double, ו- 1.75f - float. לדוגמה:
float realValue1 = 1.7;    /* неправильно! */
float realValue2 = 1.9f;   /* правильно */
אתה יכול להשתמש בליהוק סוג מפורש. זוהי הדרך הפחות אלגנטית, אך היא שימושית בעת המרת משתנה סוג doubleלסוג float. דוגמא:
float realValue1 = 1.7f;
double realValue2 = 1.9;
realValue1 = (float)realValue2;
תוכל לקרוא עוד על מספרי נקודה צפה כאן וכאן.

-- הערת מתרגם --
זהו זה.
בדוגמה 10 למעשה נפלה שגיאה 9. שמתי לב לזה מיד, אבל שכחתי לכתוב הערה. אך לא תיקן אותו כדי שלא יהיו סתירות עם המקור המקורי.

מחבר: A.Grasoff™ קישור למקור: טעויות של מתכנתי ג'אווה מתחילים
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION