JavaRush /مدونة جافا /Random-AR /أخطاء مبرمجي جافا المبتدئين. الجزء 2
articles
مستوى

أخطاء مبرمجي جافا المبتدئين. الجزء 2

نشرت في المجموعة
أخطاء مبرمجي جافا المبتدئين. الجزء 1

9. استدعاء أساليب الفئة غير الثابتة من الطريقة الرئيسية ().

يجب أن تكون نقطة الدخول لأي برنامج جافا طريقة ثابتة 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);
        }
    }
}
هناك طريقتان لإصلاح الخطأ: جعل الطريقة المطلوبة ثابتة أو إنشاء مثيل للفئة. لاختيار الطريقة الصحيحة، اسأل نفسك ما إذا كانت الطريقة تستخدم حقلاً أو أساليب فئة أخرى. إذا كانت الإجابة بنعم، فيجب عليك إنشاء مثيل للفئة واستدعاء طريقة عليها، وإلا يجب عليك جعل الطريقة ثابتة. المثال المصحح 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. استخدام كائنات فئة السلسلة كمعلمات الطريقة.

في Java، يقوم الفصل java.lang.Stringبتخزين بيانات السلسلة. ومع ذلك، سلاسل في جافا
  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. ومع ذلك، إذا تم الإعلان عن قيمة إرجاع متغير أو حقل فئة أو طريقة كفئة فائقة، فستكون حقول وأساليب الفئة الفرعية غير مرئية. بالإشارة إلى فئة فرعية تسمى فئة فرعية 'downcasting'، تحتاج إلى تسجيلها بنفسك (أي إحضار الكائن إلى الفئة الفرعية المطلوبة). غالبًا ما ينسى الطلاب تصنيف كائن ما. يحدث هذا غالبًا عند استخدام صفائف الكائنات والمجموعات من الحزمة java.util(أي إطار عمل المجموعة ). يضع المثال أدناه 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، يجب أن تأتي إعلانات امتداد الفئة قبل إعلانات تنفيذ الواجهة. أيضًا، بالنسبة للواجهات، يجب كتابة الكلمة الأساسية للتنفيذ مرة واحدة فقط، ويتم فصل الواجهات المتعددة بفواصل. بعض الأمثلة الخاطئة أكثر:
// Неправильный порядок
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 نموذجًا بسيطًا لتصميم واجهة المستخدم الرسومية: يجب إنشاء كل مكون واجهة أولاً باستخدام المُنشئ الخاص به، ثم وضعه في نافذة التطبيق باستخدام طريقة 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. استخدام طريقة readLine () المحظورة لفئة java.io.DataInputStream

readLine()في Java الإصدار 1.0، كان عليك استخدام طريقة الفصل لقراءة سلسلة نصية java.io.DataInputStream. أضاف الإصدار 1.1 من Java مجموعة كاملة من فئات الإدخال/الإخراج لتوفير عمليات الإدخال/الإخراج للنص: 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. لا تقوم 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 طرق للتخلص من هذا الخطأ. يمكنك استخدام الكتابة 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