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

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

نشرت في المجموعة

1. يختلف اسم الفئة عن اسم الملف الذي تم تخزينها فيه

تفترض جميع أطر عمل جافا التي استخدمتها، بما في ذلك Javasoft JDKs، أن الكود المصدري لفئة مع المعدل العام يتم تخزينه في ملف يحمل نفس الاسم تمامًا مثل اسم الفئة وامتداد .java. يمكن أن يؤدي عدم اتباع هذه الاتفاقية إلى حدوث العديد من المشكلات التي ستظهر أثناء التجميع.
أخطاء مبرمجي جافا المبتدئين.  الجزء 1 - 1
غالبًا ما ينسى الطلاب المبتدئون (المبرمجون) هذه الاتفاقية، على سبيل المثال، يقومون بتعيين اسم الملف وفقًا للمهمة: Lab6.java. مثال خاطئ: اسم الملفLab6.java
public class Airplane extends Vehicle
  Seat pilot;
  public Airplane() {
    pilot = new Seat();
  }
}
المثال المصحح: اسم الملفAirplane.java
public class Airplane extends Vehicle
  Seat pilot;
  public Airplane() {
    pilot = new Seat();
  }
}
يرجى الملاحظة:من المفترض أن يبدأ اسم الفئة بحرف كبير. قد تمثل أنظمة التشغيل الحساسة لحالة الأحرف في أسماء الملفات مشكلات إضافية، خاصة للطلاب الذين يتعلمون Java على Unix والذين اعتادوا على نظام تسمية الملفات DOS. يجب أن يتم تخزين الفصل MotorVehicleفي ملف MotorVehicle.java، ولكن ليس في ملف motorvehicle.java.

2. المقارنة باستخدام==

في Java، السلاسل هي كائنات من الفئة java.lang.String. يتحقق عامل التشغيل ==المطبق على الكائنات من مساواة الإشارات إلى الكائنات! في بعض الأحيان لا يفهم الطلاب دلالات العامل ==ويحاولون استخدامه لمقارنة السلاسل. مثال خاطئ:
// проверим, equals ли первый аргумент "-a"
if (args[0] == "-a") {
    optionsAll = true;
}
الطريقة الصحيحة لمقارنة سلسلتين للمساواة هي استخدام طريقة equals()الفصل java.lang.String. يتم إرجاعه trueإذا كانت السلاسل بنفس الطول وتحتوي على نفس الأحرف. (ملاحظة: في الواقع هذا لا يضمن المساواة. في الواقع، equalsفهو يتحقق مما إذا كانت سلسلتان متساويتان حرفًا بحرف) مثال مصحح:
//  проверим, equals ли первый аргумент "-a"
if ("-a".equals(args[0])) {
    optionsAll = true;
}
هذا الخطأ غبي، لأنه في الواقع تبين أن كود Java صحيح من الناحية النحوية، لكنه في النهاية لا يعمل كما هو متوقع. يحاول بعض الطلاب أيضًا استخدام عوامل التشغيل >والمقارنة بدلاً من <=طريقة compareTo()الفصل java.lang.String. من السهل اكتشاف هذا الخطأ لأنه يسبب أخطاء أثناء مرحلة الترجمة.

3. نسيت تهيئة الكائنات التي تمثل عناصر المصفوفة.

في Java، مصفوفة الكائنات هي في الواقع مصفوفة من مراجع الكائنات. إنشاء مصفوفة هو ببساطة إنشاء مجموعة من المراجع التي لا تشير إلى أي شيء (أي أنها خالية). لإنشاء مصفوفة "كاملة" من الكائنات، تحتاج إلى تهيئة كل عنصر من عناصر المصفوفة. كثير من الطلاب لا يفهمون هذا؛ ويعتقدون أنه من خلال إنشاء مجموعة من الكائنات، فإنهم يقومون تلقائيًا بإنشاء الكائنات بأنفسهم. (في معظم الحالات، يستعين الطلاب بهذا المفهوم من لغة C++‎، حيث يؤدي إنشاء مصفوفة من الكائنات إلى إنشاء الكائنات نفسها عن طريق استدعاء المُنشئ الافتراضي الخاص بها.) في المثال أدناه، يريد الطالب إنشاء 3 كائنات من الفصل StringBuffer. سيتم تجميع التعليمات البرمجية بدون أخطاء، ولكن سيحدث استثناء في السطر الأخير NullPointerException، حيث يتم الوصول إلى كائن غير موجود. مثال خاطئ:
// Создаем массив из StringBuffer
StringBuffer [] myTempBuffers;
myTempBuffers = new StringBuffer[3];
myTempBuffers[0].add(data);
لتجنب هذا الخطأ، يجب أن تتذكر تهيئة عناصر المصفوفة. مثال مصحح:
// Создаем массив из StringBuffer и инициализируем элементы
StringBuffer [] myTempBuffers;
myTempBuffers = new StringBuffer[3];
for (int ix = 0; ix < myTempBuffers.length; ix++)
     myTempBuffers[ix] = new StringBuffer();

myTempBuffers[0].add(data);

4. وضع عدة فئات مع المعدل في ملف واحد في وقت واحدpublic

ترتبط ملفات Java المصدرية بطرق معينة بالفئات الموجودة في تلك الملفات. ويمكن وصف العلاقة على النحو التالي: لا يتم تخزين أي فئة Java في أكثر من ملف واحد. في أي ملف كود مصدري، لا يمكنك وضع أكثر من فئة واحدة مع المُعدِّل public. إذا كان هناك فصل به معدل في ملف التعليمات البرمجية المصدر public، فيجب أن يكون اسم الملف واسم الفصل متطابقين تمامًا (ملاحظة الترجمة: حتى الحالة، راجع النقطة 1) أحيانًا ينسى الطلاب القاعدة الثانية، مما يؤدي إلى حدوث أخطاء في مرحلة التجميع. ستكون رسالة الخطأ للقاعدتين الثانية والثالثة هي نفسها (وهذا ما يجعل من الصعب التعرف على هذا الخطأ).

5. استبدال حقل الفئة بمتغير محلي.

تتيح لك Java الإعلان عن المتغيرات داخل الطريقة التي يتطابق اسمها مع حقول الفئة. في هذه الحالة، ستكون للمتغيرات المحلية الأولوية وسيتم استخدامها بدلاً من الحقول. سوف يلقي المترجم خطأ إذا كانت المتغيرات التي لها نفس الأسماء من أنواع مختلفة. إذا كانت من نفس الأنواع، فلن يكون هناك خطأ في الترجمة، وستكون أسباب التشغيل غير الصحيح للبرنامج غير واضحة. مثال خاطئ:
public class Point3 {
    int i = 0;
    int j = 0;
    int k = 0;

    public boolean hits(Point[] p2list) {
      for(int i = 0; i < p2list.length; i++) {
        Point p2 = p2list[i];
        if (p2.x == i && p2.y == j)
          return true;
      }
      return false;
    }
}
هناك عدة طرق لإصلاح هذا الخطأ. أبسطها هو الوصول إلى حقول الفصل باستخدام المؤشر thisالضمني this.Name_поля. أفضل طريقة هي إعادة تسمية حقل الفئة أو المتغير المحلي، ثم لن يحدث الاستبدال. (تقريبًا. ترجمة: الطريقة الثانية ليست طريقتنا. علاوة على ذلك، فهي لا تضمن أنني لن أستبدل حقل متغير عن طريق الخطأ يومًا ما. تنشأ صعوبة أكبر مع الميراث، عندما لا أرى على الإطلاق ما هي الحقول الفصل لديه ) مثال مصحح:
// One way to fix the problem
  int i = 0;
  int j = 0;
  int k = 0;

  public boolean hits(Point[] p2list) {
    for(int i = 0; i < p2list.length; i++) {
      Point p2 = p2list[i];
      if (p2.x == this.i && p2.y == this.j)
        return true;
    }
    return false;
  }

  // *****************************
  // Лучший способ
  int x = 0;
  int y = 0;
  int z = 0;

  public boolean hits(Point[] p2list) {
    for(int i = 0; i < p2list.length; i++) {
      Point p2 = p2list[i];
      if (p2.x == x && p2.y == y)
        return true;
    }
    return false;
  }
مكان آخر محتمل لحدوث هذا الخطأ هو تعيين اسم معلمة الطريقة ليكون نفس اسم حقل الفئة. يبدو هذا جيدًا في المنشئين، ولكنه غير مناسب للطرق العادية.

تقريبا. ترجمة

فوضوية بعض الشيء، ولكن هذا هو الجوهر

public class Test {
   private int param = 0;

   public Test(int param) {
      this.param = param;
   }
}

أي أن كل شيء يبدو جميلًا في المُنشئ، لكن لا ينبغي استخدام هذا في الأساليب العادية.

6. نسيت استدعاء المُنشئ الأصلي (الطبقة الفائقة).

عندما تقوم فئة بتوسيع فئة أخرى، يجب على كل مُنشئ فئة فرعية استدعاء مُنشئ فئة فائقة. يتم تحقيق ذلك عادةً عن طريق استدعاء مُنشئ الطبقة الفائقة بالطريقة super(x)الموضوعة في السطر الأول من المُنشئ. إذا لم يكن هناك استدعاء في السطر الأول من المُنشئ super(x)، فسيقوم المترجم نفسه بإدراج هذا الاستدعاء، ولكن بدون معلمات: super(). (تقريبًا.: x...se، لكنني لم أكن أعرف) أحيانًا ينسى الطلاب هذا المطلب. عادةً لا تكون هذه مشكلة: يتم إدراج استدعاء مُنشئ الطبقة الفائقة بواسطة المترجم وكل شيء يعمل بشكل جيد. ومع ذلك، إذا لم يكن للفئة الفائقة مُنشئ افتراضي، فسيقوم المترجم بإلقاء خطأ. في المثال أدناه، java.io.Fileتحتوي جميع منشئات الفئة الفائقة على 1 أو 2 معلمة: مثال خاطئ:
public class JavaClassFile extends File {
    String classname;
    public JavaClassFile(String cl) {
        classname = cl;
    }
}
حل المشكلة هو إدراج استدعاء صريح لمنشئ الفئة الفائقة الصحيح: مثال مصحح:
public class JavaClassFile extends File {
    String classname;
    public JavaClassFile(String cl) {
        super(cl + ".class");
        classname = cl;
    }
}
يحدث موقف غير سارة عندما يكون لدى الفئة الفائقة مُنشئ افتراضي، لكنه لا يقوم بتهيئة الكائن بشكل كامل. في هذه الحالة، سيتم ترجمة التعليمات البرمجية، ولكن قد يكون إخراج البرنامج غير صحيح أو قد يحدث استثناء.

7. اصطياد الاستثناءات بشكل غير صحيح

يعد نظام معالجة الاستثناءات في Java قويًا جدًا، ولكن يصعب على المبتدئين فهمه. عادةً لا يواجه الطلاب الذين يتقنون لغة C++ أو Ada نفس الصعوبات التي يواجهها مبرمجو C وFortran. توضح الأمثلة أدناه بعض الأخطاء الشائعة. في هذا المثال، لم تتم تسمية الاستثناء. سيشير المترجم إلى هذا الخطأ في مرحلة التجميع، لذلك من السهل إصلاحه بنفسك. مثال خاطئ:
try {
    stream1 = new FileInputStream("data.txt");
} catch (IOException) {
    message("Could not open data.txt");
}
مثال مصحح:
try {
   stream1 = new FileInputStream("data.txt");
} catch (IOException ie) {
   message("Could not open data.txt: " + ie);
}
يحدد ترتيب الكتل الترتيب catchالذي يتم به اكتشاف الاستثناءات. يجب أن يؤخذ في الاعتبار أن كل كتلة من هذا القبيل ستلتقط جميع استثناءات الفئة المحددة أو أي من فئاتها الفرعية. إذا لم تأخذ ذلك في الاعتبار، فقد ينتهي بك الأمر إلى كتلة التقاط غير قابلة للوصول، وهو ما سيشير إليه المترجم. في المثال أدناه SocketExceptionفئة فرعية من IOException. مثال خاطئ:
try {
    serviceSocket.setSoTimeout(1000);
    newsock = serviceSocket.accept();
} catch (IOException ie) {
    message("Error accepting connection.");
} catch (SocketException se) {
    message("Error setting time-out.");
}
مثال مصحح:
try {
    serviceSocket.setSoTimeout(1000);
    newsock = serviceSocket.accept();
} catch (SocketException se) {
    message("Error setting time-out.");
} catch (IOException ie) {
    message("Error accepting connection.");
}
إذا كان من الممكن حدوث استثناء في التعليمات البرمجية الخاصة بك ولم يتم اكتشافه بواسطة أي كتلة try-catch، فيجب الإعلان عن هذا الاستثناء في رأس الطريقة. RuntimeException( هذا ليس ضروريًا للاستثناءات - الفئات الفرعية للفئة ). ينسى الطلاب أحيانًا أن استدعاء إحدى الطرق قد يؤدي إلى حدوث استثناء. أسهل طريقة لإصلاح ذلك هي وضع استدعاء الطريقة في كتلة try-catch. مثال خاطئ:
public void waitFor(int sec) {
    Thread.sleep(sec * 1000);
}
مثال مصحح:
public void waitFor(int sec) throws InterruptedException {
    Thread.sleep(sec * 1000);
}

8. طريقة الوصول لها نوعvoid

وهذا خطأ بسيط جدا. يقوم الطالب بإنشاء طريقة للوصول إلى متغير، لكنه يحدد أن الطريقة لا تُرجع أي شيء (يضع مُعدِّلاً voidفي رأس الطريقة). لإصلاح هذا الخطأ، يجب عليك تحديد نوع الإرجاع الصحيح. مثال خاطئ:
public class Line {
    private Point start, end;
    public void getStart() {
      return start;
    }
}
مثال مصحح:
public class Line {
    private Point start, end;
    public Point getStart() {
      return start;
    }
}
يؤدي تحديد نوع الإرجاع الخاطئ إلى إنشاء فئة كاملة من الأخطاء. عادةً ما يتعرف المترجم على هذه الأخطاء ويبلغ عنها حتى يتمكن الطلاب من تصحيحها بأنفسهم. المؤلف: A. Grasoff™ اقرأ المتابعة رابط المصدر: أخطاء مبرمجي جافا المبتدئين
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION