26 توصية لاستخدام نوع var في Java


تمت إضافة 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:


ربما يوافق الكثيرون على أن أسماء المتغيرات المحلية قصيرة للغاية في المثال أدناه:


 // HAVING public boolean callDocumentationTask() { DocumentationTool dtl = ToolProvider.getSystemDocumentationTool(); DocumentationTask dtt = dtl.getTask(...); return dtt.call(); } 

عند استخدام أسماء قصيرة ، مع var ، تصبح الشفرة أقل وضوحًا:


 // AVOID public boolean callDocumentationTask() { var dtl = ToolProvider.getSystemDocumentationTool(); var dtt = dtl.getTask(...); return dtt.call(); } 

أكثر خيار مفضل:


 // PREFER public boolean callDocumentationTask() { var documentationTool = ToolProvider.getSystemDocumentationTool(); var documentationTask = documentationTool.getTask(...); return documentationTask.call(); } 

مثال 2:


تجنب تسمية المتغيرات مثل هذا:


 // AVOID public List<Product> fetchProducts(long userId) { var u = userRepository.findById(userId); var p = u.getCart(); return p; } 

استخدم أسماء أكثر أهمية:


 // PREFER public List<Product> fetchProducts(long userId) { var user = userRepository.findById(userId); var productList = user.getCart(); return productList; } 

مثال 3:


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


 // AVOID var byteArrayOutputStream = new ByteArrayOutputStream(); 

بدلاً من ذلك ، يمكنك استخدام خيار أقصر ، ولكن ليس أقل قابلية للفهم:


 // PREFER var outputStream = new ByteArrayOutputStream(); // or var outputStreamOfFoo = new ByteArrayOutputStream(); 

هل تعلم أن جافا لديه فئة داخلية اسمه:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState


حسنًا ، يمكن أن تكون تسمية المتغيرات بهذا النوع صعبة :)


النقطة 2: استخدم الحرفي للمساعدة في تحديد نوع البدائية (int ، long ، float ، double)


بدون استخدام الحرف في الأنواع البدائية ، قد نجد أن الأنواع المتوقعة والضمنية قد تختلف. يحدث هذا بسبب تحويل النوع الضمني الذي تستخدمه المتغيرات var .


على سبيل المثال ، شظايا التعليمات البرمجية جهازي التالية تتصرف كما هو متوقع. هنا نعلن صراحة أنواع منطقية وشار :


 boolean flag = true; // this is of type boolean char a = 'a'; // this is of type char 

الآن نستخدم var ، بدلاً من الإعلان بوضوح عن الأنواع:


 var flag = true; // this is inferred as boolean var a = 'a'; // this is inferred as char 

جيد حتى الان افعل الآن نفس الشيء مع الأنواع int و long و float و double :


 int intNumber = 20; // this is of type int long longNumber = 20; // this is of type long float floatNumber = 20; // this is of type float, 20.0 double doubleNumber = 20; // this is of type double, 20.0 

على الرغم من أن مقتطف الشفرة أعلاه بسيط ومباشر ، دعونا الآن نستخدم var بدلاً من تحديد الأنواع بوضوح.


تجنب:


 // AVOID var intNumber = 20; // this is inferred as int var longNumber = 20; // this is inferred as int var floatNumber = 20; // this is inferred as int var doubleNumber = 20; // this is inferred as int 

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


 // PREFER var intNumber = 20; // this is inferred as int var longNumber = 20L; // this is inferred as long var floatNumber = 20F; // this is inferred as float, 20.0 var doubleNumber = 20D; // this is inferred as double, 20.0 

ولكن ماذا يحدث إذا أعلنا رقم عشري؟


تجنب ذلك إذا كنت تتوقع الحصول على متغير من النوع float :


 // AVOID, IF THIS IS A FLOAT var floatNumber = 20.5; // this is inferred as double 

لتجنب المفاجأة ، استخدم الحرفي المناسب:


 // PREFER, IF THIS IS A FLOAT var floatNumber = 20.5F; // this is inferred as float 

النقطة 3: في بعض الحالات var ويمكن تحويلات الكتابة الضمنية تبسيط دعم التعليمات البرمجية


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


أولاً ، دعونا نلقي نظرة على الطريقة التي تحسب أفضل سعر:


 public float computeBestPrice(String[] items) { ... float price = ...; return price; } 

ثانياً ، دعونا نلقي نظرة على الطريقة التي تعمل مع الخريطة:


 public boolean debitCard(float amount, ...) { ... } 

الآن نضع رمزنا بين هاتين الطريقتين للخدمة الخارجية كعميل. يمكن للمستخدمين اختيار السلع المراد شراؤها ، ونحسب أفضل سعر لها ، ثم نقوم بشطب الأموال من البطاقة:


 // AVOID public void purchaseCart(long customerId) { ... float price = computeBestPrice(...); debitCard(price, ...); } 

بعد مرور بعض الوقت ، تقرر الشركة التي تمتلك واجهة برمجة التطبيقات (API) التخلي عن التمثيل المادي للأسعار لصالح العلامة العشرية (بدلاً من التعويم ، يتم استخدام int الآن). لذلك ، قاموا بتعديل رمز API على النحو التالي:


 public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... } 

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


 // PREFER public void purchaseCart(long customerId) { ... var price = computeBestPrice(...); debitCard(price, ...); } 

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


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


استخدم هذا بدلاً من var :


 // PREFER THIS INSTEAD OF USING VAR byte byteNumber = 45; // this is of type byte short shortNumber = 4533; // this is of type short 

ولكن لماذا في هذه الحالة تعطي الأفضلية لتدوين الكتابة الصريح بدلاً من مجرد استخدام var ؟ حسنًا ، دعنا نكتب هذا الكود باستخدام var . لاحظ أنه في كلتا الحالتين ، سوف يفترض المترجم أنك تحتاج إلى متغيرات type int .


تجنب هذا الخطأ:


 // AVOID var byteNumber = 45; // this is inferred as int var shortNumber = 4533; // this is inferred as int 

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


استخدم هذا الإدخال فقط إذا كنت تريد حقًا استخدام var :


 // PREFER THIS ONLY IF YOU WANT TO USE VAR var byteNumber = (byte) 45; // this is inferred as byte var shortNumber = (short) 4533; // this is inferred as short 

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


ميزة استخدام var هي كتابة رمز أكثر إيجازًا. على سبيل المثال ، في حالة استخدام المنشئات ، يمكننا تجنب الحاجة إلى تكرار اسم الفئة ، وبالتالي ، التخلص من التكرار في الكود.


تجنب ما يلي:


 // AVOID MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(...); 

استخدم بدلاً من ذلك:


 // PREFER var inputStream = new MemoryCacheImageInputStream(...); 

بالنسبة للبناء أدناه ، ستكون var أيضًا وسيلة جيدة لتبسيط الكود دون فقد المعلومات.


تجنب:


 // AVOID JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fm = compiler.getStandardFileManager(...); 

استخدم الكود التالي:


 // PREFER var compiler = ToolProvider.getSystemJavaCompiler(); var fileManager = compiler.getStandardFileManager(...); 

لذا ، لماذا نحن أكثر راحة في العمل مع var في الأمثلة المقدمة؟ لأن كل المعلومات الضرورية موجودة في أسماء المتغيرات. ولكن إذا كان var ، بالاقتران مع اسم متغير ، يقلل من وضوح الكود ، فمن الأفضل رفض استخدامه.


تجنب:


 // AVOID public File fetchCartContent() { return new File(...); } // As a human, is hard to infer the "cart" type without // inspecting the fetchCartContent() method var cart = fetchCartContent(); 

استخدام:


 // PREFER public File fetchCartContent() { return new File(...); } File cart = fetchCartContent(); 

النظر ، على سبيل المثال ، استخدام فئة java.nio.channels.Selector . تحتوي هذه الفئة على طريقة open() ثابتة تقوم بإرجاع محدد جديد open() . ولكن هنا يمكنك بسهولة التفكير في أن طريقة Selector.open() يمكنها إرجاع نوع منطقي ، اعتمادًا على نجاح فتح محدد موجود ، أو حتى إرجاع فراغ . باستخدام var هنا سيؤدي إلى فقدان المعلومات والارتباك في التعليمات البرمجية.


النقطة 6: يضمن var نوع الأمان وقت الترجمة


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


 // IT DOESN'T COMPILE var items = 10; items = "10 items"; // incompatible types: String cannot be converted to int 

ولكن هذا واحد يجمع:


 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>(); // inferred as ArrayList<String> 

هناك العديد من وسائط الدفاع التي تشرح هذا السلوك:


  • يتم استخدام var للمتغيرات المحلية ، حيث ، في معظم الحالات ، يتم استخدام البرمجة باستخدام الواجهات أقل من الحالات التي يتم فيها إرجاع معلمات الطريقة حسب القيم أو الحقول


  • يجب أن يكون نطاق المتغيرات المحلية صغيرًا ، لذا لا ينبغي أن يكون حل المشكلات الناتجة عن التبديل إلى تطبيق آخر أمرًا صعبًا للغاية


  • var يعامل الكود الموجود على اليمين باعتباره المُهيئ المستخدم لتحديد النوع الفعلي. إذا تم تغيير المُهيئ ، في مرحلة ما ، فيمكن أيضًا تغيير النوع الذي يتم تعريفه ، مما يتسبب في حدوث مشاكل في الكود الذي يعتمد على هذا المتغير.



النقطة 8: احتمال وجود نوع غير متوقع من الاستدلال


قد يؤدي استخدام var في تركيبة مع عامل تشغيل الماس (<>) في حالة عدم وجود معلومات لتحديد النوع إلى نتائج غير متوقعة.


قبل استخدام Java 7 ، تم استخدام الاستدلال الصريح على الكتابة للمجموعات:


 // explicitly specifying generic class's instantiation parameter type List<String> products = new ArrayList<String>(); 

بدءاً من Java 7 ، تم تقديم مشغل الماس . في هذه الحالة ، سيقوم المترجم باستنتاج النوع الضروري بشكل مستقل:


 // inferring generic class's instantiation parameter type List<String> products = new ArrayList<>(); 

ما نوع سيتم إخراج في التعليمات البرمجية أدناه؟


يجب تجنب مثل هذه الإنشاءات:


 // AVOID var productList = new ArrayList<>(); // is inferred as ArrayList<Object> 

سيتم تعريف النوع بأنه ArrayList<Object> . وذلك لأن المعلومات اللازمة لتحديد النوع بشكل صحيح غير متوفرة. هذا يؤدي إلى حقيقة أنه سيتم اختيار أقرب نوع ، والتي يمكن أن تكون متوافقة مع سياق ما يحدث. في هذه الحالة ، Object .


وبالتالي ، لا يمكن استخدام var إلا إذا قدمنا ​​المعلومات اللازمة لتحديد النوع المتوقع. يمكن تحديد النوع مباشرة أو تمريره كوسيطة.


تحديد نوع مباشرة:


 // PREFER var productList = new ArrayList<String>(); // inferred as ArrayList<String> 

تمرير وسيطات من النوع المطلوب:


 var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack); // inferred as ArrayList<String> 

 Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2); // inferred as List<Product> // DON'T DO THIS var listofProduct = List.of(); // inferred as List<Object> listofProduct.add(p1); listofProduct.add(p2); 

البند 9: تعيين صفيف لمتغير var لا يتطلب أقواس []


نعلم جميعًا كيفية الإعلان عن المصفوفات في Java:


 int[] numbers = new int[5]; // or, less preferred int numbers[] = new int[5]; 

ماذا عن استخدام var عند العمل مع المصفوفات؟ في هذه الحالة ، ليست هناك حاجة لاستخدام الأقواس على الجانب الأيسر.


تجنب ما يلي (هذا حتى لا ترجمة):


 // IT DOESN'T COMPILE var[] numbers = new int[5]; // or var numbers[] = new int[5]; 

استخدام:


 // PREFER var numbers = new int[5]; // inferred as array of int numbers[0] = 2; // work numbers[0] = 2.2; // doesn't work numbers[0] = "2"; // doesn't work 

فشل التعليمات البرمجية أدناه باستخدام var أيضًا في الترجمة. هذا لأنه لا يمكن للمترجم تحديد النوع من الجانب الأيمن:


 // explicit type work as expected int[] numbers = {1, 2, 3}; // IT DOESN'T COMPILE var numbers = {1, 2, 3}; var numbers[] = {1, 2, 3}; var[] numbers = {1, 2, 3}; 

البند 10: لا يمكن استخدام var عند التصريح عن متغيرات متعددة على نفس السطر


إذا كنت ترغب في الإعلان عن متغيرات من نفس النوع مرة واحدة ، فأنت بحاجة إلى معرفة أن var غير مناسب لذلك. لا يتم ترجمة التعليمات البرمجية التالية:


 // IT DOESN'T COMPILE // error: 'var' is not allowed in a compound declaration var hello = "hello", bye = "bye", welcome = "welcome"; 

استخدم بدلاً من ذلك:


 // PREFER String hello = "hello", bye = "bye", welcome = "welcome"; 

أم هو:


 // PREFER var hello = "hello"; var bye = "bye"; var welcome = "welcome"; 

النقطة 11: يجب أن تسعى المتغيرات المحلية إلى تقليل نطاقها. نوع var يعزز هذا البيان.


حافظ على نطاق صغير للمتغيرات المحلية - أنا متأكد من أنك سمعت هذا البيان قبل var .


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


تجنب هذا:


 // AVOID ... var stack = new Stack<String>(); stack.push("George"); stack.push("Tyllen"); stack.push("Martin"); stack.push("Kelly"); ... // 50 lines of code that doesn't use stack // George, Tyllen, Martin, Kelly stack.forEach(...); ... 

لاحظ أننا نقوم باستدعاء طريقة forEach() الموروثة من java.util.Vector . هذه الطريقة سوف تمر عبر المكدس مثل أي ناقل آخر وهذا ما نحتاجه. ولكن الآن قررنا استخدام ArrayDeque بدلاً من Stack . عند قيامنا بذلك ، forEach() طريقة forEach() تطبيقًا من ArrayDeque يتخطى المكدس باعتباره مكدسًا قياسيًا (LIFO)


 // AVOID ... var stack = new ArrayDeque<String>(); stack.push("George"); stack.push("Tyllen"); stack.push("Martin"); stack.push("Kelly"); ... // 50 lines of code that doesn't use stack // Kelly, Martin, Tyllen, George stack.forEach(...); ... 

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


من الأفضل القيام بذلك على النحو التالي:


 // PREFER ... var stack = new Stack<String>(); stack.push("George"); stack.push("Tyllen"); stack.push("Martin"); stack.push("Kelly"); ... // George, Tyllen, Martin, Kelly stack.forEach(...); ... // 50 lines of code that doesn't use stack 

الآن ، عندما ArrayQueue المطور من Stack إلى ArrayQueue ، سيكون قادرًا على ملاحظة الخطأ بسرعة وإصلاحه.


البند 12: يسهل نوع var استخدام الأنواع المختلفة في العوامل الثلاثية


يمكننا استخدام أنواع مختلفة من المعاملات على الجانب الأيمن من المشغل الثلاثي.


عند تحديد أنواع بشكل صريح ، لا يتم ترجمة التعليمات البرمجية التالية:


 // IT DOESN'T COMPILE List code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); // or Set code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); 

ومع ذلك ، يمكننا أن نفعل هذا:


 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); 

التعليمات البرمجية أدناه أيضًا لا يتم تجميع:


 // IT DOESN'T COMPILE int code = intOrString ? 12112 : "12112"; String code = intOrString ? 12112 : "12112"; 

ولكن يمكنك استخدام أنواع أكثر عمومية:


 Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112"; 

في جميع هذه الحالات ، من الأفضل تفضيل var :


 // PREFER // inferred as Collection<Integer> var code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); // inferred as Serializable var code = intOrString ? 12112 : "12112"; 

لا يتبع من هذه الأمثلة أن نوع var يعرّف أنواع الكائنات في وقت التشغيل. هذا ليس كذلك!


وبالطبع ، فإن النوع var سوف يعمل بشكل صحيح مع نفس النوعين من المعاملتين:


 // inferred as float var code = oneOrTwoDigits ? 1211.2f : 1211.25f; 

النقطة 13: يمكن استخدام النوع var داخل الحلقات


يمكننا بسهولة استبدال الإعلان الصريح عن الأنواع في الحلقات بنوع var .


تغيير نوع int صريح إلى var :


 // explicit type for (int i = 0; i < 5; i++) { ... } // using var for (var i = 0; i < 5; i++) { // i is inferred of type int ... } 

تغيير نوع Order الصريح إلى var :


 List<Order> orderList = ...; // explicit type for (Order order : orderList) { ... } // using var for (var order : orderList) { // order type is inferred as Order ... } 

النقطة 14: var يعمل بشكل جيد مع التدفقات في Java 8


من السهل جدًا استخدام var من Java 10 مع التدفقات التي ظهرت في Java 8.


يمكنك ببساطة استبدال الإعلان الصريح للنوع Stream بـ var :


مثال 1:


 // explicit type Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5); numbers.filter(t -> t % 2 == 0).forEach(System.out::println); // using var var numbers = Stream.of(1, 2, 3, 4, 5); // inferred as Stream<Integer> numbers.filter(t -> t % 2 == 0).forEach(System.out::println); 

مثال 2:


 // explicit types Stream<String> paths = Files.lines(Path.of("...")); List<File> files = paths.map(p -> new File(p)).collect(toList()); // using var var paths = Files.lines(Path.of("...")); // inferred as Stream<String> var files = paths.map(p -> new File(p)).collect(toList()); // inferred as List<File> 

المادة 15: يمكن استخدام var عند التصريح عن المتغيرات المحلية التي تهدف إلى تقسيم سلاسل كبيرة من التعبيرات إلى أجزاء


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


مثال على تعبير كبير:


 List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5); // AVOID int result = intList.stream() .collect(Collectors.partitioningBy(i -> i % 2 == 0)) .values() .stream() .max(Comparator.comparing(List::size)) .orElse(Collections.emptyList()) .stream() .mapToInt(Integer::intValue) .sum(); 

أفضل تقسيم التعليمات البرمجية إلى أجزاء المكونة:


 List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5); // PREFER Map<Boolean, List<Integer>> evenAndOdd = intList.stream() .collect(Collectors.partitioningBy(i -> i % 2 == 0)); Optional<List<Integer>> evenOrOdd = evenAndOdd.values() .stream() .max(Comparator.comparing(List::size)); int sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList()) .stream() .mapToInt(Integer::intValue) .sum(); 

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


 var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5); // PREFER var evenAndOdd = intList.stream() .collect(Collectors.partitioningBy(i -> i % 2 == 0)); var evenOrOdd = evenAndOdd.values() .stream() .max(Comparator.comparing(List::size)); var sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList()) .stream() .mapToInt(Integer::intValue) .sum(); 

الفقرة 16: لا يمكن استخدام var كنوع إرجاع أو كنوع وسيطة طريقة


لن يتم تجميع مقتطفات الشفرة الموضحة أدناه.


باستخدام var كنوع الإرجاع:


 // IT DOESN'T COMPILE public var countItems(Order order, long timestamp) { ... } 

باستخدام var كنوع من وسيطة الطريقة:


 // IT DOESN'T COMPILE public int countItems(var order, var timestamp) { ... } 

بند 17: يمكن تمرير المتغيرات المحلية من النوع var كمعلمات للطريقة أو يمكنهم أخذ القيمة التي تم إرجاعها بواسطة الطريقة


سيتم تجميع أجزاء التعليمات البرمجية التالية وستعمل بشكل صحيح:


 public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...; // an Order instance var timestamp = ...; // a long representing a timestamp var itemsNr = countItems(order, timestamp); // inferred as int type ... } 

:


 public <A, B> B contains(A container, B tocontain) { ... } var order = ...; // Order instance var product = ...; // Product instance var resultProduct = contains(order, product); // inferred as Product type 

18: var


:


 public interface Weighter { int getWeight(Product product); } // AVOID Weighter weighter = new Weighter() { @Override public int getWeight(Product product) { ... } }; Product product = ...; // a Product instance int weight = weighter.getWeight(product); 

var :


 public interface Weighter { int getWeight(Product product); } // PREFER var weighter = new Weighter() { @Override public int getWeight(Product product) { ... } }; var product = ...; // a Product instance var weight = weighter.getWeight(product); 

19: var effectively final


, :


… Java SE 8, , final effectively final. , , effectively final .

, var effectively final. .


:


 public interface Weighter { int getWeight(Product product); } // AVOID int ratio = 5; // this is effectively final Weighter weighter = new Weighter() { @Override public int getWeight(Product product) { return ratio * ...; } }; ratio = 3; // this reassignment will cause error 

:


 public interface Weighter { int getWeight(Product product); } // PREFER var ratio = 5; // this is effectively final var weighter = new Weighter() { @Override public int getWeight(Product product) { return ratio * ...; } }; ratio = 3; // this reassignment will cause error 

20: var- final-


var ( , effectively final). , final .


:


 // AVOID // IT DOESN'T COMPILE public void discount(int price) { final int limit = 2000; final int discount = 5; if (price > limit) { discount++; // this reassignment will cause error, which is ok } } 

:


 // PREFER // IT DOESN'T COMPILE public void discount(int price) { final var limit = 2000; final var discount = 5; if (price > limit) { discount++; // this reassignment will cause error, which is ok } } 

21:


var , . , var , :


 // IT DOESN'T COMPILE // lambda expression needs an explicit target-type var f = x -> x + 1; // method reference needs an explicit target-type var exception = IllegalArgumentException::new; 

:


 // PREFER Function<Integer, Integer> f = x -> x + 1; Supplier<IllegalArgumentException> exception = IllegalArgumentException::new; 

Java 11 var - . Java 11:


 // Java 11 (var x, var y) -> x + y // or (@Nonnull var x, @Nonnull var y) -> x + y 

22: var null'


var - .


( null ):


 // IT DOESN'T COMPILE var message = null; // result in an error of type: variable initializer is 'null' 

( ):


 // IT DOESN'T COMPILE var message; // result in: cannot use 'var' on variable without initializer ... message = "hello"; 

:


 // PREFER String message = null; // or String message; ... message = "hello"; 

23: var


var , .


:


 // IT DOESN'T COMPILE public class Product { private var price; // error: 'var' is not allowed here private var name; // error: 'var' is not allowed here ... } 

:


 // PREFER public class Product { private int price; private String name; ... } 

24: var catch


, try-with-resources


catch


, , .


:


 // IT DOESN'T COMPILE try { TimeUnit.NANOSECONDS.sleep(5000); } catch (var ex) { ... } 

:


 // PREFER try { TimeUnit.NANOSECONDS.sleep(5000); } catch (InterruptedException ex) { ... } 

Try-with-resources


, var try-with-resources .


, :


 // explicit type try (PrintWriter writer = new PrintWriter(new File("welcome.txt"))) { writer.println("Welcome message"); } 

var :


 // using var try (var writer = new PrintWriter(new File("welcome.txt"))) { writer.println("Welcome message"); } 

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"); // error: incompatible types: String cannot be converted to T ... } 

List<T> var :


 public <T extends Number> T add(T t) { var numbers = new ArrayList<T>(); // DON'T DO THIS, DON'T FORGET THE, T var numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5"); // error: incompatible types: String cannot be converted to T ... } 

26: var Wildcards (?),


? Wildcards


var :


 // explicit type Class<?> clazz = Integer.class; // use var var clazz = Integer.class; 

Foo<?> var , , var .


, , , , . , ArrayList , Collection<?> :


 // explicit type Collection<?> stuff = new ArrayList<>(); stuff.add("hello"); // compile time error stuff.add("world"); // compile time error // use var, this will remove the error, but I don't think that this is // what you had in mind when you wrote the above code var stuff = new ArrayList<>(); strings.add("hello"); // no error strings.add("world"); // no error 

(Foo <? extends T>) (Foo <? super T>)


, :


 // explicit type Class<? extends Number> intNumber = Integer.class; Class<? super FilterReader> fileReader = Reader.class; 

, , :


 // IT DOESN'T COMPILE // error: Class<Reader> cannot be converted to Class<? extends Number> Class<? extends Number> intNumber = Reader.class; // error: Class<Integer> cannot be converted to Class<? super FilterReader> Class<? super FilterReader> fileReader = Integer.class; 

var :


 // using var var intNumber = Integer.class; var fileReader = Reader.class; 

, . – :


 // this will compile just fine var intNumber = Reader.class; var fileReader = Integer.class; 

الخاتمة


« var », Java 10. , . , var , .


var Java!

Source: https://habr.com/ru/post/ar438206/


All Articles