JavaRush /בלוג Java /Random-HE /תכונות Java 8 - המדריך האולטימטיבי (חלק 2)
0xFF
רָמָה
Донецк

תכונות Java 8 - המדריך האולטימטיבי (חלק 2)

פורסם בקבוצה
החלק השני של התרגום של המאמר Java 8 Features – The ULTIMATE Guide . החלק הראשון כאן (ייתכן שהקישור ישתנה). תכונות Java 8 - המדריך האולטימטיבי (חלק 2) - 1

5. תכונות חדשות בספריות Java 8

Java 8 הוסיפה מחלקות חדשות רבות והרחיבה מחלקות קיימות כדי לתמוך טוב יותר במקבילות מודרנית, תכנות פונקציונלי, תאריך/שעה ועוד.

5.1. שיעור אופציונלי

NullPointerException המפורסם הוא ללא ספק הגורם השכיח ביותר לכשלים ביישומי Java. לפני זמן רב, הפרויקט המצוין של גוגל Guava הוצג Optionalכפתרון NullPointerException, ובכך מנע את זיהום הקוד על ידי בדיקות null, וכתוצאה מכך עידוד כתיבת קוד נקי יותר. מחלקת ה-Guava בהשראת גוגל Optionalהיא כעת חלק מ-Java 8. Optionalזה רק מיכל: הוא יכול להכיל ערך או סוג כלשהו Т, ​​או פשוט להיות null. הוא מספק שיטות שימושיות רבות כך שבדיקות ריק מפורשות אינן מוצדקות עוד. עיין בתיעוד הרשמי למידע מפורט יותר. הבה נסתכל על שתי דוגמאות קטנות לשימוש Optional: עם ובלי null.
Optional<String> fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
השיטה isPresent()מחזירה true אם המופע Optionalמכיל ערך שאינו null ו- false אחרת. השיטה orElseGet()מכילה מנגנון fallback עבור התוצאה אם Optional​​היא מכילה null, ומקבלת פונקציות ליצירת ערך ברירת מחדל. שיטת map () הופכת את הערך הנוכחי Optionalומחזירה מופע חדש Optional. השיטה orElse()דומה ל- orElseGet(), אבל במקום פונקציה היא לוקחת ערך ברירת מחדל. להלן הפלט של תוכנית זו:
Full Name is set? false
Full Name: [none]
Hey Stranger!
בואו נסתכל במהירות על דוגמה נוספת:
Optional<String> firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
התוצאה תהיה כזו:
First Name is set? true
First Name: Tom
Hey Tom!
למידע מפורט יותר, עיין בתיעוד הרשמי .

5.2. זרמים

ה- Stream API החדש שנוסף ( java.util.stream) מציג תכנות בסגנון פונקציונלי אמיתי ב-Java. זוהי ללא ספק התוספת המקיפה ביותר לספריית Java ומאפשרת למפתחי Java להיות יעילים משמעותית וגם מאפשרת להם ליצור קוד יעיל, נקי ותמציתי. ה-API של Stream הופך את עיבוד האוספים להרבה יותר קל (אך לא מוגבל אליהם, כפי שנראה בהמשך). ניקח כדוגמה שיעור פשוט Task.
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
למשימה יש תחושה מסוימת של נקודות (או פסבדו קשיים) ויכולה להיות OPEN או CLOSE . בואו נציג אוסף קטן של בעיות לשחק איתן.
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
השאלה הראשונה שאנו מתכוונים לברר היא כמה נקודות מכילות משימות OPEN כרגע? לפני Java 8, הפתרון הרגיל לכך היה שימוש באיטרטור foreach. אבל ב-Java 8, התשובה היא זרמים: רצף של אלמנטים התומכים בפעולות צבירה עוקבות ומקבילות.
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
ופלט הקונסולה ייראה כך:
Total points: 18
בואו נראה מה קורה כאן. ראשית, אוסף המשימות מומר לייצוג סטרימינג. לאחר מכן, הפעולה filterמסננת את כל המשימות בסטטוס סגור . בשלב הבא, הפעולה mapToIntממירה זרם Tasks לזרמים Integerבאמצעות שיטה Task::getPointsלכל מופע Task. לבסוף, כל הנקודות מסוכמות באמצעות השיטה sum, המספקת את התוצאה הסופית. לפני המעבר לדוגמאות הבאות, יש כמה הערות לגבי שרשורים שכדאי לזכור (פרטים נוספים כאן ). הפעולות streamמחולקות לפעולות ביניים וסופיות . פעולות ביניים מחזירות זרם חדש. הם תמיד עצלנים; כאשר הם מבצעים פעולות ביניים כגון filter, הם אינם מבצעים סינון בפועל, אלא יוצרים זרם חדש, שכאשר הוא הושלם, מכיל את הרכיבים של הזרם המקורי התואמים את הפרדיקט הנתון. פעולות סופיות , כגון forEachו- sum, יכולות לעבור דרך זרם כדי לייצר תוצאה או תופעת לוואי. לאחר השלמת הפעולה הסופית, הזרם נחשב בשימוש ולא ניתן להשתמש בו שוב. כמעט בכל המקרים, פעולות הקצה נוטות להשלים את המעבר שלהן דרך מקור הנתונים הבסיסי. תכונה חשובה נוספת של חוטים היא התמיכה בתהליכים מקבילים מחוץ לקופסה. הבה נסתכל על דוגמה זו, שמוצאת את סכום הציונים של כל הבעיות.
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );
זה דומה מאוד לדוגמא הראשונה, אלא שאנו מנסים לעבד את כל המשימות במקביל ולחשב את התוצאה הסופית באמצעות השיטה reduce. הנה פלט הקונסולה:
Total points (all tasks): 26.0
לעתים קרובות יש צורך לקבץ אלמנטים לפי קריטריון מסוים. הדוגמה מדגימה כיצד שרשורים יכולים לעזור בכך.
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
הפלט של המסוף יהיה כדלקמן:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
לסיום עם דוגמאות הבעיות, הבה נחשב את האחוז הכולל (או המשקל) של כל בעיה באוסף על סמך סך הנקודות:
// Подсчет веса каждой задачи (How процент от общего количества очков)
final Collection<String> result = tasks
    .stream()                                        // Stream<String>
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream<Double>
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream<String>
    .collect( Collectors.toList() );                 // List<String>

System.out.println( result );
פלט הקונסולה יהיה כך:
[19%, 50%, 30%]
לבסוף, כפי שציינו קודם לכן, ה-API של Stream לא מיועד רק לאוספי Java. פעולת I/O טיפוסית, כגון קריאת קבצי טקסט שורה אחר שורה, היא מועמדת טובה מאוד לשימוש בעיבוד זרם. הנה דוגמה קטנה כדי להוכיח זאת.
final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
השיטה onConsole, שנקראת על שרשור, מחזירה שרשור שווה עם מטפל פרטי נוסף. המטפל הפרטי נקרא כאשר מתודה close()נקראת על שרשור. ה-Stream API יחד עם lambdas ושיטות התייחסות יחד עם שיטות ברירת מחדל וסטטיות ב-Java 8 הם התשובה לפרדיגמות פיתוח תוכנה מודרניות. למידע מפורט יותר, עיין בתיעוד הרשמי .

5.3. API של תאריך/שעה (JSR 310)

Java 8 מביאה מראה חדש לניהול תאריך וזמן על ידי מתן API חדש לתאריך ושעה (JSR 310) . מניפולציה של תאריך ושעה היא אחת מנקודות הכאב הקשות ביותר עבור מפתחי Java. הסטנדרט java.util.Dateהבא java.util.Calendarלא שיפר את המצב באופן כללי (אולי אפילו הפך אותו לבלבל יותר). כך נולדה Joda-Time : אלטרנטיבה מצוינת ל-API של תאריך/שעה עבור Java . ה-Date/Time API החדש ב-Java 8 (JSR 310) מושפע מאוד מג'ודה-טיים ולוקח ממנו את המיטב. החבילה החדשה java.timeמכילה את כל המחלקות לתאריך, שעה, תאריך/שעה, אזורי זמן, משכי זמן ומניפולציה של זמן . עיצוב ה-API לקח את חוסר השינוי ברצינות רבה: שינויים אינם מותרים (לקח קשה שנלמד מ java.util.Calendar). אם נדרש שינוי, יוחזר מופע חדש של המחלקה המתאימה. בואו נסתכל על הכיתות העיקריות ודוגמאות לשימוש בהן. המחלקה הראשונה Clock, המספקת גישה לרגע הנוכחי, לתאריך ולשעה באמצעות אזור זמן. Clockניתן להשתמש במקום System.currentTimeMillis()ו TimeZone.getDefault().
// Получить системное время How смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
פלט מסוף לדוגמה:
2014-04-12T15:19:29.282Z
1397315969360
שיעורים חדשים נוספים שנבחן הם LocaleDateו LocalTime. LocaleDateמכיל רק את חלק התאריך ללא אזור הזמן במערכת לוח השנה ISO-8601. בהתאם לכך, LocalTimeהוא מכיל רק חלק מקוד הזמן>.
// получить местную date и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// получить местную date и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

System.out.println( time );
System.out.println( timeFromClock );
פלט מסוף לדוגמה:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTimeמשרשרת LocaleDateומכילה LocalTimeתאריך ושעה, אך ללא אזור זמן, במערכת לוח השנה ISO-8601. דוגמה פשוטה ניתנת להלן.
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );
System.out.println( datetimeFromClock );
פלט מסוף לדוגמה:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
במקרה שאתה צריך תאריך/שעה עבור אזור זמן מסוים, ZonedDateTime. הוא מכיל את התאריך והשעה במערכת לוח השנה ISO-8601. הנה כמה דוגמאות לאזורי זמן שונים.
// Получение даты/времени для временной зоны
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
פלט מסוף לדוגמה:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
ולבסוף, בואו נסתכל על הכיתה Duration: טווח זמן בשניות וננו-שניות. זה הופך את החישוב בין שני תאריכים לפשוט מאוד. בוא נראה איך עושים את זה:
// Получаем разницу между двумя датами
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
הדוגמה שלמעלה מחשבת את משך הזמן (בימים ובשעות) בין שני תאריכים, 16 באפריל 2014 ו -16 באפריל 2015 . הנה דוגמה לפלט המסוף:
Duration in days: 365
Duration in hours: 8783
הרושם הכללי של התאריך/שעה החדשים ב-Java 8 הוא מאוד מאוד חיובי. חלקית משום שהשינויים מבוססים על בסיס שנבדק בקרב (Joda-Time), חלקית משום שהפעם הנושא נשקל מחדש ברצינות וקולות המפתחים נשמעו. לפרטים, עיין בתיעוד הרשמי .

5.4. מנוע JavaScript של Nashorn

Java 8 מגיע עם מנוע ה-JavaScript החדש של Nashorn , המאפשר לך לפתח ולהפעיל סוגים מסוימים של יישומי JavaScript ב-JVM. מנוע ה-JavaScript של Nashorn הוא פשוט יישום נוסף של javax.script.ScriptEngine שעוקב אחר אותה מערכת כללים כדי לאפשר אינטראקציה בין Java ו-JavaScript. הנה דוגמה קטנה.
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );

System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
פלט מסוף לדוגמה:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

5.5. בסיס 64

לבסוף, תמיכה בקידוד Base64 מצאה את דרכה לספריית הסטנדרטית של Java עם שחרורו של Java 8. זה מאוד קל לשימוש, הדוגמה מדגימה זאת.
package com.javacodegeeks.java8.base64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String(
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}
פלט המסוף של התוכנית מציג טקסט מקודד ומפוענח כאחד:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
יש גם שיעורים למקודדים/מפענחים ידידותיים ל-URL, כמו גם למקודדים/מפענחים ידידותיים למיייטים ( Base64.getUrlEncoder()/ Base64.getUrlDecoder(), Base64.getMimeEncoder()/ Base64.getMimeDecoder()).

5.6. מערכים מקבילים

מהדורת Java 8 מוסיפה שיטות חדשות רבות לעיבוד מערך מקביל. אולי החשוב שבהם הוא parallelSort(), שיכול להאיץ מאוד את המיון במכונות מרובות ליבות. הדוגמה הקטנה להלן מדגימה את משפחת השיטות החדשה ( parallelXxx) בפעולה.
package com.javacodegeeks.java8.parallel.arrays;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];

        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();

        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}
פיסת קוד קטנה זו משתמשת בשיטה parallelSetAll()למילוי מערך ב-20,000 ערכים אקראיים. אחרי זה הוא מוחל parallelSort(). התוכנית מדפיסה את 10 האלמנטים הראשונים לפני ואחרי המיון כדי להראות שהמערך אכן ממוין. פלט תוכנית לדוגמה עשוי להיראות כך (שים לב שהרכיבים של המערך הם אקראיים).
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

5.7. מַקבִּילוּת

שיטות חדשות נוספו למחלקה java.util.concurrent.ConcurrentHashMapכדי לתמוך בפעולות מצטברות המבוססות על אובייקטי הזרם החדשים שנוספו וביטויי למבדה. כמו כן, נוספו לכיתה שיטות חדשות java.util.concurrent.ForkJoinPoolכדי לתמוך בגיבוש משותף (ראה גם את הקורס החינמי שלנו בנושא ג'אווה במקביל ). מחלקה חדשה java.util.concurrent.locks.StampedLockנוספה כדי לספק נעילה מבוססת יכולות עם שלושה מצבי גישה לבקרת קריאה/כתיבה (ניתן להתייחס אליה כאלטרנטיבה טובה יותר לזו הלא טובה java.util.concurrent.locks.ReadWriteLock). כיתות חדשות שנוספו לחבילה java.util.concurrent.atomic:
  • מצבר כפול
  • DoubleAdder
  • מצבר ארוך
  • LongAdder

6. תכונות חדשות בסביבת זמן ריצה של Java (JVM)

האזור PermGenהוצא לגמלאות והוחלף על ידי Metaspace (JEP 122). אפשרויות JVM -XX:PermSizeוהוחלפו -XX:MaxPermSizeב- -XX:MetaSpaceSizeובהתאמה -XX:MaxMetaspaceSize.

7. מסקנה

העתיד כבר כאן: Java 8 הזיזה את הפלטפורמה שלה קדימה על ידי אספקת תכונות המאפשרות למפתחים להיות פרודוקטיביים יותר. זה עדיין מוקדם מדי להעביר מערכות ייצור ל-Java 8, אבל האימוץ אמור להתחיל לצמוח לאט במהלך החודשים הקרובים. עם זאת, עכשיו זה הזמן להתחיל להכין את בסיס הקוד שלך לתאימות Java 8 ולהיות מוכן לשלב שינויים ב-Java 8 כאשר הוא מאובטח ויציב מספיק. כהוכחה לקבלה של הקהילה עם Java 8, Pivotal הוציאה לאחרונה את Spring Framework עם תמיכה בייצור עבור Java 8 . אתה יכול לספק את המשוב שלך לגבי התכונות החדשות המרגשות ב-Java 8 בהערות.

8. מקורות

כמה משאבים נוספים הדנים בהיבטים שונים של תכונות Java 8 לעומק:
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION