تمت إضافة Java Local Variable Type Inference (LVTI) ، أو باختصار ، var var (المعرف var ليس كلمة رئيسية ، ولكن اسم نوع محجوز) إلى Java 10 باستخدام JEP 286: Local Variable Type Inference . كونه وظيفة مترجم 100 ٪ ، فإنه لا يؤثر على البايت أو وقت التشغيل أو الأداء. في الأساس ، يقوم المحول البرمجي بفحص الجانب الأيمن من مشغل المهمة ، وبناءً عليه ، يحدد النوع المحدد للمتغير ، ثم يستبدله بـ var .
بالإضافة إلى ذلك ، يكون مفيدًا في الحد من شفافية كود boilerplate ، كما أنه يسرع من عملية البرمجة نفسها. على سبيل المثال ، من المريح جدًا كتابة var evenAndOdd =...
بدلاً من Map<Boolean, List<Integer>> evenAndOdd =...
لا يعني ظهور var أنه دائمًا ومناسب لاستخدامه في كل مكان ، وأحيانًا يكون الأمر أكثر عملية مع الأدوات القياسية.
في هذه المقالة ، سننظر في 26 موقفًا ، مع أمثلة حول متى يمكنك استخدام var ، وعندما لا يستحق ذلك.
النقطة 1: حاول إعطاء أسماء ذات معنى للمتغيرات المحلية
عادة ما نركز على إعطاء الأسماء الصحيحة لحقول الفئات ، لكننا لا نولي نفس الاهتمام لأسماء المتغيرات المحلية. عندما يتم تطبيق أساليبنا تمامًا ، تحتوي على القليل من الشفرة ولديها أسماء جيدة ، غالبًا ما لا نولي اهتمامًا للمتغيرات المحلية ، أو حتى ننقص أسمائها تمامًا.
عندما نستخدم var بدلاً من كتابة أنواع صريحة ، يقوم المترجم تلقائيًا باكتشافها والبدائل var . ولكن من ناحية أخرى ، نتيجة لذلك ، يصبح من الصعب على الأشخاص قراءة التعليمات البرمجية وفهمها ، حيث إن استخدام var يمكن أن يعقد قراءتها وفهمها. في معظم الحالات ، يكون هذا بسبب أننا نميل إلى النظر في نوع المتغير كمعلومات أولية ، واسمه ثانوي. على الرغم من أنه ينبغي أن يكون عكس ذلك تماما.
مثال 1:
ربما يوافق الكثيرون على أن أسماء المتغيرات المحلية قصيرة للغاية في المثال أدناه:
عند استخدام أسماء قصيرة ، مع var ، تصبح الشفرة أقل وضوحًا:
أكثر خيار مفضل:
مثال 2:
تجنب تسمية المتغيرات مثل هذا:
استخدم أسماء أكثر أهمية:
مثال 3:
في محاولة لإعطاء أسماء أكثر قابلية للفهم للمتغيرات المحلية ، لا تذهب إلى الحدود القصوى:
بدلاً من ذلك ، يمكنك استخدام خيار أقصر ، ولكن ليس أقل قابلية للفهم:
هل تعلم أن جافا لديه فئة داخلية اسمه:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
حسنًا ، يمكن أن تكون تسمية المتغيرات بهذا النوع صعبة :)
النقطة 2: استخدم الحرفي للمساعدة في تحديد نوع البدائية (int ، long ، float ، double)
بدون استخدام الحرف في الأنواع البدائية ، قد نجد أن الأنواع المتوقعة والضمنية قد تختلف. يحدث هذا بسبب تحويل النوع الضمني الذي تستخدمه المتغيرات var .
على سبيل المثال ، شظايا التعليمات البرمجية جهازي التالية تتصرف كما هو متوقع. هنا نعلن صراحة أنواع منطقية وشار :
boolean flag = true;
الآن نستخدم var ، بدلاً من الإعلان بوضوح عن الأنواع:
var flag = true;
جيد حتى الان افعل الآن نفس الشيء مع الأنواع int و long و float و double :
int intNumber = 20;
على الرغم من أن مقتطف الشفرة أعلاه بسيط ومباشر ، دعونا الآن نستخدم var بدلاً من تحديد الأنواع بوضوح.
تجنب:
سيتم إخراج المتغيرات الأربعة كما int . لإصلاح هذا السلوك ، نحتاج إلى استخدام حرفية Java:
ولكن ماذا يحدث إذا أعلنا رقم عشري؟
تجنب ذلك إذا كنت تتوقع الحصول على متغير من النوع float :
لتجنب المفاجأة ، استخدم الحرفي المناسب:
النقطة 3: في بعض الحالات var ويمكن تحويلات الكتابة الضمنية تبسيط دعم التعليمات البرمجية
على سبيل المثال ، لنفترض أن الكود لدينا هو بين طريقتين. تحصل إحدى الطرق على عربة تسوق بمنتجات مختلفة وتحسب أفضل سعر. للقيام بذلك ، يقارن الأسعار المختلفة في السوق ويعيد السعر الإجمالي في شكل نوع عائم . طريقة أخرى تقتطع ببساطة هذا السعر من البطاقة.
أولاً ، دعونا نلقي نظرة على الطريقة التي تحسب أفضل سعر:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
ثانياً ، دعونا نلقي نظرة على الطريقة التي تعمل مع الخريطة:
public boolean debitCard(float amount, ...) { ... }
الآن نضع رمزنا بين هاتين الطريقتين للخدمة الخارجية كعميل. يمكن للمستخدمين اختيار السلع المراد شراؤها ، ونحسب أفضل سعر لها ، ثم نقوم بشطب الأموال من البطاقة:
بعد مرور بعض الوقت ، تقرر الشركة التي تمتلك واجهة برمجة التطبيقات (API) التخلي عن التمثيل المادي للأسعار لصالح العلامة العشرية (بدلاً من التعويم ، يتم استخدام int الآن). لذلك ، قاموا بتعديل رمز API على النحو التالي:
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
الحقيقة هي أن الكود الخاص بنا يستخدم إعلانًا صريحًا عن متغير تعويم كسعر. في شكله الحالي ، سوف نتلقى خطأ في وقت الترجمة. ولكن إذا توقعنا مثل هذا الموقف واستخدمنا var بدلاً من float ، فسيستمر عمل الكود لدينا دون مشاكل ، وذلك بفضل تحويل النوع الضمني:
النقطة 4: عندما لا تكون القيم الحرفية حلاً مناسبًا ، فاستخدم صبًا صريحًا أو تجاهل
لا تحتوي بعض الأنواع البدائية في Java على حرفية خاصة ، على سبيل المثال ، أنواع البايت والأنواع القصيرة . في هذه الحالة ، باستخدام تعيين النوع الصريح ، يمكننا إنشاء متغيرات دون أي مشاكل.
استخدم هذا بدلاً من var :
ولكن لماذا في هذه الحالة تعطي الأفضلية لتدوين الكتابة الصريح بدلاً من مجرد استخدام var ؟ حسنًا ، دعنا نكتب هذا الكود باستخدام var . لاحظ أنه في كلتا الحالتين ، سوف يفترض المترجم أنك تحتاج إلى متغيرات type int .
تجنب هذا الخطأ:
لا توجد هنا حرفية من شأنها أن تساعدنا ، لذلك نحن مضطرون لاستخدام تحويل صريح إلى أسفل نوع. شخصيا ، سأتجنب مثل هذه الحالات ، لأنني لا أرى أي مزايا هنا.
استخدم هذا الإدخال فقط إذا كنت تريد حقًا استخدام var :
ميزة استخدام var هي كتابة رمز أكثر إيجازًا. على سبيل المثال ، في حالة استخدام المنشئات ، يمكننا تجنب الحاجة إلى تكرار اسم الفئة ، وبالتالي ، التخلص من التكرار في الكود.
تجنب ما يلي:
استخدم بدلاً من ذلك:
بالنسبة للبناء أدناه ، ستكون var أيضًا وسيلة جيدة لتبسيط الكود دون فقد المعلومات.
تجنب:
استخدم الكود التالي:
لذا ، لماذا نحن أكثر راحة في العمل مع var في الأمثلة المقدمة؟ لأن كل المعلومات الضرورية موجودة في أسماء المتغيرات. ولكن إذا كان var ، بالاقتران مع اسم متغير ، يقلل من وضوح الكود ، فمن الأفضل رفض استخدامه.
تجنب:
استخدام:
النظر ، على سبيل المثال ، استخدام فئة java.nio.channels.Selector
. تحتوي هذه الفئة على طريقة open()
ثابتة تقوم بإرجاع محدد جديد open()
. ولكن هنا يمكنك بسهولة التفكير في أن طريقة Selector.open()
يمكنها إرجاع نوع منطقي ، اعتمادًا على نجاح فتح محدد موجود ، أو حتى إرجاع فراغ . باستخدام var هنا سيؤدي إلى فقدان المعلومات والارتباك في التعليمات البرمجية.
النقطة 6: يضمن var نوع الأمان وقت الترجمة
هذا يعني أنه لا يمكننا ترجمة تطبيق يحاول أداء مهام غير صحيحة. على سبيل المثال ، لا يتم تجميع التعليمات البرمجية أدناه:
ولكن هذا واحد يجمع:
var items = 10; items = 20;
ويجمع هذا الرمز بنجاح:
var items = "10"; items = "10 items";
بمجرد أن يحدد المترجم قيمة متغير var ، لا يمكننا تعيين أي شيء آخر غير هذا النوع.
النقطة 7: لا يمكن استخدام var لإنشاء نوع معين وتعيينه لمتغير نوع الواجهة
في Java ، نستخدم نهج "البرمجة مع واجهات". على سبيل المثال ، نقوم بإنشاء مثيل لفئة ArrayList ، ونربطه بالتجريد (واجهة):
List<String> products = new ArrayList<>();
ونتجنب أشياء مثل ربط كائن بمتغير من نفس النوع:
ArrayList<String> products = new ArrayList<>();
هذه هي الممارسة الأكثر شيوعًا والمرغوب فيها ، حيث يمكننا بسهولة استبدال تطبيق الواجهة بأي تطبيق آخر. لهذا ، من الضروري فقط الإعلان عن متغير نوع الواجهة.
لن نتمكن من اتباع هذا المفهوم باستخدام المتغيرات var ، كما دائما يتم عرض نوع معين لهم. على سبيل المثال ، في مقتطف الشفرة التالي ، سيحدد المترجم نوع المتغير كـ ArrayList<String>
:
var productList = new ArrayList<String>();
هناك العديد من وسائط الدفاع التي تشرح هذا السلوك:
يتم استخدام var للمتغيرات المحلية ، حيث ، في معظم الحالات ، يتم استخدام البرمجة باستخدام الواجهات أقل من الحالات التي يتم فيها إرجاع معلمات الطريقة حسب القيم أو الحقول
يجب أن يكون نطاق المتغيرات المحلية صغيرًا ، لذا لا ينبغي أن يكون حل المشكلات الناتجة عن التبديل إلى تطبيق آخر أمرًا صعبًا للغاية
var يعامل الكود الموجود على اليمين باعتباره المُهيئ المستخدم لتحديد النوع الفعلي. إذا تم تغيير المُهيئ ، في مرحلة ما ، فيمكن أيضًا تغيير النوع الذي يتم تعريفه ، مما يتسبب في حدوث مشاكل في الكود الذي يعتمد على هذا المتغير.
النقطة 8: احتمال وجود نوع غير متوقع من الاستدلال
قد يؤدي استخدام var في تركيبة مع عامل تشغيل الماس (<>) في حالة عدم وجود معلومات لتحديد النوع إلى نتائج غير متوقعة.
قبل استخدام Java 7 ، تم استخدام الاستدلال الصريح على الكتابة للمجموعات:
بدءاً من Java 7 ، تم تقديم مشغل الماس . في هذه الحالة ، سيقوم المترجم باستنتاج النوع الضروري بشكل مستقل:
ما نوع سيتم إخراج في التعليمات البرمجية أدناه؟
يجب تجنب مثل هذه الإنشاءات:
سيتم تعريف النوع بأنه ArrayList<Object>
. وذلك لأن المعلومات اللازمة لتحديد النوع بشكل صحيح غير متوفرة. هذا يؤدي إلى حقيقة أنه سيتم اختيار أقرب نوع ، والتي يمكن أن تكون متوافقة مع سياق ما يحدث. في هذه الحالة ، Object
.
وبالتالي ، لا يمكن استخدام var إلا إذا قدمنا المعلومات اللازمة لتحديد النوع المتوقع. يمكن تحديد النوع مباشرة أو تمريره كوسيطة.
تحديد نوع مباشرة:
تمرير وسيطات من النوع المطلوب:
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack);
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2);
البند 9: تعيين صفيف لمتغير var لا يتطلب أقواس []
نعلم جميعًا كيفية الإعلان عن المصفوفات في Java:
int[] numbers = new int[5];
ماذا عن استخدام var عند العمل مع المصفوفات؟ في هذه الحالة ، ليست هناك حاجة لاستخدام الأقواس على الجانب الأيسر.
تجنب ما يلي (هذا حتى لا ترجمة):
استخدام:
فشل التعليمات البرمجية أدناه باستخدام var أيضًا في الترجمة. هذا لأنه لا يمكن للمترجم تحديد النوع من الجانب الأيمن:
البند 10: لا يمكن استخدام var عند التصريح عن متغيرات متعددة على نفس السطر
إذا كنت ترغب في الإعلان عن متغيرات من نفس النوع مرة واحدة ، فأنت بحاجة إلى معرفة أن var غير مناسب لذلك. لا يتم ترجمة التعليمات البرمجية التالية:
استخدم بدلاً من ذلك:
أم هو:
النقطة 11: يجب أن تسعى المتغيرات المحلية إلى تقليل نطاقها. نوع var يعزز هذا البيان.
حافظ على نطاق صغير للمتغيرات المحلية - أنا متأكد من أنك سمعت هذا البيان قبل var .
تعد قابلية القراءة وإصلاح الأخطاء السريعة حججًا لصالح هذا النهج. على سبيل المثال ، دعنا نعرّف المكدس كما يلي:
تجنب هذا:
لاحظ أننا نقوم باستدعاء طريقة forEach()
الموروثة من java.util.Vector
. هذه الطريقة سوف تمر عبر المكدس مثل أي ناقل آخر وهذا ما نحتاجه. ولكن الآن قررنا استخدام ArrayDeque
بدلاً من Stack
. عند قيامنا بذلك ، forEach()
طريقة forEach()
تطبيقًا من ArrayDeque يتخطى المكدس باعتباره مكدسًا قياسيًا (LIFO)
هذا ليس ما نريد. من الصعب للغاية تتبع الخطأ هنا ، لأن الكود الذي يحتوي على الجزء forEach()
غير موجود بجوار الكود الذي تم إجراء التغييرات عليه. لزيادة سرعة البحث وإصلاح الأخطاء ، من الأفضل بكثير كتابة التعليمات البرمجية باستخدام متغير stack
، في أقرب وقت ممكن من إعلان هذا المتغير.
من الأفضل القيام بذلك على النحو التالي:
الآن ، عندما ArrayQueue
المطور من Stack
إلى ArrayQueue
، سيكون قادرًا على ملاحظة الخطأ بسرعة وإصلاحه.
البند 12: يسهل نوع var استخدام الأنواع المختلفة في العوامل الثلاثية
يمكننا استخدام أنواع مختلفة من المعاملات على الجانب الأيمن من المشغل الثلاثي.
عند تحديد أنواع بشكل صريح ، لا يتم ترجمة التعليمات البرمجية التالية:
ومع ذلك ، يمكننا أن نفعل هذا:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
التعليمات البرمجية أدناه أيضًا لا يتم تجميع:
ولكن يمكنك استخدام أنواع أكثر عمومية:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
في جميع هذه الحالات ، من الأفضل تفضيل var :
لا يتبع من هذه الأمثلة أن نوع var يعرّف أنواع الكائنات في وقت التشغيل. هذا ليس كذلك!
وبالطبع ، فإن النوع var سوف يعمل بشكل صحيح مع نفس النوعين من المعاملتين:
النقطة 13: يمكن استخدام النوع var داخل الحلقات
يمكننا بسهولة استبدال الإعلان الصريح عن الأنواع في الحلقات بنوع var .
تغيير نوع int صريح إلى var :
تغيير نوع Order
الصريح إلى var :
List<Order> orderList = ...;
النقطة 14: var يعمل بشكل جيد مع التدفقات في Java 8
من السهل جدًا استخدام var من Java 10 مع التدفقات التي ظهرت في Java 8.
يمكنك ببساطة استبدال الإعلان الصريح للنوع Stream بـ var :
مثال 1:
مثال 2:
المادة 15: يمكن استخدام var عند التصريح عن المتغيرات المحلية التي تهدف إلى تقسيم سلاسل كبيرة من التعبيرات إلى أجزاء
تبدو التعبيرات مع الكثير من التعشيش مثيرة للإعجاب وعادة ما تبدو وكأنها نوع من الرموز الذكية والهامة. في الحالة التي يكون فيها من الضروري تسهيل قراءة التعليمات البرمجية ، يوصى بتقسيم تعبير كبير باستخدام المتغيرات المحلية. لكن في بعض الأحيان ، يبدو أن كتابة الكثير من المتغيرات المحلية مهمة مرهقة للغاية وأود أن أتجنبها.
مثال على تعبير كبير:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
أفضل تقسيم التعليمات البرمجية إلى أجزاء المكونة:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
يبدو الإصدار الثاني من الكود أكثر قابلية للقراءة وأكثر بساطة ، لكن الإصدار الأول له أيضًا الحق في الوجود. من الطبيعي تمامًا أن يتكيف تفكيرنا مع فهم مثل هذه التعبيرات الكبيرة ويفضلها على المتغيرات المحلية. ومع ذلك ، يمكن أن يساعد استخدام نوع var في تفتيت الهياكل الكبيرة عن طريق تقليل الجهد المبذول لإعلان المتغيرات المحلية:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
الفقرة 16: لا يمكن استخدام var كنوع إرجاع أو كنوع وسيطة طريقة
لن يتم تجميع مقتطفات الشفرة الموضحة أدناه.
باستخدام var كنوع الإرجاع:
باستخدام var كنوع من وسيطة الطريقة:
بند 17: يمكن تمرير المتغيرات المحلية من النوع var كمعلمات للطريقة أو يمكنهم أخذ القيمة التي تم إرجاعها بواسطة الطريقة
سيتم تجميع أجزاء التعليمات البرمجية التالية وستعمل بشكل صحيح:
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...;
:
public <A, B> B contains(A container, B tocontain) { ... } var order = ...;
18: var
:
public interface Weighter { int getWeight(Product product); }
var :
public interface Weighter { int getWeight(Product product); }
19: var effectively final
, :
… Java SE 8, , final effectively final. , , effectively final .
, var effectively final. .
:
public interface Weighter { int getWeight(Product product); }
:
public interface Weighter { int getWeight(Product product); }
20: var- final-
var ( , effectively final). , final .
:
:
21:
var , . , var , :
:
Java 11 var - . Java 11:
22: var null'
var - .
( null ):
( ):
:
23: var
var , .
:
:
24: var catch
, try-with-resources
catch
, , .
:
:
Try-with-resources
, var try-with-resources .
, :
var :
25: var
, :
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
, var , T var :
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
, var :
codepublic <T extends Number> T add(T t) { List<T> numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5");
List<T> var :
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>();
26: var Wildcards (?),
? Wildcards
var :
Foo<?> var , , var .
, , , , . , ArrayList , Collection<?> :
(Foo <? extends T>) (Foo <? super T>)
, :
, , :
var :
, . – :
الخاتمة
« var », Java 10. , . , var , .
var Java!