Buggy Java Code: أكثر 10 أخطاء شائعة يرتكبها مطورو Java

نشرت: 2022-03-11

Java هي لغة برمجة تم تطويرها في البداية للتلفزيون التفاعلي ، ولكن بمرور الوقت أصبحت منتشرة في كل مكان يمكن استخدام البرامج فيه. صممت Java بمفهوم البرمجة الموجهة للكائنات ، وألغت تعقيدات اللغات الأخرى مثل C أو C ++ ، وجمع البيانات المهملة ، وآلة افتراضية حيادية من الناحية المعمارية ، وأنشأت طريقة جديدة للبرمجة. علاوة على ذلك ، فهو يتمتع بمنحنى تعليمي لطيف ويبدو أنه يلتزم بنجاح بشخصيته الخاصة - "اكتب مرة واحدة ، اركض في كل مكان" ، وهذا صحيح دائمًا ؛ لكن مشاكل Java لا تزال موجودة. سأقوم بمعالجة عشرة مشاكل جافا التي أعتقد أنها الأخطاء الأكثر شيوعًا.

الخطأ الشائع الأول: إهمال المكتبات الموجودة

من المؤكد أنه من الخطأ أن يتجاهل مطورو Java عدد لا يحصى من المكتبات المكتوبة بلغة Java. قبل إعادة اختراع العجلة ، حاول البحث عن المكتبات المتاحة - فقد تم صقل العديد منها على مدار سنوات من وجودها ويمكن استخدامها مجانًا. يمكن أن تكون هذه مكتبات تسجيل ، مثل logback و Log4j ، أو مكتبات ذات صلة بالشبكة ، مثل Netty أو Akka. أصبحت بعض المكتبات ، مثل Joda-Time ، معيارًا واقعيًا.

فيما يلي تجربة شخصية من أحد مشاريعي السابقة. تمت كتابة جزء الكود المسؤول عن هروب HTML من البداية. كانت تعمل بشكل جيد لسنوات ، لكنها واجهت في النهاية إدخالًا من المستخدم جعلها تدور في حلقة لا نهائية. حاول المستخدم ، الذي وجد الخدمة غير مستجيبة ، إعادة المحاولة بنفس الإدخال. في النهاية ، تم شغل جميع وحدات المعالجة المركزية (CPU) الموجودة على الخادم المخصصة لهذا التطبيق بواسطة هذه الحلقة اللانهائية. إذا قرر مؤلف أداة هروب HTML الساذجة استخدام إحدى المكتبات المعروفة المتاحة للهروب من HTML ، مثل HtmlEscapers من Google Guava ، فمن المحتمل ألا يحدث هذا. على الأقل ، صحيح بالنسبة لمعظم المكتبات الشائعة التي يوجد بها مجتمع وراءها ، كان من الممكن أن يتم العثور على الخطأ وإصلاحه في وقت سابق من قبل المجتمع لهذه المكتبة.

الخطأ الشائع الثاني: فقدان الكلمة الرئيسية "الفاصلة" في كتلة حالة التبديل

قد تكون مشكلات Java هذه محرجة للغاية ، وفي بعض الأحيان تظل غير مكتشفة حتى يتم تشغيلها في الإنتاج. غالبًا ما يكون السلوك الخاطئ في عبارات التبديل مفيدًا ؛ ومع ذلك ، فإن عدم وجود كلمة رئيسية "فاصل" عندما يكون مثل هذا السلوك غير مرغوب فيه يمكن أن يؤدي إلى نتائج كارثية. إذا نسيت وضع "فاصل" في "الحالة 0" في مثال الكود أدناه ، فسيقوم البرنامج بكتابة "صفر" متبوعًا بـ "واحد" ، نظرًا لأن تدفق التحكم بالداخل سيمر عبر عبارة "التبديل" بالكامل حتى تصل إلى "استراحة". علي سبيل المثال:

 public static void switchCasePrimer() { int caseIndex = 0; switch (caseIndex) { case 0: System.out.println("Zero"); case 1: System.out.println("One"); break; case 2: System.out.println("Two"); break; default: System.out.println("Default"); } }

