ليس
الإصدار البعيد
من Java هو
الإصدار الرابع عشر الجديد ، مما يعني أن الوقت قد حان للتعرف على ميزات بناء الجملة الجديدة التي سيتضمنها هذا الإصدار من Java. أحد هذه الاحتمالات النحوية هو
مطابقة الأنماط من النوع الذي سيتم تنفيذه باستخدام مشغل
instanceof
محسن (ممتد).
أود اليوم أن ألعب مع هذا المشغل الجديد وأنظر في ميزات عمله بمزيد من التفاصيل. نظرًا لأن مطابقة النمط حسب النوع لم تدخل بعد مستودع تخزين JDK الرئيسي ، اضطررت إلى تنزيل مستودع
مشروع Amber ، الذي يقوم بتطوير تصميمات بناء جملة Java جديدة ،
وتجميع JDK من هذا المخزون.
لذا ، فإن أول ما سنفعله هو التحقق من إصدار Java للتأكد من أننا نستخدم JDK 14 حقًا:
> java -version openjdk version "14-internal" 2020-03-17 OpenJDK Runtime Environment (build 14-internal+0-adhoc.osboxes.amber-amber) OpenJDK 64-Bit Server VM (build 14-internal+0-adhoc.osboxes.amber-amber, mixed mode, sharing)
هذا صحيح.
سنقوم الآن بكتابة جزء صغير من الشفرة مع عامل التشغيل "القديم" وتشغيله:
public class A { public static void main(String[] args) { new A().f("Hello, world!"); } public void f(Object obj) { if (obj instanceof String) { String str = (String) obj; System.out.println(str.toLowerCase()); } } }
> java A.java hello, world!
إنه يعمل. هذا هو الاختيار نوع قياسي تليها المدلى بها. نكتب إنشاءات مشابهة كل يوم ، بغض النظر عن إصدار Java الذي نستخدمه ، 1.0 على الأقل ، 13 على الأقل.
ولكن لدينا الآن Java 14 بين أيدينا ، ودعنا نعيد كتابة الكود باستخدام مُشغل
instanceof
محسن (سأحذف أسطر الكود المتكررة في المستقبل):
if (obj instanceof String str) { System.out.println(str.toLowerCase()); }
> java --enable-preview --source 14 A.java hello, world!
غرامة. الرمز أنظف ، أقصر وأكثر أمانًا وأكثر قابلية للقراءة. كان هناك ثلاثة تكرارات للكلمة سلسلة ، واحد أصبح. لاحظ أننا لم ننس تحديد الوسيطات
--enable-preview --source 14
، كما المشغل الجديد هو
ميزة المعاينة . بالإضافة إلى ذلك ، ربما لاحظ القارئ اليقظ أننا قمنا بتشغيل الملف المصدر A.java مباشرة ، دون تجميع.
ظهرت هذه الميزة في Java 11.
دعونا نحاول كتابة شيء أكثر تطوراً وإضافة شرط ثانٍ يستخدم المتغير المعلن للتو:
if (obj instanceof String str && str.length() > 5) { System.out.println(str.toLowerCase()); }
انه يجمع ويعمل. ولكن ماذا لو قمت بتبديل الشروط؟
if (str.length() > 5 && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: cannot find symbol if (str.length() > 5 && obj instanceof String str) { ^
خطأ في التجميع. ما هو متوقع: لم يتم الإعلان عن متغير
str
، مما يعني أنه لا يمكن استخدامه.
بالمناسبة ، ماذا عن التغير؟ هل المتغير نهائي أم لا؟ نحن نحاول:
if (obj instanceof String str) { str = "World, hello!"; System.out.println(str.toLowerCase()); }
A.java:8: error: pattern binding str may not be assigned str = "World, hello!"; ^
نعم ، المتغير النهائي. هذا يعني أن كلمة "متغير" ليست صحيحة تمامًا هنا. ويستخدم المترجم المصطلح الخاص "نمط الربط". لذلك ، أقترح من الآن فصاعدًا أن أقول ليس "متغير" ، ولكن "نمط الربط" (لسوء الحظ ، لم يتم ترجمة كلمة "ملزمة" جيدًا إلى اللغة الروسية).
مع قابلية التحويل والمصطلحات فرزها. دعنا نذهب التجربة أبعد من ذلك. ماذا لو تمكنا من "كسر" المترجم؟
ماذا لو قمت بتسمية متغير ونمط ربط بنفس الاسم؟
if (obj instanceof String obj) { System.out.println(obj.toLowerCase()); }
A.java:7: error: variable obj is already defined in method f(Object) if (obj instanceof String obj) { ^
هو منطقي. تداخل متغير من النطاق الخارجي لا يعمل. هذا مكافئ كما لو أننا انتهينا للتو من تشغيل المتغير
obj
مرة أخرى في نفس النطاق.
وإذا كان الأمر كذلك:
if (obj instanceof String str && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: illegal attempt to redefine an existing match binding if (obj instanceof String str && obj instanceof String str) { ^
المترجم هو صلب مثل الخرسانة.
ماذا يمكنك أن تجرب؟ دعنا نلعب مع نطاقات. إذا تم تعريف الربط في الفرع
if
، فهل سيتم تعريفه في الفرع
else
إذا تم عكس الشرط؟
if (!(obj instanceof String str)) { System.out.println("not a string"); } else { System.out.println(str.toLowerCase()); }
لقد نجحت. المترجم ليس فقط موثوقة ، ولكن أيضا ذكية.
وإذا كان الأمر كذلك؟
if (obj instanceof String str && true) { System.out.println(str.toLowerCase()); }
انها عملت مرة أخرى. يفهم المترجم بشكل صحيح أن الشرط يتلخص في نسخة بسيطة من
obj instanceof String str
.
هل حقا لا يمكن "كسر" المترجم؟
ربما هكذا؟
if (obj instanceof String str || false) { System.out.println(str.toLowerCase()); }
A.java:8: error: cannot find symbol System.out.println(str.toLowerCase()); ^
آها! هذا بالفعل يبدو وكأنه خطأ. بعد كل شيء ، كل الشروط الثلاثة متساوية تماما:
obj instanceof String str
obj instanceof String str && true
obj instanceof String str || false
قواعد تحديد النطاق ، من ناحية أخرى ،
غير تافهة ، وربما لا ينبغي أن تعمل مثل هذه الحالة. لكن إذا نظرت بحتة من وجهة نظر إنسانية ، فأعتقد أن هذا خطأ.
لكن هيا ، لنجرب شيئًا آخر. هل هذا العمل:
if (!(obj instanceof String str)) { throw new RuntimeException(); } System.out.println(str.toLowerCase());
المترجمة. هذا جيد ، لأن هذا الرمز مكافئ لما يلي:
if (!(obj instanceof String str)) { throw new RuntimeException(); } else { System.out.println(str.toLowerCase()); }
وبما أن كلا الخيارين متساويان ، يتوقع المبرمج أن يعملا بنفس الطريقة.
ماذا عن الحقول المتداخلة؟
public class A { private String str; public void f(Object obj) { if (obj instanceof String str) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } } }
المترجم لم يقسم. هذا منطقي ، لأن المتغيرات المحلية يمكن أن تتداخل دائمًا مع الحقول. على ما يبدو ، قرروا أيضًا عدم إجراء استثناءات لربط النمط. من ناحية أخرى ، هذا الرمز هش إلى حد ما. خطوة واحدة مهمل ، وقد لا تلاحظ كيف تنكسر فرعك:
private boolean isOK() { return false; } public void f(Object obj) { if (obj instanceof String str || isOK()) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } }
يستخدم كلا الفرعين الآن حقل
str
، والذي قد لا يتوقعه مبرمج غير نشط. لاكتشاف مثل هذه الأخطاء في أقرب وقت ممكن ، استخدم عمليات التفتيش في IDE وتمييز بناء الجملة المختلف للحقول والمتغيرات. كما أوصي دائمًا باستخدام
this
التصفيات للحقول. هذا سيضيف المزيد من الموثوقية.
ماذا هو مثير للاهتمام؟ مثل
instanceof
"القديم" ، الجديد لا يطابق أبداً. هذا يعني أنه يمكنك دائمًا الاعتماد على حقيقة أن مجلدات النقش لا يمكن أن تكون أبدًا
null
:
if (obj instanceof String str) { System.out.println(str.toLowerCase());
بالمناسبة ، باستخدام هذه الخاصية ، يمكنك تقصير هذه السلاسل:
if (a != null) { B b = a.getB(); if (b != null) { C c = b.getC(); if (c != null) { System.out.println(c.getSize()); } } }
إذا كنت تستخدم
instanceof
، فيمكن إعادة كتابة الرمز أعلاه كما يلي:
if (a != null && a.getB() instanceof B b && b.getC() instanceof C c) { System.out.println(c.getSize()); }
اكتب في التعليقات ما رأيك في هذا النمط. هل تستخدم هذا المصطلح؟
ماذا عن الوراثة؟
import java.util.List; public class A { public static void main(String[] args) { new A().f(List.of(1, 2, 3)); } public void f(Object obj) { if (obj instanceof List<Integer> list) { System.out.println(list.size()); } } }
> java --enable-preview --source 14 A.java Note: A.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 3
شيق جدا إذا كان
instanceof
"القديم" يدعم فقط
instanceof List
أو
instanceof List<?>
، فستعمل القائمة الجديدة مع أي نوع معين. نحن في انتظار أن يقع أول شخص في هذا الفخ:
if (obj instanceof List<Integer> list) { System.out.println("Int list of size " + list.size()); } else if (obj instanceof List<String> list) { System.out.println("String list of size " + list.size()); }
لماذا هذا لا يعمل؟الجواب: عدم وجود الأدوية الوراثية في جاوة.
IMHO ، هذه مشكلة خطيرة جدا. من ناحية أخرى ، لا أعرف كيفية إصلاحها. يبدو أن عليك الاعتماد على عمليات التفتيش في IDE مرة أخرى.
النتائج
بشكل عام ، يعمل نوع مطابقة الأنماط الجديد بشكل رائع للغاية. يتيح لك مشغل
instanceof
المثنى ليس فقط اختبار النوع ، ولكن أيضًا يعلن عن المجلدات الجاهزة من هذا النوع ، مما يلغي الحاجة إلى الصب اليدوي. هذا يعني أنه سيكون هناك ضجيج أقل في الشفرة ، وسيكون من الأسهل للقارئ أن يميز المنطق المفيد. على سبيل المثال ، يمكن كتابة معظم تطبيقات
equals()
في سطر واحد:
public class Point { private final int x, y; … @Override public int hashCode() { return Objects.hash(x, y); } @Override public boolean equals(Object obj) { return obj instanceof Point p && px == this.x && py == this.y; } }
الرمز أعلاه يمكن كتابته حتى أقصر. كيف؟باستخدام
الإدخالات التي سيتم تضمينها أيضًا في Java 14. سنتحدث عنها في المرة القادمة.
من ناحية أخرى ، تثير العديد من النقاط المثيرة للجدل أسئلة صغيرة:
- قواعد نطاق غير شفافة تمامًا (مثال مع
instanceof || false
). - الحقول المتداخلة.
- instof و الوراثة.
ومع ذلك ، هذه هي نيتبيكس أكثر من ادعاءات خطيرة. الكل في الكل ، الفوائد الكبيرة لمشغل
instanceof
جديد تستحق بالتأكيد لغة الإضافة. وإذا كان لا يزال يترك حالة المعاينة ويصبح بناء جملة ثابتًا ، فسيكون دافعًا كبيرًا هو ترك Java 8 أخيرًا إلى الإصدار الجديد من Java.
ملاحظة: لدي
قناة في Telegram حيث أكتب عن أخبار Java. أنا أحثكم على الاشتراك فيه.