مقدمة
المؤشرات الخالية هي الاستثناءات الأكثر شيوعا والمزعج الذي نكرهه. من أجل منع المؤشرات الخالية من الاستثناءات ، يجب ألا تكتب الكثير من الأحكام غير الفريدة في القانون الخاص بك.
Java 8 يقدم فئة اختيارية جديدة. لتجنب حدوث المؤشرات الخالية ، ليست هناك حاجة لكتابة عدد كبير من الأحكام if(obj!=null) ، طالما كان عليك تحميل البيانات بشكل اختياري ، فهي عبارة عن حاوية تلتف الكائن.
يقال أنه لا يوجد مبرمج واجه استثناء مؤشر فارغ ليس مبرمج Java ، وقد تسبب Null بالفعل في العديد من المشكلات. تقدم Java 8 فئة جديدة تسمى java.util.Optional لتجنب العديد من المشكلات الناجمة عن Null.
دعونا نرى ما الضرر الذي يمكن أن يسببه مرجع فارغ. أولاً إنشاء كمبيوتر فئة ، يظهر الهيكل في الشكل أدناه:
ماذا يحدث عندما نسمي الرمز التالي؟
إصدار سلسلة = computer.getSoundCard (). getusb (). getVersion () ؛
يبدو أن الكود أعلاه على ما يرام ، لكن العديد من أجهزة الكمبيوتر (مثل Raspberry PI) لا تحتوي في الواقع على بطاقات صوتية ، لذا فإن استدعاء طريقة getSoundcard() سوف يلقي بالتأكيد استثناء مؤشر فارغ.
تتمثل الطريقة العادية ولكن السيئة في إرجاع مرجع فارغ للإشارة إلى أن الكمبيوتر لا يحتوي على بطاقة صوتية ، ولكن هذا يعني أنه سيتم استدعاء طريقة getUSB () على مرجع فارغ ، والتي من الواضح أنها ستلقي استثناء التحكم أثناء تشغيل البرنامج ، مما يتسبب في إيقاف البرنامج. فكر في الأمر ، ما مدى إحراجه فجأة عندما يعمل برنامجك على جهاز كمبيوتر عميل؟
كتب Tony Hoare العظيم في علوم الكمبيوتر: "أعتقد أن الاستشهادات الفارغة تم إنشاؤها في عام 1965 ، مما أدى إلى خسائر مليار دولار. كان أكبر إغراء بالنسبة لي عند استخدام الاستشهادات الفارغة أنه كان من السهل تنفيذها".
فكيف يمكننا تجنب استثناءات المؤشر الفارغ عند تشغيل البرنامج؟ يجب أن تكون في حالة تأهب وأن تحقق باستمرار من مؤشرات فارغة محتملة ، مثل هذا:
إصدار سلسلة = "غير معروف" ؛ if (computer! = null) {SoundCard SoundCard = computer.getSoundCard () ؛ if (soundcard! = null) {USB USB = SoundCard.getusB () ؛ if (USB! = null) {version = usb.getversion () ؛ }}} ومع ذلك ، يمكنك أن ترى أن الكود أعلاه يحتوي على الكثير من الشيكات الفارغة وأن بنية الكود بأكملها تصبح قبيحة للغاية. ولكن يتعين علينا استخدام هذا الحكم لضمان عدم وجود مؤشرات فارغة عند تشغيل النظام. من المزعج ببساطة الحكم إذا كان هناك الكثير من هذه المراجع الفارغة في رمز أعمالنا ، كما أنه يؤدي أيضًا إلى قابلية قراءة الكود لدينا.
إذا نسيت التحقق مما إذا كانت القيمة فارغة ، فإن المراجع الخالية لديها أيضًا مشاكل محتملة كبيرة. في هذه المقالة ، سأثبت أن استخدام المراجع الخالية كتمثيل حيث لا توجد القيم هو وسيلة سيئة. نحتاج إلى نموذج أفضل يشير إلى أن القيمة غير موجودة ، بدلاً من استخدام المراجع الخالية مرة أخرى.
قدمت Java 8 فئة جديدة تسمى java.util.Optional<T> ، والتي كانت مستوحاة من لغة Haskell ولغة Scala. يمكن أن تحتوي هذه الفئة على قيمة تعسفية ، كما هو موضح في الشكل والرمز أدناه. يمكنك التفكير في اختياري كقيمة قد تحتوي على قيمة. إذا كان الاختياري لا يحتوي على قيمة ، فهو فارغ ، كما هو موضح في الشكل أدناه.
CLASS CLASS Computer {private اختياري <SoundCard> SOUNDCARD ؛ Public Optional <SoundCard> getSoundCard () {...} ...} Class SoundCard {private اختياري <SB> USB ؛ الجمهور الاختياري <Sub> getusb () {...}} الفئة العامة USB {public String getVersion () {...}} يوضح الرمز أعلاه أن الكمبيوتر قد يحل محل بطاقة صوت (قد تكون أو عدم وجود بطاقة الصوت). قد تتضمن بطاقة الصوت أيضًا منفذ USB. هذه طريقة تحسين ، ويمكن أن يعكس النموذج بشكل أوضح أنه قد لا توجد قيمة معينة.
ولكن كيف تتعامل مع كائن Optional<Soundcard> ؟ بعد كل شيء ، ما تريد الحصول عليه هو رقم منفذ USB. الأمر بسيط للغاية. تحتوي الفئة الاختيارية على بعض الطرق للتعامل مع ما إذا كانت القيمة موجودة. بالمقارنة مع المراجع الخالية ، تجبرك الفئة الاختيارية على التعامل مع ما إذا كانت القيمة مرتبطة ، وبالتالي تجنب استثناءات المؤشر الفارغ.
تجدر الإشارة إلى أن الفئة الاختيارية لا تحل محل المراجع الخالية. على العكس من ذلك ، لتسهيل فهم واجهة برمجة التطبيقات المصممة ، عندما ترى توقيع الوظيفة ، يمكنك تحديد ما إذا كانت القيمة المراد نقلها إلى الوظيفة قد لا تكون موجودة. هذا يطالبك بفتح الفئة الاختيارية للتعامل مع القيمة الفعلية.
اعتماد الوضع الاختياري
بعد قول الكثير ، دعونا نلقي نظرة على بعض الكود! دعنا نلقي نظرة أولاً على كيفية استخدام اختياري لإعادة كتابة الكشف المرجعي الفارغ التقليدي. في نهاية هذه المقالة ، ستفهم كيفية استخدام اختياري.
اسم السلسلة = الكمبيوتر.
إنشاء كائنات اختيارية
يمكن إنشاء كائن اختياري فارغ:
اختياري <SoundCard> sc = اختياري. empty () ؛
التالي هو إنشاء قيم غير خارقة اختيارية:
SoundCard SoundCard = new SoundCard () ؛ اختياري <SoundCard> sc = اختياري (SoundCard) ؛
إذا كانت بطاقة الصوت خالية ، فسيتم طرح استثناء مؤشر NULL على الفور (هذا أفضل من مجرد رميها عند الحصول على سمة بطاقة الصوت).
باستخدام Ofnullable ، يمكنك إنشاء كائن اختياري قد يحتوي على مراجع خالية:
اختياري <SOUNDCARD> SC = اختياري.
إذا كانت بطاقة الصوت مرجعًا فارغًا ، فإن الكائن الاختياري فارغ.
معالجة القيم في اختياري
الآن بعد أن يوجد كائن اختياري ، يمكنك استدعاء الطريقة المقابلة للتعامل مع ما إذا كانت القيمة في الكائن الاختياري موجودة. بالمقارنة مع الكشف الفارغ ، يمكننا استخدام طريقة ifpresent () ، مثل هذا:
اختياري <SOUNDCARD> SOUNDCARD = ... ؛ SOUNDCARD.IFPRESENT (System.out :: println) ؛
بهذه الطريقة ، ليست هناك حاجة للقيام بالكشف الفارغ. إذا كان الكائن الاختياري فارغًا ، فلن يتم طباعة أي معلومات.
يمكنك أيضًا استخدام طريقة isPresent() لمعرفة ما إذا كان الكائن الاختياري موجودًا حقًا. بالإضافة إلى ذلك ، هناك أيضًا طريقة GET () التي تُرجع القيم المضمنة في الكائن الاختياري ، إن وجدت. خلاف ذلك ، سيتم طرح nosuchelementException. يمكن استخدام هاتين الطريقتين معًا مثل ما يلي لتجنب الاستثناءات:
if (soundcard.ispresent ()) {system.out.println (soundcard.get ()) ؛} ومع ذلك ، لا ينصح هذه الطريقة (ليس لها أي تحسن مقارنة بالكشف الفارغ). أدناه سنناقش الطرق المعتادة للعمل.
إرجاع القيم الافتراضية والعمليات ذات الصلة
عند مواجهة NULL ، تتمثل العملية العادية في إرجاع قيمة افتراضية ، يمكنك استخدام التعبيرات الثلاثية لتنفيذها:
بطاقة صوت بطاقة الصوت = MaybesoundCard! = NULL؟ MaybesoundCard: بطاقة صوت جديدة ("basic_sound_card") ؛ إذا كنت تستخدم كائنًا اختياريًا ، فيمكنك استخدام orElse() للتغلب. عندما يكون الاختياري فارغًا ، يمكن أن تُرجع orElse() قيمة افتراضية:
SoundCard SoundCard = MaybesoundCard.orelse (بطاقة صوت جديدة ("defaut")) ؛ وبالمثل ، عندما تكون اختيارية فارغة ، يمكن استخدام orelsethrow () لرمي الاستثناءات:
بطاقة SoundCard SoundCard = MaybesoundCard.orelsethRow (غير aluctalStateException :: New) ؛
استخدم المرشح لتصفية قيم محددة
غالبًا ما نسمي طريقة كائن للحكم على خصائصها. على سبيل المثال ، قد تحتاج إلى التحقق مما إذا كان رقم منفذ USB هو قيمة محددة. لأسباب تتعلق بالسلامة ، تحتاج إلى التحقق مما إذا كان الاستخدام الطبي الذي يشير إلى USB فارغًا ، ثم استدعاء طريقة getVersion() ، مثل هذا:
USB USB = ... ؛ if (USB! = null && "3.0" .equals (USB.GetVersion ())) {system.out.println ("OK") ؛} إذا كنت تستخدم اختياريًا ، فيمكنك استخدام وظيفة التصفية لإعادة كتابة:
اختياري <SB> ربما USB = ... ؛ ربما USB.Filter (USB -> "3.0" .equals (USB.GetVersion ()) .Ifpresent (() -> system.out.println ("ok")) ؛ تتطلب طريقة المرشح مسندًا مقابل معلمة. إذا كانت القيمة في الاختيارية موجودة وتتنبأ بها ، فإن وظيفة المرشح ستعيد قيمة تفي بالشرط ؛ خلاف ذلك ، سيتم إرجاع كائن اختياري فارغ.
استخدم طريقة الخريطة لاستخراج البيانات وتحويلها
النمط الشائع هو استخراج بعض خصائص كائن. على سبيل المثال ، بالنسبة لكائن بطاقة الصوت ، قد تحتاج إلى الحصول على كائن USB ثم تحديد رقم الإصدار الخاص به. عادة ما يكون تنفيذنا مثل هذا:
if (soundcard! = null) {USB USB = SoundCard.getusB () ؛ if (USB! = null && "3.0" .equals (USB.GetVersion ()) {system.out.println ("OK") ؛}} يمكننا استخدام طريقة الخريطة لتجاوز هذا الكشف الفارغ ثم استخراج الكائن من نوع الكائن.
اختياري <usb> USB = MaybesoundCard.map (SoundCard :: getusb) ؛
هذا هو نفسه استخدام وظيفة الخريطة باستخدام الدفق. يتطلب استخدام الدفق تمرير وظيفة كمعلمة إلى وظيفة الخريطة ، وسيتم تطبيق وظيفة تم تمريرها على كل عنصر في الدفق. عند تدفق المساحة والوقت ، لا شيء يحدث.
سيتم تحويل القيمة الواردة في اختياري بواسطة الوظيفة التي تم تمريرها (فيما يلي وظيفة تحصل على USB من بطاقة الصوت). إذا كان الكائن الاختياري وقتًا للمكان ، فلن يحدث شيء.
بعد ذلك ، نجمع بين طريقة الخريطة وطريقة التصفية لتصفية بطاقات الصوت مع رقم إصدار USB وليس 3.0.
maybesoundcard.map (SoundCard :: getusb) .filter (USB -> "3.0" .equals (USB.Getversion ()) .Ifpresent (() -> system.out.println ("ok")) ؛ وبهذه الطريقة ، يبدأ الكود الخاص بنا في أن يبدو مثل ما قدمناه في البداية ، دون اكتشاف فارغ.
تمرير كائن اختياري باستخدام وظيفة flatmap
الآن ، تم تقديم مثال على كيفية إعادة تشكيل الكود باستخدام اختياري. فكيف يجب أن ننفذ الكود التالي بطريقة آمنة؟
إصدار سلسلة = computer.getSoundCard (). getusb (). getVersion () ؛
لاحظ أن الكود أعلاه يتم استخراج كل كائن آخر من كائن واحد ، والذي يمكن تنفيذه باستخدام وظيفة الخريطة. في المقالة السابقة ، قمنا بإعداد كائن Optional<Soundcard> في الكمبيوتر ، ويحتوي بطاقة الصوت على كائن Optional<USB> ، حتى نتمكن من إعادة تشكيل الرمز بهذه الطريقة
إصدار سلسلة = computer.map (الكمبيوتر :: getSoundCard) .map (SoundCard :: getusb) .map (USB :: getVersion) .orelse ("غير معروف") ؛ لسوء الحظ ، فإن الكود أعلاه يجمع الأخطاء ، فلماذا؟ متغير الكمبيوتر من النوع Optional<Computer> ، لذلك ليس لديه مشكلة في استدعاء وظيفة الخريطة. ومع ذلك ، getSoundcard() تُرجع كائن Optional<Soundcard> ، والذي يقوم بإرجاع كائن من النوع Optional<Optional<Soundcard>> . بعد استدعاء وظيفة الخريطة الثانية ، تصبح الدعوة إلى وظيفة getUSB() غير قانونية.
يصف الشكل التالي هذا السيناريو:
إن تنفيذ رمز المصدر لوظيفة الخريطة هو كما يلي:
public <u> اختياري <u> خريطة (وظيفة <؟ super t ،؟ يمتد u> mapper) {objects.requirenonnull (mapper) ؛ if (! ispresent ()) إرجاع فارغ () ؛ else {return eptarical.ofNullable (mapper.apply (value)) ؛ }} يمكن ملاحظة أن وظيفة الخريطة سوف تستدعي Optional.ofNullable() Optional<Optional<Soundcard>>
يوفر الاختياري وظيفة FlatMap ، والتي تم تصميمها لتحويل قيمة الكائن الاختياري (مثل عملية الخريطة) ثم ضغط مستويين إلى مستويين إلى واحد. يوضح الشكل التالي الفرق بين الكائنات الاختيارية في تحويل النوع عن طريق استدعاء خريطة و FlatMap:
لذلك يمكننا كتابة هذا:
إصدار سلسلة = computer.flatmap (الكمبيوتر :: getSoundCard) .flatmap (SoundCard :: getusb) .map (USB :: getversion) .orelse ("غير معروف") ؛ تضمن FlatMap الأول أن تكون الإرجاع Optional<Soundcard> بدلاً من Optional<Optional<Soundcard>> ، وتنفيذ FlatMap الثاني نفس الوظيفة بحيث تكون العائد Optional<USB> . لاحظ أن map() تسمى المرة الثالثة ، لأن getVersion() يعيد كائن سلسلة بدلاً من كائن اختياري.
أخيرًا نعيد كتابة رمز الشيكات الفارغة المتداخلة التي بدأناها للتو ، والتي يمكن قراءتها للغاية ، وأيضًا تجنب حدوث استثناءات مؤشر فارغ.
لخص
في هذه المقالة ، نعتمد الفئة الجديدة java.util.Optional<T> التي توفرها Java 8. لا تتمثل النية الأصلية لهذه الفئة في استبدال المراجع الخالية ، ولكن لمساعدة المصممين على تصميم واجهات برمجة التطبيقات بشكل أفضل. ما عليك سوى قراءة توقيع الوظيفة ومعرفة ما إذا كانت الوظيفة تقبل القيمة التي قد تكون أو غير موجودة. بالإضافة إلى ذلك ، يجبرك الاختياري على تشغيل اختياري ثم التعامل مع ما إذا كانت القيمة موجودة ، مما يجعل الكود الخاص بك يتجنب استثناءات مؤشر NULL المحتملة.
حسنًا ، ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.