JavaRush /Java Blog /Random-KO /Java 8 기능 – 최종 가이드(2부)
0xFF
레벨 9
Донецк

Java 8 기능 – 최종 가이드(2부)

Random-KO 그룹에 게시되었습니다
Java 8 기능 – ULTIMATE 가이드 기사 번역의 두 번째 부분입니다 . 첫 번째 부분은 여기에 있습니다 (링크는 변경될 수 있음). Java 8 기능 – 최종 가이드(2부) - 1

5. Java 8 라이브러리의 새로운 기능

Java 8에서는 최신 동시성, 함수형 프로그래밍, 날짜/시간 등을 더 잘 지원하기 위해 많은 새로운 클래스를 추가하고 기존 클래스를 확장했습니다.

5.1. 수업 선택사항

유명한 NullPointerException은 Java 애플리케이션 오류의 가장 일반적인 원인입니다. 오래전 Google의 우수한 프로젝트인 Guava가Optional 솔루션으로 제시되어 NullPointerExceptionnull 검사로 인해 코드가 오염되는 것을 방지하고 결과적으로 더 깔끔한 코드 작성을 장려했습니다. Google에서 영감을 받은 Guava 클래스는 Optional이제 Java 8의 일부입니다. Optional이는 단순한 컨테이너입니다. 이나 일부 유형을 포함하거나 Т단순히 null일 수 있습니다. 명시적인 null 검사가 더 이상 정당화되지 않도록 많은 유용한 방법을 제공합니다. 자세한 내용은 공식 문서를 참조하세요 . null이 있는 경우와 없는 경우 의 두 가지 작은 사용 예를 살펴보겠습니다 Optional.
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!" ) );
이 메서드는 인스턴스에 null이 아닌 값이 포함되어 있으면 true를isPresent() 반환 하고 그렇지 않으면 false를 반환합니다 . 이 메서드에는 null이 포함된 경우 결과에 대한 대체 메커니즘이 포함되어 있으며 기본값을 생성하는 함수를 허용합니다. 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 개발자가 훨씬 더 효율적으로 작업할 수 있도록 하고 효율적이고 깨끗하며 간결한 코드를 작성할 수 있게 해줍니다. 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 );
        }
    }
}
작업에는 점수(또는 유사 난이도)가 있으며 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 이전에는 이에 대한 일반적인 솔루션은 iterator 를 사용하는 것이었습니다 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
여기서 무슨 일이 일어나고 있는지 살펴 보겠습니다. 먼저 작업 컬렉션이 스트리밍 표현으로 변환됩니다. 그런 다음 작업은 CLOSEDfilter 상태 의 모든 작업을 필터링합니다 . 다음 단계에서 작업은 각 인스턴스에 대한 메서드를 사용하여 스트림 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 컬렉션에만 사용되는 것이 아닙니다. 텍스트 파일을 한 줄씩 읽는 것과 같은 일반적인 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()스레드에서 메소드가 호출될 때 호출됩니다. Java 8의 기본 및 정적 메서드 와 함께 람다 및 참조 메서드와 함께 Stream API는 현대 소프트웨어 개발 패러다임에 대한 답입니다. 자세한 내용은 공식 문서를 참고하세요 .

5.3. 날짜/시간 API(JSR 310)

Java 8은 새로운 날짜 및 시간 API(JSR 310)를 제공하여 날짜 및 시간 관리에 새로운 모습을 제공합니다 . 날짜 및 시간 조작은 Java 개발자에게 가장 큰 어려움 중 하나입니다. java.util.Date다음 표준은 java.util.Calendar일반적으로 상황을 개선하지 못했습니다(어쩌면 상황을 더 혼란스럽게 만들 수도 있음). 이것이 바로 Joda-Time이 탄생한 방법입니다. Java를 위한 훌륭한 날짜/시간 API 대안입니다 . Java 8(JSR 310)의 새로운 날짜/시간 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타임코드>의 일부만 포함됩니다.
// получить местную 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년 4월 16일과 2015년 4 월 16일 두 날짜 사이의 기간(일 및 시간)을 계산합니다 . 다음은 콘솔 출력의 예입니다.
Duration in days: 365
Duration in hours: 8783
Java 8의 새로운 날짜/시간에 대한 전반적인 인상은 매우 긍정적입니다. 부분적으로는 변경 사항이 전투 테스트를 거친 기반(Joda-Time)을 기반으로 했기 때문이고, 부분적으로는 이번에 문제가 심각하게 재검토되고 개발자의 목소리가 반영되었기 때문입니다. 자세한 내용은 공식 문서를 참조하세요 .

5.4. 나스호른 자바스크립트 엔진

Java 8에는 새로운 Nashorn JavaScript 엔진이 포함되어 있어 JVM에서 특정 유형의 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. Base64

마지막으로, Base64 인코딩에 대한 지원은 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 );
    }
}
프로그램의 콘솔 출력에는 인코딩된 텍스트와 디코딩된 텍스트가 모두 표시됩니다.
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
URL 친화적인 인코더/디코더와 MIME 친화적인 인코더/디코더( 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 동시성에 대한 무료 강좌 도 참조하세요 ). 읽기/쓰기 제어를 위한 세 가지 액세스 모드를 갖춘 기능 기반 잠금을 제공하기 위해 새로운 클래스가 java.util.concurrent.locks.StampedLock추가되었습니다(별로 좋지 않은 클래스에 대한 더 나은 대안으로 간주될 수 있음 java.util.concurrent.locks.ReadWriteLock). 패키지에 추가된 새 클래스 java.util.concurrent.atomic:
  • DoubleAccumulator
  • 이중가산기
  • LongAccumulator
  • 장가산기

6. JVM(Java Runtime Environment)의 새로운 기능

이 영역은 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은 최근 Java 8에 대한 프로덕션 지원이 포함된 Spring Framework를 출시했습니다 . 댓글을 통해 Java 8의 흥미로운 새 기능에 대한 의견을 제공할 수 있습니다.

8. 출처

Java 8 기능의 다양한 측면을 심층적으로 논의하는 추가 리소스는 다음과 같습니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION