واجهات برمجة التطبيقات التي تستحق الترقية في النهاية من Java 8. Part 1

يعد Java 8 هو الإصدار الأكثر شعبية من Java وسيظل معه لفترة طويلة. ومع ذلك ، تم بالفعل إصدار خمسة إصدارات جديدة من Java منذ ذلك الحين (9 ، 10 ، 11 ، 12 ، 13) ، وسرعان ما سيتم إصدار Java 14 آخر ، وقد ظهر عدد كبير من الميزات الجديدة في هذه الإصدارات الجديدة. على سبيل المثال ، إذا عدت في JEPs ، فسيتم تطبيق إجمالي 141:



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


1. أساليب Objects.requireNonNullElse() و Objects.requireNonNullElseGet()


قدم في: جافا 9


نبدأ قائمتنا بطريقتين بسيطتين للغاية ، لكنهما مفيدتان جدًا في فئة java.util.Objects : requireNonNullElse() و requireNonNullElseGet() . تسمح لك هذه الطرق بإرجاع الكائن المرسل ، وإذا لم يكن null ، وإذا كان null ، null بإرجاع الكائن افتراضيًا. على سبيل المثال:


 class MyCoder { private final Charset charset; MyCoder(Charset charset) { this.charset = Objects.requireNonNullElse( charset, StandardCharsets.UTF_8); } } 

requireNonNullElseGet() ليس أكثر من إصدار كسول من requireNonNullElse() . قد يكون مفيدًا إذا كان حساب الوسيطة الافتراضية باهظًا:


 class MyCoder { private final Charset charset; MyCoder(Charset charset) { this.charset = Objects.requireNonNullElseGet( charset, MyCoder::defaultCharset); } private static Charset defaultCharset() { // long operation... } } 

نعم ، بالطبع ، في كلتا الحالتين ، يمكن للمرء بسهولة الاستغناء عن هذه الوظائف ، على سبيل المثال ، باستخدام عامل التشغيل الثلاثي المعتاد أو Optional ، ولكن الاستمرار في استخدام وظيفة خاصة يجعل الكود أقصر وأنظف قليلاً. وإذا كنت تستخدم الاستيراد الثابت والكتابة ببساطة requireNonNullElse() بدلاً من Objects.requireNonNullElse() ، فيمكن تقليل الرمز أكثر.



2. طرق مصنع العودة مجموعات غير قابل للتغيير


قدم في: جافا 9


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



إلى نفس القائمة ، يمكنك إضافة الأسلوب Map.entry(K k, V v) المصاحب ، والذي ينشئ Entry من المفتاح والقيمة ، وكذلك طرق نسخ المجموعات التي ظهرت في Java 10:



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


 List<String> imageExtensions = List.of("bmp", "jpg", "png", "gif"); 

إذا كنت لا تستخدم مكتبات الجهات الخارجية ، فإن التعليمات البرمجية المماثلة في Java 8 تبدو أكثر تعقيدًا:


 List<String> imageExtensions = Collections.unmodifiableList( Arrays.asList("bmp", "jpg", "png", "gif")); 

وفي حالة Set أو Map لا يزال الأمر أكثر حزنًا ، لأنه لا توجد نظائرها من Arrays.asList() لـ Set و Map .


تثير هذه المرهقة الكثير من الأشخاص الذين يكتبون في Java 8 للتخلي عن المجموعات غير القابلة للتغيير تمامًا ويستخدمون دائمًا ArrayList و HashSet و HashMap المعتاد ، حتى عند الحاجة إلى معنى المجموعات غير القابلة للتغيير. نتيجة لذلك ، يؤدي هذا إلى كسر مفهوم "التثبيت الافتراضي" ويقلل من أمان الرمز.


إذا قمت بالترقية أخيرًا من Java 8 ، يصبح العمل مع المجموعات الثابتة أسهل بكثير وأكثر متعة بفضل أساليب المصنع.



3. Files.readString() و Files.writeString()


قدم في: جافا 11


لطالما كانت جافا معروفة بإدخالها على مهل في الأساليب الجاهزة للعمليات المتكررة. على سبيل المثال ، بالنسبة إلى واحدة من أكثر العمليات شيوعًا في البرمجة ، قراءة ملف ، لفترة طويلة جدًا ، لم تكن هناك طريقة جاهزة. بعد 15 سنة فقط من إصدار Java 1.0 ، ظهر NIO ، حيث تم تقديم طريقة Files.readAllBytes() لقراءة الملف في صفيف بايت.


ولكن هذا لا يزال غير كافٍ ، لأن الأشخاص غالبًا ما يتعين عليهم العمل مع الملفات النصية ولهذا عليك قراءة السلاسل من الملف وليس البايتات. لذلك ، في Java 8 ، تمت إضافة الأسلوب Files.readAllLines() ، وإرجاع List<String> .


ومع ذلك ، لم يكن هذا كافيًا ، حيث سأل الناس عن مدى سهولة قراءة الملف بأكمله كسطر واحد. نتيجةً لذلك ، لإكمال الصورة في Java 11 ، تمت Files.readString() طريقة Files.readString() طال انتظارها ، وبالتالي إغلاق هذا السؤال أخيرًا. من المثير للدهشة ، إذا كانت هناك طريقة مماثلة موجودة في العديد من اللغات الأخرى منذ البداية ، فإن Java استغرق أكثر من 20 عامًا للقيام بذلك.


جنبا إلى جنب مع readString() بطبيعة الحال ، تم تقديم طريقة متماثل writeString() أيضا. تحتوي هذه الطرق أيضًا على أحمال زائدة تسمح لك بتحديد مجموعة Charset . معا ، كل هذا يجعل العمل مع الملفات النصية مريحة للغاية. مثال:


 /**        */ private void reencodeFile(Path path, Charset from, Charset to) throws IOException { String content = Files.readString(path, from); Files.writeString(path, content, to); } 


4. Optional.ifPresentOrElse() . إذا كان Optional.ifPresentOrElse() Optional.stream()


قدم في: جافا 9


عندما ظهر Optional في Java 8 ، لم يكن لديهم طريقة ملائمة للقيام بعملين مختلفين ، اعتمادًا على ما إذا كان له قيمة أم لا. نتيجةً لذلك ، يتعين على الناس اللجوء إلى السلسلة المعتادة isPresent() get() :


 Optional<String> opt = ... if (opt.isPresent()) { log.info("Value = " + opt.get()); } else { log.error("Empty"); } 

أو لا يزال بإمكانك المراوغة بهذه الطريقة:


 Optional<String> opt = ... opt.ifPresent(str -> log.info("Value = " + str)); if (opt.isEmpty()) { log.error("Empty"); } 

كلا الخيارين ليسا مثاليين. ولكن ، بدءًا من Java 9 ، يمكن القيام بذلك بأناقة باستخدام الطريقة Optional.ifPresentOrElse() :


 Optional<String> opt = ... opt.ifPresentOrElse( str -> log.info("Value = " + str), () -> log.error("Empty")); 

هناك طريقة جديدة أخرى مثيرة للاهتمام في Java 9 وهي Optional.stream() . Stream Optional.stream() ، والذي يقوم بإرجاع Stream من عنصر واحد إذا كانت القيمة موجودة ، Stream فارغ إذا لم يكن كذلك. مثل هذه الطريقة يمكن أن تكون مفيدة للغاية في سلاسل مع flatMap() . على سبيل المثال ، في هذا المثال ، من السهل جدًا الحصول على قائمة بجميع أرقام هواتف الشركة:


 class Employee { Optional<String> getPhoneNumber() { ... } } class Department { List<Employee> getEmployees() { ... } } class Company { List<Department> getDepartments() { ... } Set<String> getAllPhoneNumbers() { return getDepartments() .stream() .flatMap(d -> d.getEmployees().stream()) .flatMap(e -> e.getPhoneNumber().stream()) .collect(Collectors.toSet()); } } 

في Java 8 ، يجب عليك كتابة شيء مثل:


 e -> e.getPhoneNumber().map(Stream::of).orElse(Stream.empty()) 

يبدو ضخمة وغير قابلة للقراءة للغاية.



5. Process.pid() و Process.info() و ProcessHandle


قدم في: جافا 9


إذا كان لا يزال بإمكانك الإدارة بدون واجهات برمجة التطبيقات السابقة ، فسيكون استبدال الأسلوب Process.pid() في Java 8 مشكلة كبيرة ، خاصة عبر الأنظمة الأساسية. هذه الطريقة تقوم بإرجاع معرف العملية الأصلي:


 Process process = Runtime.getRuntime().exec("java -version"); System.out.println(process.pid()); 

باستخدام الأسلوب Process.info() ، يمكنك أيضًا العثور على معلومات إضافية مفيدة حول العملية. تقوم بإرجاع كائن من النوع ProcessHandle.Info . دعونا نرى ما يعود إلينا للعملية أعلاه:


 Process process = Runtime.getRuntime().exec("java -version"); ProcessHandle.Info info = process.info(); System.out.println("PID = " + process.pid()); System.out.println("User = " + info.user()); System.out.println("Command = " + info.command()); System.out.println("Args = " + info.arguments().map(Arrays::toString)); System.out.println("Command Line = " + info.commandLine()); System.out.println("Start Time = " + info.startInstant()); System.out.println("Total Time = " + info.totalCpuDuration()); 

الاستنتاج:


 PID = 174 User = Optional[orionll] Command = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java] Args = Optional[[-version]] Command Line = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java -version] Start Time = Optional[2020-01-24T05:54:25.680Z] Total Time = Optional[PT0.01S] 

ماذا لو لم تبدأ العملية من عملية Java الحالية؟ لهذا ، يأتي ProcessHandle إلى ProcessHandle . على سبيل المثال ، دعنا نحصل على كل المعلومات نفسها للعملية الحالية باستخدام أسلوب ProcessHandle.current() :


 ProcessHandle handle = ProcessHandle.current(); ProcessHandle.Info info = handle.info(); System.out.println("PID = " + handle.pid()); System.out.println("User = " + info.user()); System.out.println("Command = " + info.command()); System.out.println("Args = " + info.arguments().map(Arrays::toString)); System.out.println("Command Line = " + info.commandLine()); System.out.println("Start Time = " + info.startInstant()); System.out.println("Total Time = " + info.totalCpuDuration()); 

الاستنتاج:


 PID = 191 User = Optional[orionll] Command = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java] Args = Optional[[Main.java]] Command Line = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java Main.java] Start Time = Optional[2020-01-24T05:59:17.060Z] Total Time = Optional[PT1.56S] 

للحصول على ProcessHandle لأي عملية بواسطة PID الخاص به ، يمكنك استخدام الأسلوب ProcessHandle.of() (سيعود Optional.empty ProcessHandle.of() إذا كانت العملية غير موجودة).


يوجد أيضًا في ProcessHandle العديد من الطرق الأخرى المثيرة للاهتمام ، على سبيل المثال ، ProcessHandle.allProcesses() .



6. طرق String : isBlank() ، strip() ، stripLeading() ، stripTrailing() ، repeat() lines()


قدم في: جافا 11


ظهرت مجموعة كاملة من الطرق المفيدة للسلاسل في Java 11.


تسمح لك طريقة String.isBlank() بمعرفة ما إذا كانت السلسلة تتكون فقط من مسافة بيضاء:


 System.out.println(" \n\r\t".isBlank()); // true 

String.stripLeading() و String.stripTrailing() و String.strip() تزيل أحرف مسافة بيضاء في بداية السطر ، في نهاية السطر ، أو في كلا الطرفين:


 String str = " \tHello, world!\t\n"; String str1 = str.stripLeading(); // "Hello, world!\t\n" String str2 = str.stripTrailing(); // " \tHello, world!" String str3 = str.strip(); // "Hello, world!" 

لاحظ أن String.strip() ليس String.strip() نفسه String.trim() : يزيل الثاني فقط الأحرف التي يكون String.trim() أقل من U + 0020 أو مساويًا له ، الأول يزيل أيضًا المسافات من Unicode:


 System.out.println("str\u2000".strip()); // "str" System.out.println("str\u2000".trim()); // "str\u2000" 


الأسلوب String.repeat() يسلسل السلسلة نفسها n مرات:


 System.out.print("Hello, world!\n".repeat(3)); 

الاستنتاج:


 Hello, world! Hello, world! Hello, world! 

أخيرًا ، يقوم الأسلوب String.lines() بتقسيم السلسلة إلى خطوط. وداعًا String.split() ، حيث يخلط الناس دائمًا بين الحجة التي يجب استخدامها لذلك ، إما "\n" أو "\r" أو "\n\r" (في الواقع ، من الأفضل استخدام العادية التعبير "\R" ، الذي يغطي جميع المجموعات). بالإضافة إلى ذلك ، يمكن أن تكون String.lines() غالبًا أكثر فاعلية حيث تقوم بإرجاع الخطوط بتكاسل.


 System.out.println("line1\nline2\nline3\n" .lines() .map(String::toUpperCase) .collect(Collectors.joining("\n"))); 

الاستنتاج:


 LINE1 LINE2 LINE3 


7. String.indent()


ظهرت في: جافا 12


دعونا نخفف قصتنا بشيء جديد ظهر مؤخراً. يجتمع: أسلوب String.indent() ، مما يزيد (أو يقلل) المسافة البادئة لكل سطر في سطر معين بالقيمة المحددة. على سبيل المثال:


 String body = "<h1>Title</h1>\n" + "<p>Hello, world!</p>"; System.out.println("<html>\n" + " <body>\n" + body.indent(4) + " </body>\n" + "</html>"); 

الاستنتاج:


 <html> <body> <h1>Title</h1> <p>Hello, world!</p> </body> </html> 

لاحظ أنه في السطر الأخير ، قام String.indent() نفسه بإدخال موجز السطر ، لذلك لم يكن علينا إضافة '\n' بعد body.indent(4) .


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



8. أساليب Stream : takeWhile() ، dropWhile() ، iterate() مع المسند و ofNullable()


قدم في: جافا 9


Stream.takeWhile() Stream.limit() ، لكنه يقيد Stream ليس بالكمية ، ولكن حسب المسند. مثل هذه الحاجة للبرمجة تنشأ في كثير من الأحيان. على سبيل المثال ، إذا كنا بحاجة إلى الحصول على جميع إدخالات اليوميات للعام الحالي:


 [ { "date" : "2020-01-27", "text" : "..." }, { "date" : "2020-01-25", "text" : "..." }, { "date" : "2020-01-22", "text" : "..." }, { "date" : "2020-01-17", "text" : "..." }, { "date" : "2020-01-11", "text" : "..." }, { "date" : "2020-01-02", "text" : "..." }, { "date" : "2019-12-30", "text" : "..." }, { "date" : "2019-12-27", "text" : "..." }, ... ] 

Stream السجلات يكاد لا ينتهي ، لذلك لا يمكن استخدام filter() . ثم takeWhile() إلى takeWhile() :


 getNotesStream() .takeWhile(note -> note.getDate().getYear() == 2020); 

وإذا أردنا الحصول على سجلات لعام 2019 ، فيمكننا استخدام dropWhile() :


 getNotesStream() .dropWhile(note -> note.getDate().getYear() == 2020) .takeWhile(note -> note.getDate().getYear() == 2019); 

في Java 8 ، يمكن فقط لإنشاء Stream.iterate() Stream لانهائي. ولكن في Java 9 ، تحتوي هذه الطريقة على حمل يأخذ مسندًا. بفضل هذا ، يمكن الآن استبدال العديد من الحلقات بـ Stream :


 // Java 8 for (int i = 1; i < 100; i *= 2) { System.out.println(i); } 

 // Java 9+ IntStream .iterate(1, i -> i < 100, i -> i * 2) .forEach(System.out::println); 

كلا الإصدارين يطبعان جميع درجات الشيطان التي لا تتجاوز 100 :


 1 2 4 8 16 32 64 

بالمناسبة ، يمكن إعادة كتابة الرمز الأخير باستخدام takeWhile() :


 IntStream .iterate(1, i -> i * 2) .takeWhile(i -> i < 100) .forEach(System.out::println); 

ومع ذلك ، لا يزال الخيار ذو iterate() ذو الوسيطة الثلاث iterate() نظافة ( وتقترح IntelliJ IDEA تصحيحه مرة أخرى).


وأخيرًا ، Stream.ofNullable() مع عنصر واحد إذا لم يكن null ، Stream.ofNullable() فارغًا إذا كان null . هذه الطريقة مثالية في المثال أعلاه مع هواتف الشركة إذا كان getPhoneNumber() سيعود String getPhoneNumber() بدلاً من Optional<String> :


 class Employee { String getPhoneNumber() { ... } } class Department { List<Employee> getEmployees() { ... } } class Company { List<Department> getDepartments() { ... } Set<String> getAllPhoneNumbers() { return getDepartments() .stream() .flatMap(d -> d.getEmployees().stream()) .flatMap(e -> Stream.ofNullable(e.getPhoneNumber())) .collect(Collectors.toSet()); } } 


9. Predicate.not()


ظهرت في: جافا 11


هذه الطريقة لا تقدم أي شيء جديد بشكل أساسي وأكثر تجميلية من الأساس. ومع ذلك ، فإن القدرة على تقصير الشفرة قليلاً تكون دائمًا لطيفة للغاية. باستخدام Predicate.not() يمكن استبدال lambdas التي لها نفي بمراجع الطريقة:


 Files.lines(path) .filter(str -> !str.isEmpty()) .forEach(System.out::println); 

والآن not() تستخدم not() :


 Files.lines(path) .filter(not(String::isEmpty)) .forEach(System.out::println); 

نعم ، المدخرات ليست ضخمة جدًا ، وإذا استخدمت s -> !s.isEmpty() ، يصبح عدد الأحرف ، على العكس ، أكبر. لكن حتى في هذه الحالة ، ما زلت أفضل الخيار الثاني ، لأنه أكثر تعريفًا ولا يستخدم متغيرًا فيه ، مما يعني أن مساحة الاسم لا تشوش.



10. نظافة


ظهرت في: جافا 9


أريد أن أنهي قصتي اليوم بواجهة برمجة تطبيقات جديدة مثيرة للاهتمام ظهرت في Java 9 وتعمل على تنظيف الموارد قبل أن يتم التخلص منها بواسطة جامع البيانات المهملة. Cleaner هو بديل آمن Object.finalize() ، والذي أصبح مهملًا في Java 9.


باستخدام Cleaner يمكنك تسجيل عملية تنظيف الموارد التي ستحدث إذا نسيت القيام بذلك بشكل صريح (على سبيل المثال ، لقد نسيت استدعاء طريقة close() أو أنك لم تستخدم طريقة try-with-resources ). فيما يلي مثال لمورد تجريدي يتم تسجيل إجراء تطهير له في المُنشئ:


 public class Resource implements Closeable { private static final Cleaner CLEANER = Cleaner.create(); private final Cleaner.Cleanable cleanable; public Resource() { cleanable = CLEANER.register(this, () -> { //   // (,  ) }); } @Override public void close() { cleanable.clean(); } } 

بطريقة جيدة ، يجب على المستخدمين إنشاء مثل هذا المورد في كتلة try :


 try (var resource = new Resource()) { //   } 

ومع ذلك ، قد يكون هناك مستخدمون ينسون القيام بذلك ويكتبون ببساطة var resource = new Resource() . في مثل هذه الحالات ، لن يتم إجراء التنظيف على الفور ، ولكن سيتم استدعاؤه لاحقًا في إحدى دورات جمع القمامة التالية. إنه أفضل من لا شيء.


إذا كنت ترغب في دراسة " Cleaner بشكل أفضل ومعرفة سبب عدم استخدام finalize() ، فنوصيك بالاستماع إلى حديثي حول هذا الموضوع.



استنتاج


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


في المرة القادمة ، سننظر إلى 10 واجهات برمجة تطبيقات جديدة.


إذا كنت لا ترغب في تخطي الجزء التالي ، فنوصيك بالاشتراك في قناة Telegram ، حيث أنشر أيضًا أخبار Java.

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


All Articles