يعد 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() {
نعم ، بالطبع ، في كلتا الحالتين ، يمكن للمرء بسهولة الاستغناء عن هذه الوظائف ، على سبيل المثال ، باستخدام عامل التشغيل الثلاثي المعتاد أو 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());
String.stripLeading()
و String.stripTrailing()
و String.strip()
تزيل أحرف مسافة بيضاء في بداية السطر ، في نهاية السطر ، أو في كلا الطرفين:
String str = " \tHello, world!\t\n"; String str1 = str.stripLeading();
لاحظ أن String.strip()
ليس String.strip()
نفسه String.trim()
: يزيل الثاني فقط الأحرف التي يكون String.trim()
أقل من U + 0020 أو مساويًا له ، الأول يزيل أيضًا المسافات من Unicode:
System.out.println("str\u2000".strip());
الأسلوب 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
:
كلا الإصدارين يطبعان جميع درجات الشيطان التي لا تتجاوز 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, () -> {
بطريقة جيدة ، يجب على المستخدمين إنشاء مثل هذا المورد في كتلة try
:
try (var resource = new Resource()) {
ومع ذلك ، قد يكون هناك مستخدمون ينسون القيام بذلك ويكتبون ببساطة var resource = new Resource()
. في مثل هذه الحالات ، لن يتم إجراء التنظيف على الفور ، ولكن سيتم استدعاؤه لاحقًا في إحدى دورات جمع القمامة التالية. إنه أفضل من لا شيء.
إذا كنت ترغب في دراسة " Cleaner
بشكل أفضل ومعرفة سبب عدم استخدام finalize()
، فنوصيك بالاستماع إلى حديثي حول هذا الموضوع.
استنتاج
جافا لا يزال قائما ويتطور تدريجيا. أثناء جلوسك على Java 8 ، مع كل إصدار هناك واجهات برمجة تطبيقات جديدة ومثيرة للاهتمام أكثر. اليوم قمنا بمراجعة 10 واجهات برمجة التطبيقات هذه. ويمكنك استخدامها جميعًا إذا قررت أخيرًا الترحيل من Java 8.
في المرة القادمة ، سننظر إلى 10 واجهات برمجة تطبيقات جديدة.
إذا كنت لا ترغب في تخطي الجزء التالي ، فنوصيك بالاشتراك في قناة Telegram ، حيث أنشر أيضًا أخبار Java.