في معظم الحالات ، يكون الحل الأنظف هو استخدام تعدد الأشكال ونقل التعليمات البرمجية ذات السلوكيات المحددة إلى فئات منفصلة. يمكن اكتشاف أخطاء Java مثل هذا الخطأ باستخدام محللات الكود الثابت ، مثل FindBugs و PMD.

الخطأ الشائع الثالث: نسيان الموارد الحرة

في كل مرة يفتح فيها أحد البرامج ملفًا أو اتصالاً بالشبكة ، من المهم للمبتدئين في Java تحرير المورد بمجرد الانتهاء من استخدامه. يجب توخي الحذر نفسه في حالة طرح أي استثناء أثناء العمليات على هذه الموارد. يمكن للمرء أن يجادل بأن FileInputStream يحتوي على أداة نهائية تستدعي طريقة close () في حدث تجميع البيانات المهملة ؛ ومع ذلك ، نظرًا لأنه لا يمكننا التأكد من موعد بدء دورة جمع البيانات المهملة ، يمكن أن يستهلك تدفق الإدخال موارد الكمبيوتر لفترة غير محددة من الوقت. في الواقع ، هناك عبارة مفيدة وأنيقة حقًا تم تقديمها في Java 7 خاصة لهذه الحالة ، تسمى try-with-Resources:

 private static void printFileJava7() throws IOException { try(FileInputStream input = new FileInputStream("file.txt")) { int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } } }

يمكن استخدام هذه العبارة مع أي كائن يقوم بتنفيذ واجهة AutoClosable. يضمن إغلاق كل مورد بنهاية البيان.

الموضوعات ذات الصلة: 8 أسئلة مقابلة جافا أساسية

الخطأ الشائع الرابع: تسرب الذاكرة

تستخدم Java الإدارة التلقائية للذاكرة ، وعلى الرغم من أنه من المريح نسيان تخصيص الذاكرة وتحريرها يدويًا ، فهذا لا يعني أن مطور Java المبتدئ يجب ألا يكون على دراية بكيفية استخدام الذاكرة في التطبيق. لا تزال مشاكل تخصيص الذاكرة ممكنة. طالما أن البرنامج ينشئ إشارات إلى كائنات لم تعد مطلوبة بعد الآن ، فلن يتم تحريرها. بطريقة ما ، لا يزال بإمكاننا استدعاء تسرب الذاكرة هذا. يمكن أن تحدث تسريبات الذاكرة في Java بطرق مختلفة ، ولكن السبب الأكثر شيوعًا هو مراجع الكائنات الدائمة ، لأن جامع القمامة لا يمكنه إزالة الكائنات من الكومة بينما لا تزال هناك مراجع لها. يمكن للمرء إنشاء مثل هذا المرجع من خلال تعريف فئة مع حقل ثابت يحتوي على بعض مجموعة من الكائنات ، ونسيان تعيين هذا الحقل الثابت إلى قيمة خالية بعد عدم الحاجة إلى المجموعة. تعتبر الحقول الثابتة جذور GC ولا يتم جمعها أبدًا.

سبب آخر محتمل وراء مثل هذه التسريبات في الذاكرة هو مجموعة من الكائنات تشير إلى بعضها البعض ، مما يتسبب في تبعيات دائرية بحيث لا يمكن لمجمع البيانات المهملة تحديد ما إذا كانت هذه الكائنات التي تحتوي على مراجع التبعية المتبادلة مطلوبة أم لا. هناك مشكلة أخرى تتمثل في حدوث تسريبات في الذاكرة غير الكومة عند استخدام JNI.

قد يبدو مثال التسرب البدائي كما يلي:

 final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); final Deque<BigDecimal> numbers = new LinkedBlockingDeque<>(); final BigDecimal divisor = new BigDecimal(51); scheduledExecutorService.scheduleAtFixedRate(() -> { BigDecimal number = numbers.peekLast(); if (number != null && number.remainder(divisor).byteValue() == 0) { System.out.println("Number: " + number); System.out.println("Deque size: " + numbers.size()); } }, 10, 10, TimeUnit.MILLISECONDS); scheduledExecutorService.scheduleAtFixedRate(() -> { numbers.add(new BigDecimal(System.currentTimeMillis())); }, 10, 10, TimeUnit.MILLISECONDS); try { scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }

يقوم هذا المثال بإنشاء مهمتين مجدولتين. تأخذ المهمة الأولى الرقم الأخير من deque يسمى "الأرقام" وتطبع الرقم وحجم deque في حالة كان الرقم قابلاً للقسمة على 51. المهمة الثانية تضع الأرقام في deque. تتم جدولة كلتا المهمتين بمعدل ثابت ، ويتم تشغيلهما كل 10 مللي ثانية. إذا تم تنفيذ الكود ، فسترى أن حجم deque يتزايد بشكل دائم. سيؤدي هذا في النهاية إلى ملء deque بالكائنات التي تستهلك كل ذاكرة الكومة المتاحة. لمنع هذا مع الحفاظ على دلالات هذا البرنامج ، يمكننا استخدام طريقة مختلفة لأخذ الأرقام من deque: “pollLast”. على عكس الطريقة "peekLast" ، تقوم "pollLast" بإرجاع العنصر وإزالته من deque بينما تقوم "peekLast" بإرجاع العنصر الأخير فقط.

لمعرفة المزيد حول تسرب الذاكرة في Java ، يرجى الرجوع إلى مقالتنا التي أزال الغموض عن هذه المشكلة.

الخطأ الشائع الخامس: الإفراط في تخصيص القمامة

قد يحدث تخصيص مفرط للقمامة عندما يقوم البرنامج بإنشاء الكثير من الكائنات قصيرة العمر. يعمل جهاز تجميع البيانات المهملة بشكل مستمر ، مما يؤدي إلى إزالة الكائنات غير الضرورية من الذاكرة ، مما يؤثر على أداء التطبيقات بطريقة سلبية. مثال واحد بسيط:

 String oneMillionHello = ""; for (int i = 0; i < 1000000; i++) { oneMillionHello = oneMillionHello + "Hello!"; } System.out.println(oneMillionHello.substring(0, 6));

في تطوير Java ، السلاسل غير قابلة للتغيير. لذلك ، في كل تكرار يتم إنشاء سلسلة جديدة. لمعالجة هذا يجب علينا استخدام StringBuilder قابل للتغيير:

 StringBuilder oneMillionHelloSB = new StringBuilder(); for (int i = 0; i < 1000000; i++) { oneMillionHelloSB.append("Hello!"); } System.out.println(oneMillionHelloSB.toString().substring(0, 6));

بينما يتطلب الإصدار الأول وقتًا طويلاً للتنفيذ ، فإن الإصدار الذي يستخدم StringBuilder ينتج نتيجة في وقت أقل بكثير.

الخطأ الشائع السادس: استخدام المراجع الفارغة دون الحاجة

يعد تجنب الاستخدام المفرط لـ null ممارسة جيدة. على سبيل المثال ، يُفضل إرجاع المصفوفات أو المجموعات الفارغة من العمليات بدلاً من القيم الخالية ، حيث يمكن أن تساعد في منع NullPointerException.

ضع في اعتبارك الطريقة التالية التي تعبر مجموعة تم الحصول عليها من طريقة أخرى ، كما هو موضح أدناه:

 List<String> accountIds = person.getAccountIds(); for (String accountId : accountIds) { processAccount(accountId); }

إذا أعادت getAccountIds () قيمة خالية عندما لا يكون لدى الشخص حساب ، فسيتم رفع NullPointerException. لإصلاح ذلك ، ستكون هناك حاجة إلى تحقق فارغ. ومع ذلك ، إذا تم إرجاع قائمة فارغة بدلاً من القيمة الفارغة ، فإن NullPointerException لم يعد يمثل مشكلة. علاوة على ذلك ، فإن الكود أنظف لأننا لسنا بحاجة إلى التحقق من المتغير accountIds.

للتعامل مع الحالات الأخرى عندما يريد المرء تجنب الأصفار ، يمكن استخدام استراتيجيات مختلفة. تتمثل إحدى هذه الاستراتيجيات في استخدام النوع الاختياري الذي يمكن أن يكون إما كائنًا فارغًا أو التفافًا له قيمة ما:

 Optional<String> optionalString = Optional.ofNullable(nullableString); if(optionalString.isPresent()) { System.out.println(optionalString.get()); }

في الواقع ، توفر Java 8 حلاً أكثر إيجازًا:

 Optional<String> optionalString = Optional.ofNullable(nullableString); optionalString.ifPresent(System.out::println);

كان النوع الاختياري جزءًا من Java منذ الإصدار 8 ، ولكنه معروف جيدًا لفترة طويلة في عالم البرمجة الوظيفية. قبل ذلك ، كان متاحًا في Google Guava للإصدارات السابقة من Java.

الخطأ الشائع السابع: تجاهل الاستثناءات

غالبًا ما يكون من المغري ترك الاستثناءات دون معالجة. ومع ذلك ، فإن أفضل ممارسة لمطوري Java المبتدئين وذوي الخبرة على حد سواء هي التعامل معهم. يتم طرح الاستثناءات عمدًا ، لذلك نحتاج في معظم الحالات إلى معالجة المشكلات التي تسبب هذه الاستثناءات. لا تغفل عن هذه الأحداث. إذا لزم الأمر ، يمكنك إما إعادة عرضها أو إظهار مربع حوار خطأ للمستخدم أو إضافة رسالة إلى السجل. على الأقل ، يجب توضيح سبب ترك الاستثناء دون معالجة من أجل السماح للمطورين الآخرين بمعرفة السبب.

 selfie = person.shootASelfie(); try { selfie.show(); } catch (NullPointerException e) { // Maybe, invisible man. Who cares, anyway? }

هناك طريقة أوضح لإبراز عدم أهمية الاستثناءات وهي ترميز هذه الرسالة في اسم متغير الاستثناءات ، على النحو التالي:

 try { selfie.delete(); } catch (NullPointerException unimportant) { }

الخطأ الشائع الثامن: استثناء التعديل المتزامن

يحدث هذا الاستثناء عندما يتم تعديل مجموعة أثناء التكرار عليها باستخدام طرق غير تلك التي يوفرها كائن المكرر. على سبيل المثال ، لدينا قائمة بالقبعات ونريد إزالة كل القبعات التي تحتوي على غطاء للأذن:

 List<IHat> hats = new ArrayList<>(); hats.add(new Ushanka()); // that one has ear flaps hats.add(new Fedora()); hats.add(new Sombrero()); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hats.remove(hat); } }

إذا قمنا بتشغيل هذا الرمز ، فسيتم رفع "ConcurrentModificationException" لأن الكود يعدل المجموعة أثناء تكرارها. قد يحدث نفس الاستثناء إذا كان أحد مؤشرات الترابط المتعددة التي تعمل مع نفس القائمة يحاول تعديل المجموعة بينما يقوم الآخرون بتكرارها. يعد التعديل المتزامن للمجموعات في سلاسل عمليات متعددة أمرًا طبيعيًا ، ولكن يجب معالجته باستخدام الأدوات المعتادة من مربع أدوات البرمجة المتزامنة مثل أقفال المزامنة والمجموعات الخاصة المعتمدة للتعديل المتزامن وما إلى ذلك. هناك اختلافات دقيقة حول كيفية حل مشكلة Java هذه في الحالات ذات الخيوط المفردة والحالات متعددة الخيوط. فيما يلي مناقشة موجزة لبعض الطرق التي يمكن بها معالجة ذلك في سيناريو مترابط واحد:

جمع الأشياء وإزالتها في حلقة أخرى

يعد جمع القبعات ذات أغطية الأذن في قائمة لإزالتها لاحقًا من داخل حلقة أخرى حلاً واضحًا ، ولكنه يتطلب مجموعة إضافية لتخزين القبعات المراد إزالتها:

 List<IHat> hatsToRemove = new LinkedList<>(); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hatsToRemove.add(hat); } } for (IHat hat : hatsToRemove) { hats.remove(hat); }

استخدم طريقة Iterator.remove

هذا النهج أكثر إيجازًا ، ولا يحتاج إلى إنشاء مجموعة إضافية:

 Iterator<IHat> hatIterator = hats.iterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); } }

استخدم أساليب ListIterator

يعد استخدام مكرر القائمة مناسبًا عندما تطبق المجموعة المعدلة واجهة القائمة. لا تدعم التكرارات التي تنفذ واجهة ListIterator عمليات الإزالة فحسب ، بل تدعم أيضًا عمليات الإضافة وتعيينها. يقوم ListIterator بتنفيذ واجهة Iterator لذا سيبدو المثال تقريبًا مثل طريقة إزالة Iterator. الاختلاف الوحيد هو نوع مكرر القبعة ، والطريقة التي نحصل بها على هذا المكرر باستخدام طريقة "listIterator ()". يوضح المقتطف أدناه كيفية استبدال كل قبعة بغطاء أذن بأسلوب سومبريرو باستخدام طريقتين "ListIterator.remove" و "ListIterator.add":

 IHat sombrero = new Sombrero(); ListIterator<IHat> hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); hatIterator.add(sombrero); } }

باستخدام ListIterator ، يمكن استبدال استدعاءات طريقة الإزالة والإضافة بمكالمة واحدة لتعيين:

 IHat sombrero = new Sombrero(); ListIterator<IHat> hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.set(sombrero); // set instead of remove and add } }

استخدم طرق الدفق المقدمة في Java 8 باستخدام Java 8 ، يتمتع المبرمجون بالقدرة على تحويل مجموعة إلى دفق وتصفية هذا التدفق وفقًا لبعض المعايير. فيما يلي مثال على كيف يمكن أن تساعدنا واجهة برمجة التطبيقات للدفق في تصفية القبعات وتجنب "ConcurrentModificationException".

 hats = hats.stream().filter((hat -> !hat.hasEarFlaps())) .collect(Collectors.toCollection(ArrayList::new));

ستنشئ طريقة "Collectors.toCollection" قائمة ArrayList جديدة بقبعات تمت تصفيتها. يمكن أن تكون هذه مشكلة إذا كان شرط التصفية سيتم استيفائه بعدد كبير من العناصر ، مما يؤدي إلى ArrayList كبيرة ؛ وبالتالي ، يجب استخدامه بحذر. استخدم طريقة List.removeIf المقدمة في Java 8 الحل الآخر المتاح في Java 8 ، والأكثر إيجازًا بشكل واضح ، هو استخدام طريقة "removeIf":

 hats.removeIf(IHat::hasEarFlaps);

هذا هو. تحت الغطاء ، يستخدم "Iterator.remove" لإنجاز السلوك.

استخدم مجموعات متخصصة

إذا قررنا في البداية استخدام "CopyOnWriteArrayList" بدلاً من "ArrayList" ، فلن تكون هناك مشكلة على الإطلاق ، نظرًا لأن "CopyOnWriteArrayList" يوفر طرق تعديل (مثل التعيين والإضافة والإزالة) التي لا تتغير مجموعة دعم المجموعة ، ولكن بدلاً من ذلك قم بإنشاء نسخة جديدة معدلة منها. هذا يسمح بالتكرار على النسخة الأصلية للمجموعة والتعديلات عليها في نفس الوقت ، دون المخاطرة بـ “ConcurrentModificationException”. عيب تلك المجموعة واضح - توليد مجموعة جديدة مع كل تعديل.

هناك مجموعات أخرى تم ضبطها لحالات مختلفة ، على سبيل المثال "CopyOnWriteSet" و "ConcurrentHashMap".

خطأ آخر محتمل مع تعديلات المجموعة المتزامنة هو إنشاء دفق من مجموعة ، وأثناء تكرار الدفق ، قم بتعديل مجموعة الدعم. القاعدة العامة للتدفقات هي تجنب تعديل المجموعة الأساسية أثناء الاستعلام عن الدفق. سيُظهر المثال التالي طريقة غير صحيحة للتعامل مع البث:

 List<IHat> filteredHats = hats.stream().peek(hat -> { if (hat.hasEarFlaps()) { hats.remove(hat); } }).collect(Collectors.toCollection(ArrayList::new));

تجمع نظرة خاطفة للطريقة كل العناصر وتنفذ الإجراء المقدم على كل عنصر منها. هنا ، يحاول الإجراء إزالة العناصر من القائمة الأساسية ، وهذا خطأ. لتجنب ذلك ، جرب بعض الطرق الموضحة أعلاه.

الخطأ الشائع التاسع: كسر العقود

في بعض الأحيان ، تعتمد التعليمات البرمجية التي يتم توفيرها بواسطة المكتبة القياسية أو بواسطة بائع تابع لجهة خارجية على القواعد التي يجب الالتزام بها من أجل جعل الأشياء تعمل. على سبيل المثال ، يمكن أن يكون hashCode و يساوي العقد الذي عند اتباعه ، يجعل العمل مضمونًا لمجموعة من المجموعات من إطار عمل مجموعة Java ، وللفئات الأخرى التي تستخدم أساليب hashCode و equals. عدم إطاعة العقود ليس نوعًا من الخطأ الذي يؤدي دائمًا إلى الاستثناءات أو كسر تجميع التعليمات البرمجية ؛ الأمر أكثر صعوبة ، لأنه في بعض الأحيان يغير سلوك التطبيق دون أي علامة على الخطر. يمكن أن تنزلق الشفرة الخاطئة إلى إصدار الإنتاج وتتسبب في مجموعة كاملة من التأثيرات غير المرغوب فيها. يمكن أن يشمل ذلك سلوك واجهة المستخدم السيئ وتقارير البيانات الخاطئة وأداء التطبيق الضعيف وفقدان البيانات والمزيد. لحسن الحظ ، لا تحدث هذه الأخطاء الكارثية كثيرًا. لقد ذكرت بالفعل عقد التجزئة ويساوي. يتم استخدامه في المجموعات التي تعتمد على تجزئة العناصر ومقارنتها ، مثل HashMap و HashSet. ببساطة ، يحتوي العقد على قاعدتين:

  • إذا تساوى كائنان ، فيجب أن تكون رموز التجزئة الخاصة بهم متساوية.
  • إذا كان هناك كائنان لهما نفس رمز التجزئة ، فقد يكونان متساويين وقد لا يكونان.

يؤدي كسر القاعدة الأولى في العقد إلى حدوث مشكلات أثناء محاولة استرداد العناصر من علامة التجزئة. تشير القاعدة الثانية إلى أن الكائنات التي لها نفس رمز التجزئة ليست بالضرورة متساوية. دعونا نفحص آثار كسر القاعدة الأولى:

 public static class Boat { private String name; Boat(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Boat boat = (Boat) o; return !(name != null ? !name.equals(boat.name) : boat.name != null); } @Override public int hashCode() { return (int) (Math.random() * 5000); } }

كما ترى ، فقد تجاوز class Boat أساليب equals و hashCode. ومع ذلك ، فقد انتهك العقد ، لأن hashCode يُرجع قيمًا عشوائية لنفس الكائن في كل مرة يتم استدعاؤه. لن تجد الكود التالي على الأرجح قاربًا باسم "Enterprise" في التجزئة ، على الرغم من حقيقة أننا أضفنا هذا النوع من القوارب في وقت سابق:

 public static void main(String[] args) { Set<Boat> boats = new HashSet<>(); boats.add(new Boat("Enterprise")); System.out.printf("We have a boat named 'Enterprise' : %b\n", boats.contains(new Boat("Enterprise"))); }

مثال آخر للعقد يتضمن طريقة الإنجاز. فيما يلي اقتباس من وثائق جافا الرسمية يصف وظيفتها:

العقد العام للانتهاء هو أنه يتم استدعاؤه إذا وعندما يقرر الجهاز الظاهري لـ JavaTM أنه لم يعد هناك أي وسيلة يمكن من خلالها الوصول إلى هذا الكائن من خلال أي مؤشر ترابط (لم يختف بعد) ، باستثناء نتيجة الإجراء الذي تم اتخاذه من خلال وضع اللمسات الأخيرة على كائن أو فئة أخرى جاهزة للانتهاء. قد يتخذ التابع finalize أي إجراء ، بما في ذلك جعل هذا الكائن متاحًا مرة أخرى لمؤشرات الترابط الأخرى ؛ ومع ذلك ، فإن الغرض المعتاد من الإنهاء هو تنفيذ إجراءات التنظيف قبل التخلص من الكائن بشكل نهائي. على سبيل المثال ، قد تقوم طريقة الإنهاء للكائن الذي يمثل اتصال إدخال / إخراج بإجراء معاملات إدخال / إخراج صريحة لقطع الاتصال قبل تجاهل الكائن نهائيًا.

يمكن للمرء أن يقرر استخدام طريقة الإنهاء لتحرير الموارد مثل معالجات الملفات ، لكن هذه ستكون فكرة سيئة. هذا لأنه لا توجد ضمانات زمنية بشأن وقت استدعاء الإنهاء ، حيث يتم استدعاؤه أثناء جمع البيانات المهملة ، ووقت GC غير قابل للتحديد.

الخطأ الشائع رقم 10: استخدام النوع الخام بدلاً من النوع ذي المعاملات

الأنواع الأولية ، وفقًا لمواصفات Java ، هي أنواع إما غير معلمة أو أعضاء غير ثابتة من الفئة R غير موروثة من الطبقة الفائقة أو الواجهة الفائقة لـ R. لم تكن هناك بدائل للأنواع الأولية حتى يتم تقديم الأنواع العامة في Java . وهو يدعم البرمجة العامة منذ الإصدار 1.5 ، وكانت الأدوية الجنسية بلا شك تحسنًا كبيرًا. ومع ذلك ، نظرًا لأسباب التوافق مع الإصدارات السابقة ، فقد تم ترك مشكلة قد تؤدي إلى كسر نظام الكتابة. لنلق نظرة على المثال التالي:

 List listOfNumbers = new ArrayList(); listOfNumbers.add(10); listOfNumbers.add("Twenty"); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

هنا لدينا قائمة بالأرقام المحددة على أنها ArrayList خام. نظرًا لأن نوعه غير محدد بمعامل النوع ، يمكننا إضافة أي كائن إليه. لكن في السطر الأخير ، قمنا بإلقاء العناصر من أجل int ، ومضاعفتها ، وطباعة الرقم المضاعف إلى الناتج القياسي. سيتم تجميع هذا الرمز بدون أخطاء ، ولكن بمجرد تشغيله سيؤدي إلى استثناء وقت التشغيل لأننا حاولنا تحويل سلسلة إلى عدد صحيح. من الواضح أن نظام الكتابة غير قادر على مساعدتنا في كتابة رمز آمن إذا أخفينا المعلومات الضرورية منه. لإصلاح المشكلة ، نحتاج إلى تحديد نوع العناصر التي سنخزنها في المجموعة:

 List<Integer> listOfNumbers = new ArrayList<>(); listOfNumbers.add(10); listOfNumbers.add("Twenty"); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

الاختلاف الوحيد عن الأصل هو السطر الذي يحدد المجموعة:

 List<Integer> listOfNumbers = new ArrayList<>();

لن يتم ترجمة الشفرة الثابتة لأننا نحاول إضافة سلسلة إلى مجموعة من المتوقع أن تخزن أعدادًا صحيحة فقط. سيظهر المترجم خطأ ويشير إلى السطر الذي نحاول فيه إضافة السلسلة "عشرين" إلى القائمة. من الجيد دائمًا تحديد معلمات للأنواع العامة. بهذه الطريقة ، يكون المترجم قادرًا على إجراء جميع عمليات التحقق الممكنة من النوع ، وتقليل فرص استثناءات وقت التشغيل الناتجة عن عدم تناسق نظام النوع.

خاتمة

تعمل Java كمنصة على تبسيط العديد من الأشياء في تطوير البرامج ، بالاعتماد على كل من JVM المتطورة واللغة نفسها. ومع ذلك ، فإن ميزاته ، مثل إزالة إدارة الذاكرة اليدوية أو أدوات OOP اللائقة ، لا تقضي على جميع المشكلات والمشكلات التي يواجهها مطور Java العادي. كما هو الحال دائمًا ، فإن المعرفة والممارسة ودروس Java التعليمية مثل هذه هي أفضل الوسائل لتجنب أخطاء التطبيق ومعالجتها - لذا تعرف على مكتباتك ، واقرأ جافا ، واقرأ وثائق JVM ، واكتب البرامج. لا تنسَ محللي الكود الثابت أيضًا ، حيث يمكنهم الإشارة إلى الأخطاء الفعلية وإبراز الأخطاء المحتملة.

الموضوعات ذات الصلة: دروس متقدمة في Java Class: دليل لإعادة تحميل الفصل