أحد عشر لآلئ خفية من جافا 11

لم يقدم Java 11 أي ميزات مبتكرة ، ولكنه يحتوي على العديد من الأحجار الكريمة التي ربما لم تسمع بها بعد. هل نظرت بالفعل إلى الأحدث في String و " Optional و " Collection وغيرها من المناهج؟ إذا لم يكن الأمر كذلك ، فأنت وصلت إلى العنوان: اليوم سننظر إلى 11 جواهر مخفية من Java 11!


اكتب الاستدلال لمعلمات لامدا


عند كتابة تعبير lambda ، يمكنك الاختيار بين تحديد الأنواع بوضوح وتخطيها:


 Function<String, String> append = string -> string + " "; Function<String, String> append = (String s) -> s + " "; 

قدم Java 10 var ، لكن لا يمكن استخدامه في lambdas:


 //    Java 10 Function<String, String> append = (var string) -> string + " "; 

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


  • يجعل استخدام var أكثر عالمية عن طريق إزالة الاستثناء للقاعدة
  • يسمح لك بإضافة التعليقات التوضيحية إلى نوع المعلمة دون اللجوء إلى استخدام اسمه الكامل

فيما يلي مثال للحالة الثانية:


 List<EnterpriseGradeType<With, Generics>> types = /*...*/; types .stream() // ,     @Nonnull   .filter(type -> check(type)) //  Java 10    ~>  .filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type)) //  Java 11    ~>   .filter((@Nonnull var type) -> check(type)) 

على الرغم من أن الخلط بين الأنواع المشتقة والصريحة والضمنية في تعبيرات lambda من النموذج (var type, String option, index) -> ... يمكن تنفيذه ، ولكن ( في إطار JEP-323 ) لم يتم تنفيذ هذا العمل. لذلك ، من الضروري اختيار واحد من الطرق الثلاثة والالتزام بها لجميع معايير التعبير lambda. قد تكون الحاجة إلى تحديد var لجميع المعلمات من أجل إضافة تعليق توضيحي لأحدها مزعجة بعض الشيء ، لكن يمكن تحملها عمومًا.


دفق معالجة السلاسل مع 'String::lines'


حصلت على سلسلة متعددة الخطوط؟ تريد أن تفعل شيئا مع كل سطر منه؟ ثم String::lines هو الخيار الصحيح:


 var multiline = "\r\n\r\n\r\n"; multiline .lines() //Stream<String> .map(line -> "// " + line) .forEach(System.out::println); // : //  //  //  //  

لاحظ أن السطر الأصلي يستخدم محددات المسمار \r\n وعلى الرغم من أنني على نظام Linux ، إلا أن lines() لا تزال تعمل على كسرها. ويعزى ذلك إلى حقيقة أنه على الرغم من نظام التشغيل الحالي ، تفسر هذه الطريقة \r و \n و \r\n كفواصل أسطر - حتى إذا كانت مختلطة على نفس السطر.


لا يحتوي دفق الخطوط مطلقًا على فواصل الأسطر نفسها. يمكن أن تكون الخطوط فارغة ( "\n\n \n\n" ، التي تحتوي على 5 خطوط) ، ولكن يتم تجاهل السطر الأخير من السطر الأصلي إذا كان فارغًا ( "\n\n" ؛ سطرين). (ملاحظة من المترجم: من المريح بالنسبة لهم أن يكون لديهم line ، ولكن لديهم string ، ولدينا كل منهما.)


على عكس split("\R") ، تعتبر lines() كسولة ، وأقتبس ، "توفر أداء أفضل [...] من خلال البحث بشكل أسرع عن فواصل الأسطر الجديدة". (إذا أراد شخص ما تقديم ملف قياسي على JMH للتحقق منه ، فأعلمني بذلك). كما أنه يعكس بشكل أفضل خوارزمية المعالجة ويستخدم بنية بيانات أكثر ملاءمة (دفق بدلاً من الصفيف).


إزالة مسافة بيضاء باستخدام 'String::strip' ، إلخ.


في البداية ، كان لدى String طريقة تقليدية لإزالة المسافة البيضاء ، والتي كانت تعتبر كل شيء برموز حتى U+0020 . نعم ، BACKSPACE ( U+0008) هي مسافة بيضاء مثل BELL ( U+0007 ) ، ولكن لم يعد LINE SEPARATOR ( U+2028 ) على هذا النحو.


أدخلت Java 11 طريقة strip ، الذي يحتوي منهجها على مزيد من الفروق الدقيقة. يستخدم Character::isWhitespace من Java 5 لتحديد ما يجب إزالته بالضبط. من وثائقها ، من الواضح أن هذا:


  • SPACE SEPARATOR ، SPACE SEPARATOR LINE SEPARATOR ، PARAGRAPH SEPARATOR ، ولكن ليس مساحة لا تنفصم
  • HORIZONTAL TABULATION ( U+0009 ) ، LINE FEED VERTICAL TABULATION ( U+000B ) ، VERTICAL TABULATION ( U+000B ) ، FORM FEED ( U+000C ) ، CARRIAGE RETURN ( U+000D )
  • FILE SEPARATOR ( U+001C ) ، GROUP SEPARATOR ( U+001D ) ، RECORD SEPARATOR ( U+001E ) ، UNIT SEPARATOR U+001E ( U+001F )

مع نفس المنطق ، هناك طريقتان للتنظيف ، stripLeading وقطاع stripTailing ، stripTailing بالضبط بما هو متوقع منها.


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


 " ".isBlank(); //  ~> true " ".isBlank(); //   ~> false 

تكرار السلاسل مع 'String::repeat'


قبض على الفكرة:


الخطوة 1: مراقبة الساعة على JDK

مراقبة عن كثب لتطوير JDK


الخطوة 2: البحث عن الأسئلة المتعلقة بـ StackOverflow

تبحث عن أسئلة ذات صلة على Stackoverflow


الخطوة 3: الوصول إلى إجابة جديدة بناءً على التغييرات المستقبلية

انقض بإجابة جديدة بناءً على التغييرات القادمة


الخطوة 4: ؟؟؟

الخطوة 4: الربح

¯ \ _ (ツ) _ / ¯


كما يمكنك أن تتخيل ، لدى String طريقة repeat(int) . إنه يعمل تمامًا بما يتماشى مع التوقعات ، ولا يوجد الكثير للمناقشة.


إنشاء مسارات باستخدام 'Path::of'


تعجبني واجهة برمجة تطبيقات Path ، ولكن تحويل المسارات بين طرق العرض المختلفة (مثل Path File URL و URI و String ) لا يزال مزعجًا. أصبحت هذه النقطة أقل إرباكًا في Java 11 عن طريق نسخ Paths::get الطرق في Path::of الطرق:


 Path tmp = Path.of("/home/nipa", "tmp"); Path codefx = Path.of(URI.create("http://codefx.org")); 

يمكن اعتبارها متعارف عليها ، لأن كلا Paths::get القديمين Paths::get طرق تستخدم خيارات جديدة.


قراءة وكتابة الملفات باستخدام 'Files::readString' و 'Files::writeString'


إذا كنت بحاجة إلى القراءة من ملف كبير ، فإنني عادةً ما أستخدم Files::lines للحصول على دفق كسول من خطوطه. وبالمثل ، لكتابة قدر كبير من البيانات التي قد لا يتم تخزينها في الذاكرة بأكملها ، يمكنني استخدام Files::write Iterable<String> كـ Iterable<String> .


ولكن ماذا عن الحالة البسيطة عندما أرغب في معالجة محتويات الملف كسطر واحد؟ هذا ليس ملائمًا للغاية ، لأن Files::readAllBytes والمتغيرات المناسبة من Files::write تعمل على صفائف البايت.


ثم يظهر Java 11 ، مضيفًا readString و writeString إلى Files :


 String haiku = Files.readString(Path.of("haiku.txt")); String modified = modify(haiku); Files.writeString(Path.of("haiku-mod.txt"), modified); 

واضح وسهل الاستخدام. إذا لزم الأمر ، يمكنك تمرير readString إلى readString ، وفي writeString أيضًا صفيف OpenOptions .


إفراغ الإدخال / الإخراج باستخدام 'Reader::nullReader' ، إلخ.


هل تحتاج إلى OutputStream لا يكتب في أي مكان؟ أو InputStream فارغة؟ ماذا عن Reader Writer الذي لا يفعل شيئًا؟ جافا 11 لديه كل شيء:


 InputStream input = InputStream.nullInputStream(); OutputStream output = OutputStream.nullOutputStream(); Reader reader = Reader.nullReader(); Writer writer = Writer.nullWriter(); 

(ملاحظة المترجم: في commons-io هذه الفئات منذ عام 2014 تقريبًا)


ومع ذلك ، أنا مندهش - هل هو حقًا أفضل بادئة؟ لا أحب كيف يتم استخدامه ليعني "الغياب المتعمد" ... ربما سيكون من الأفضل استخدام noOp ؟ (ملاحظة المترجم: على الأرجح تم اختيار هذه البادئة بسبب الاستخدام الشائع لـ /dev/null .)


{ } ~> [ ] مع 'Collection::toArray'


كيف يمكنك تحويل المجموعات إلى صفائف؟


 //  Java 11 List<String> list = /*...*/; Object[] objects = list.toArray(); String[] strings_0 = list.toArray(new String[0]); String[] strings_size = list.toArray(new String[list.size()]); 

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


ولكن لماذا يجب أن أهتم بهذا؟ ليس هناك طريقة أفضل للقيام بذلك؟ في Java 11 هناك:


 String[] strings_fun = list.toArray(String[]::new); 

Collection::toArray متغير جديد من Collection::toArray ، يقبل IntFunction<T[]> ، أي دالة تستقبل حجم الصفيف وتقوم بإرجاع صفيف بالحجم المطلوب. يمكن التعبير عنها لفترة وجيزة كمرجع إلى مُنشئ النموذج T[]::new (من أجل T معروف جيدًا).


حقيقة مثيرة للاهتمام ، التنفيذ الافتراضي لـ Collection#toArray(IntFunction<T[]>) يمرر دائمًا 0 إلى منشئ الصفيف. في البداية ، قررت أن هذا الحل يعتمد على أفضل أداء للصفائف ذات الطول الصفري ، لكن الآن أعتقد أن السبب قد يكون بالنسبة لبعض المجموعات ، فإن حساب الحجم يمكن أن يكون عملية مكلفة للغاية ويجب عدم استخدام هذا النهج في التنفيذ الافتراضي Collection . ومع ذلك ، يمكن أن تقوم تطبيقات المجموعة المحددة ، مثل ArrayList ، بتغيير هذا الأسلوب ، لكنها لا تتغير في Java 11. لا يستحق كل هذا العناء ، أعتقد.


تحقق من عدم وجود 'Optional::isEmpty'


مع الاستخدام الوفير لـ Optional ، خاصة في المشروعات الكبيرة ، حيث تواجه غالبًا نهجًا غير Optional ، يجب عليك في كثير من الأحيان التحقق مما إذا كانت له قيمة. هناك طريقة Optional::isPresent لهذا الغرض. ولكن في كثير من الأحيان تحتاج إلى معرفة العكس - أن Optional فارغ. لا مشكلة ، فقط استخدم !opt.isPresent() ، أليس كذلك؟


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


 public boolean needsToCompleteAddress(User user) { return !getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isPresent(); } 

في هذه الحالة ، تخطي ذلك ! سهل جدا بدءًا من Java 11 ، هناك خيار أفضل:


 public boolean needsToCompleteAddress(User user) { return getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isEmpty(); } 

عكس المسند مع 'Predicate::not'


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


 //      Stream .of("a", "b", "", "c") // ,  ~>        .filter(s -> !s.isBlank()) //          ~>  .filter((String::isBlank).negate()) // ,  ~>       .filter(((Predicate<String>) String::isBlank).negate()) .forEach(System.out::println); 

المشكلة هي أنه نادراً ما يمكنني الوصول إلى حالة Predicate . في أغلب الأحيان ، أرغب في الحصول على مثل هذا المثال من خلال ارتباط لطريقة (وعكسها) ، ولكن لكي يعمل ذلك ، يجب أن يعرف المترجم ما يجب إحضاره إلى الطريقة - دون ذلك ، لا يمكنه فعل أي شيء. وهذا هو بالضبط ما يحدث إذا استخدمت (String::isBlank).negate() : لم يعد المحول البرمجي يعرف ما يجب أن يكون عليه String::isBlank في هذا الأمر String::isBlank . طبقة محددة بشكل صحيح بإصلاح هذا ، ولكن بأي ثمن؟


رغم أن هناك حل بسيط. لا تستخدم negate مثيل negate ، ولكن استخدم الأسلوب الثابت الجديد Predicate.not(Predicate<T>) من Java 11:


 Stream .of("a", "b", "", "c") //   `java.util.function.Predicate.not` .filter(not(String::isBlank)) .forEach(System.out::println); 

بالفعل أفضل!


التعبيرات العادية 'Pattern::asMatchPredicate' مع 'Pattern::asMatchPredicate'


هل هناك تعبير منتظم؟ تحتاج إلى تصفية البيانات على ذلك؟ ماذا عن هذا:


 Pattern nonWordCharacter = Pattern.compile("\\W"); Stream .of("Metallica", "Motörhead") .filter(nonWordCharacter.asPredicate()) .forEach(System.out::println); 

كنت سعيدًا جدًا بالعثور على هذه الطريقة! تجدر الإشارة إلى أن هذه طريقة من Java 8. عفوًا ، فاتني ذلك. أضاف Java 11 طريقة أخرى مماثلة: Pattern::asMatchPredicate . ما هو الفرق؟


  • يتحقق asPredicate السلسلة أو جزء منها مع النموذج (يعمل مثل s -> this.matcher(s).find() )
  • يتحقق asMatchPredicate من تطابق السلسلة بالكامل مع النمط (يعمل مثل s -> this.matcher(s).matches() )

على سبيل المثال ، لدينا تعبير منتظم يتحقق من أرقام الهواتف ، لكنه لا يحتوي على ^ و $ لتتبع بداية ونهاية الخط. ثم لن تعمل التعليمات البرمجية التالية كما قد تتوقع:


 prospectivePhoneNumbers .stream() .filter(phoneNumberPatter.asPredicate()) .forEach(this::robocall); 

هل لاحظت خطأ؟ سيتم تصفية سطر مثل " -152 ? +1-202-456-1414" ، لأنه يحتوي على رقم هاتف صالح. من ناحية أخرى ، لن يسمح Pattern::asMatchPredicate بذلك ، لأن السلسلة بأكملها لن تتطابق مع النموذج.


اختبار الذات


إليك نظرة عامة على جميع اللؤلؤات الإحدى عشرة - هل ما زلت تتذكر ما تفعله كل طريقة؟ إذا كان الأمر كذلك ، لقد نجحت في الاختبار.


  • في String :
    • Stream<String> lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • في Path :
    • static Path of(String, String...)
    • static Path of(URI)
  • في Files :
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • في static InputStream nullInputStream() : static InputStream nullInputStream()
  • في static OutputStream nullOutputStream() : static OutputStream nullOutputStream()
  • في Reader : Reader static Reader nullReader()
  • في Writer : static Writer nullWriter()
  • في Collection : T[] toArray(IntFunction<T[]>)
  • في Optional : المنطقة boolean isEmpty()
  • في Predicate : static Predicate<T> not(Predicate<T>)
  • في Pattern : Predicate<String> asMatchPredicate()

استمتع مع Java 11!

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


All Articles