JavaRush /Java блогы /Random-KK /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 қолданбасының ақауларының ең көп тараған себебі болып табылады. Ұзақ уақыт бұрын Google-дың GuavaOptional тамаша жобасы шешім ретінде ұсынылды NullPointerException, осылайша codeты нөлдік тексерулермен ластанудан сақтайды және нәтижесінде таза codeты жазуды ынталандырады. Google шабыттандырған Guava класы Optionalқазір Java 8 бөлігі болып табылады. OptionalБұл жай ғана контейнер: ол мәнді немесе кейбір түрін қамтуы мүмкін Тнемесе жай нөл болуы мүмкін. Ол көптеген пайдалы әдістерді қамтамасыз етеді, осылайша анық нөлдік тексерулер бұдан былай негізделмейді. Толық ақпарат алу үшін ресми құжаттаманы қараңыз . Қолданудың екі шағын мысалын қарастырайық 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!" ) );
Егер данада нөлдік емес мән болса , әдіс " true"isPresent() мәнін қайтарады , ал әйтпесе жалған . Әдіс әдепкі мәнді жасау үшін функцияларды қабылдайтын нөлді қамтыса, нәтиже үшін қалпына келтіру механизмін қамтиды . map () әдісі ағымдағы мәнді түрлендіреді және жаңа дананы қайтарады . Әдіс -ге ұқсас , бірақ функцияның орнына ол әдепкі мәнді қабылдайды. Міне, осы бағдарламаның нәтижесі: OptionalorElseGet()OptionalOptionalOptionalorElse()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 әзірлеушілеріне айтарлықтай тиімдірек болуға мүмкіндік береді, сонымен қатар оларға тиімді, таза және қысқа codeты жасауға мүмкіндік береді. Stream API өңдеу жинақтарын әлдеқайда жеңілдетеді (бірақ олармен шектелмейді, біз кейінірек көреміз). Мысал ретінде қарапайым сыныпты алайық 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 );
        }
    }
}
Тапсырмада кейбір нүктелер (немесе жалған қиындықтар) бар және АШЫҚ немесе ЖАБУ болуы мүмкін . Ойынға арналған есептердің шағын жинағын енгізейік.
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
Білгіміз келетін бірінші сұрақ: АШЫҚ тапсырмалары қазіргі уақытта қанша ұпайды қамтиды? 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 күйі бар барлық тапсырмаларды сүзеді . Келесі қадамда операция әрбір данаға арналған әдісті пайдаланып s ағынын s ағынына түрлендіреді . Соңында, соңғы нәтижені қамтамасыз ететін әдіс арқылы барлық ұпайлар жинақталады . Келесі мысалдарға көшпес бұрын, есте сақтау керек ағындар туралы кейбір ескертулер бар (толығырақ осында ). Операциялар аралық және соңғы операциялар болып бөлінеді . Аралық операциялар жаңа ағынды қайтарады. Олар әрқашан жалқау; сияқты аралық операцияларды орындаған кезде олар іс жүзінде сүзгілеуді орындамайды, оның орнына жаңа ағын жасайды, ол аяқталғаннан кейін берілген предикатқа сәйкес келетін бастапқы ағынның элементтерін қамтиды. Нәтижені немесе жанама әсерді шығару үшін және сияқты соңғы операцияларды ағын арқылы өткізуге болады. Соңғы операция аяқталғаннан кейін ағын пайдаланылған болып саналады және оны қайта пайдалану мүмкін емес. Барлық дерлік жағдайларда соңғы әрекеттер негізгі деректер көзі арқылы өтуді аяқтауға бейім. Жіптердің тағы бір құнды қасиеті қораптан тыс параллель процестерді қолдау болып табылады. Барлық есептердің ұпайларының қосындысын табатын мына мысалды қарастырайық. mapToIntTaskIntegerTask::getPointsTasksumstreamfilterforEachsum
// 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%]
Соңында, біз бұрын атап өткендей, Stream API тек Java жинақтарына арналған емес. Мәтіндік файлдарды жол бойынша оқу сияқты әдеттегі енгізу/шығару операциясы ағынды өңдеуді пайдалану үшін өте жақсы үміткер болып табылады. Мұны дәлелдеу үшін шағын мысал келтірейік.
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, ламбдалармен және анықтамалық әдістермен , Java 8-дегі әдепкі және статикалық әдістермен бірге заманауи бағдарламалық жасақтаманы әзірлеу парадигмаларына жауап болып табылады. Толық ақпарат алу үшін ресми құжаттаманы қараңыз .

5.3. Date/Time API (JSR 310)

Java 8 жаңа Date and Time API (JSR 310) ұсыну арқылы күн мен уақытты басқаруға жаңа көрініс береді . Күн мен уақытты манипуляциялау Java әзірлеушілері үшін ең ауыр мәселелердің бірі болып табылады. java.util.DateТөмендегі стандарт java.util.Calendarжағдайды жақсартпады (мүмкін оны одан сайын шатастырған болуы мүмкін). Joda-Time осылай дүниеге келді: Java үшін тамаша күн/уақыт API балама . Java 8 (JSR 310) жүйесіндегі жаңа Date/Time API интерфейсіне Joda-Time қатты әсер етеді және одан ең жақсысын алады. Жаңа бумада күн, уақыт, күн/уақыт, уақыт белдеулері, ұзақтықтар және уақытты басқаруға арналған барлық сыныптарjava.time бар . API дизайны өзгермейтіндікке өте байыпты қарады: өзгертулерге жол берілмейді (өткен қиын сабақ ). Егер өзгерту қажет болса, сәйкес сыныптың жаңа данасы қайтарылады. Негізгі сыныптарды және оларды пайдалану мысалдарын қарастырайық. Уақыт белдеуін пайдалану арқылы ағымдағы лезде, күн мен уақытқа қол жеткізуді қамтамасыз ететін бірінші сынып . және орнына қолдануға болады . java.util.CalendarClockClockSystem.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. LocaleDateISO-8601 күнтізбе жүйесінде уақыт белдеуі жоқ күн бөлігін ғана қамтиды. Тиісінше, LocalTimeол уақыт codeын> бір бөлігін ғана қамтиды.
// получить местную 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
LocalDateTimeISO-8601 күнтізбе жүйесінде күн мен уақытты біріктіреді LocaleDateжәне қамтиды, бірақ уақыт белдеуі жоқ. LocalTimeТөменде қарапайым мысал келтірілген.
// 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() );
Жоғарыдағы мысал екі күн, 2014 жылдың 16 сәуірі мен 2015 жылдың 16 сәуірі арасындағы ұзақтықты (күндер мен сағаттармен) есептейді . Мұнда консоль шығысының мысалы келтірілген:
Duration in days: 365
Duration in hours: 8783
Java 8-дегі жаңа күн/уақыт туралы жалпы әсер өте жақсы. Ішінара өзгерістер шайқаста сыналған негізге (Joda-Time) негізделгендіктен, ішінара бұл жолы мәселе байыпты түрде қайта қаралып, әзірлеушілердің дауыстары естілгендіктен. Толық ақпаратты ресми құжаттамадан қараңыз .

5.4. Nashorn JavaScript қозғалтқышы

Java 8 JVM жүйесінде JavaScript қолданбаларының белгілі бір түрлерін әзірлеуге және іске қосуға мүмкіндік беретін жаңа Nashorn JavaScript қозғалтқышымен бірге келеді . Nashorn JavaScript қозғалтқышы Java және JavaScript өзара әрекеттесуіне мүмкіндік беретін бірдей ережелер жинағына бағынатын javax.script.ScriptEngine бағдарламасының жай ғана басқа іске асырылуы болып табылады. Міне, шағын мысал.
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 codeтамасын қолдау Java 8 шығарылымымен Java стандартты кітапханасына жол тапты. Оны пайдалану өте оңай, мысал мұны көрсетеді.
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 );
    }
}
Бағдарламаның консольдық шығысы codeталған және деcodeталған мәтінді көрсетеді:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
Сондай-ақ URL мекенжайына қолайлы codeтаушылар/деcodeерлер, сондай-ақ MIME-codeерлер/деcodeерлер ( Base64.getUrlEncoder()/ Base64.getUrlDecoder(), Base64.getMimeEncoder()/ Base64.getMimeDecoder()) үшін сыныптар бар.

5.6. Параллель массивтер

Java 8 шығарылымы параллельді массивтерді өңдеудің көптеген жаңа әдістерін қосады. Мүмкін, олардың ең маңыздысы parallelSort()көп ядролы машиналарда сұрыптауды айтарлықтай жылдамdateы мүмкін. 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()Бұл кішкене code бөлігі 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 concurrency бойынша тегін курсымызды қараңыз ). Оқу/жазуды басқару үшін үш қатынас режимі бар мүмкіндіктерге негізделген құлыптауды қамтамасыз ету үшін жаңа сынып java.util.concurrent.locks.StampedLockқосылды (оны соншалықты жақсы емес нұсқаға жақсы балама ретінде қарастыруға болады java.util.concurrent.locks.ReadWriteLock). Пакетке қосылған жаңа сыныптар java.util.concurrent.atomic:
  • Қосарланған аккумулятор
  • DoubleAdder
  • Ұзын аккумулятор
  • LongAdder

6. Java орындалу ортасындағы жаңа мүмкіндіктер (JVM)

Аймақ PermGenжұмыстан шығарылды және оның орнына Metspace (JEP 122) ауыстырылды. JVM опциялары -XX:PermSizeжәне сәйкесінше және -XX:MaxPermSizeауыстырылды . -XX:MetaSpaceSize-XX:MaxMetaspaceSize

7. Қорытынды

Болашақ осында: Java 8 әзірлеушілерге өнімдірек болуға мүмкіндік беретін мүмкіндіктерді ұсыну арқылы платформасын алға жылжытты. Өндірістік жүйелерді Java 8 нұсқасына көшіру әлі ерте, бірақ қабылдау келесі бірнеше айда баяу өсе бастауы керек. Дегенмен, қазір Java 8 үйлесімділігі үшін code базасын дайындауды бастау және ол жеткілікті қауіпсіз және тұрақты болған кезде Java 8 өзгерістерін енгізуге дайын болу уақыты. Қауымдастықтың Java 8 нұсқасын қабылдағанының куәсі ретінде Pivotal жақында Java 8 үшін өндірістік қолдауы бар Spring Framework шығарды . Java 8-дегі қызықты жаңа мүмкіндіктер туралы өз пікіріңізді түсініктемелерде бере аласыз.

8. Дереккөздер

Java 8 мүмкіндіктерінің әртүрлі аспектілерін тереңірек талқылайтын кейбір қосымша ресурстар:
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION