Java

[Java]Java 8의 특징

DevStory 2022. 9. 26.

Java 8 소개

Java가 유료 선언을 하고 나서 대부분의 개발자들은 JDK 8을 사용하고 있습니다. 정확하게 이야기하자면, JDK 8u202 이하는 무료, JDK 8u211 이상 및 11 이후 버전은 유료입니다.

아이러니하게도 많은 개발자들이 Java 8을 사용하고 있지만, Java 8의 특징을 이해하지 못하고 Java 5(Generics)의 기능까지 사용하는 개발자들이 상당히 많습니다.

대부분의 개발자들이 Java 8의 기능을 사용하지 못하는 여러 가지 이유가 존재하겠지만, 가장 큰 이유는 학습의 부재라고 생각합니다. Java 8에서 가장 큰 변화가 일어났기 때문에 다른 버전에 비해 학습해야 하는 분량이 많기 때문이죠.

그리고 'Java 8을 사용하지 않아도 애플리케이션은 정상적으로 실행되는데, 굳이 Java 8을 배워야 할까?'라는 의문을 가질 수 있습니다. 하지만, Java 8의 기능을 사용하여 코드를 작성하는 개발자와 Java 8 이전 버전의 기능을 사용하여 코드를 작성하는 개발자의 코드는 확연히 차이가 납니다.

이번 포스팅은 Java 8의 탄생 배경과 특징에 대해 이해하기 쉽게 설명할 것이며, 기능적인 부분은 최대한 설명하지 않으려고 합니다. Java 8의 기능까지 설명하면, 포스팅 내용이 워낙 길어질 수 있으므로 기능적인 내용은 추후 포스팅할 예정입니다.


Java 8의 도입

일반적으로 현재 사람들이 사용하는 CPU는 대부분 듀얼 혹은 쿼드 코어 이상일 것입니다. CPU에 코어가 두 개인 경우 듀얼 코어, 네 개인 경우 쿼드 코어라고 이해하면 됩니다.

Java로 개발된 대부분의 프로그램은 여러 개의 코어 중 하나만 사용하므로 나머지 코어들은 아무 일도 안 하는 문제가 발생합니다. 스레드를 활용하면, 나머지 코어들을 활용할 수 있으며, Java로 개발된 프로그램을 좀 더 빠르게 동작하도록 구현할 수 있지만, 스레드를 사용하기에는 개념적으로 알아야 할 내용들이 많고 동시성 이슈가 발생할 수 있으므로 스레드를 활용한 코드를 구현하는 것은 쉽지 않습니다.

나머지 코어들을 쉽게 활용할 수 있도록 Java 5에서 스레드 풀(Thread Pool), 병렬 실행 컬렉션(Concurrent Collection)을 도입했으며, Java 7에서 Fork, Join 프레임워크를 제공했지만, 여전히 개발자가 스레드를 활용하는 코드를 구현하는 것은 쉽지 않았습니다.

그리고 시대가 달라지면서 대용량 데이터를 처리하는 빅데이터 시대가 도래했고 Java에서도 빅데이터를 효과적으로 처리해야 하는 상황이 다가왔습니다. 대용량 데이터를 신속하게 처리하면서 다른 작업을 수행하려면 하나의 코어만 사용하는 것이 아니라 병렬 작업을 통해 멀티코어 CPU를 효과적으로 사용해야만 했습니다.

요약하자면, Java에서 대용량 데이터를 다루기 위해 멀티코어 CPU를 효과적으로 사용해야만 했지만, 개발자가 병렬 작업을 수행하는 코드를 구현하는 것은 어려웠습니다.

따라서, Java 8은 개발자가 쉽게 병렬 작업을 수행할 수 있도록 Stream API, 람다 표현식(Lambda express), 인터페이스의 디폴트 메서드(Default Method of Interface), 메서드 참조(Method Reference) 등 다양한 기능을 제공하였습니다.


특징 1. Stream API

Java 8에는 java.util.stream 패키지에 Stream API가 추가되었습니다. 스트림(Stream)이란 연속적인 데이터 항목들의 모임입니다. List, Map, ArrayList와 같은 컬렉션도 연속적인 데이터 항목들의 모임이지만, 스트림은 질의 구문에 최적화되어 있습니다. 질의 구문은 특정 조건을 만족하는 데이터 집합에서 원하는 결과를 추출하는 방법이라고 생각하면 됩니다.

예를 들어, Person이라는 클래스 인스턴스로 구성된 List에서 Age라는 필드의 값이 20 이상이며, Name이라는 필드의 첫 글자가 "A"로 시작하고 Name 필드의 값이 중복되지 않은 항목으로 구성된 데이터를 추출하고자 합니다.

컬렉션을 사용하는 경우 반복문을 사용하여 위 조건에 맞는 데이터를 추출하기 위해 상당히 많은 조건문을 사용하게 될 것입니다. 하지만, Stream API를 사용하는 경우 List를 스트림으로 변환 후 스트림에서 제공하는 메서드를 사용하여 보다 간결한 코드를 구현할 수 있습니다.


특징 2. 동작 파라미터화

Java 8에 추가된 두 번째 특징은 바로 메서드를 매개변수로 전달하는 것입니다. Java 8 이전에는 객체를 통해 메서드를 매개변수로 전달할 수 있었지만, 재사용하지 않는 불필요한 객체를 생성해야만 했습니다.

이를 해결하고자 Java 8은 코드 일부를 API로 전달하는 기능인 람다 표현식 또는 메서드 참조를 제공합니다.

예를 들어, 문자열 배열을 대소문자 구분 없이 정렬하는 소스 코드는 다음과 같습니다.

public static void main(String args[]) {
  String[] strArr = {"a", "B", "e", "c", "D"};

  Comparator comparator = new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareToIgnoreCase(s2);
    }
  };

  Arrays.sort(strArr, comparator);

  System.out.println("Sorted strArr[] : " + Arrays.toString(strArr));
}

[실행 결과]

Sorted strArr[] : [a, B, c, D, e]

프로그램은 정상적으로 실행되지만, 대소문자 구분 없이 문자열 배열을 정렬하기 위해 Comparator 객체를 구현하는 것은 다소 비효율적으로 보입니다.

하지만, 다음과 같이 메서드 참조를 사용하면, Comparator 객체를 생성하지 않고 대소문자 구분 없이 문자열 배열을 정렬할 수 있습니다.

public static void main(String args[]) {
  String[] strArr = {"a", "B", "e", "c", "D"};

  Arrays.sort(strArr, String::compareToIgnoreCase);

  System.out.println("Sorted strArr[] : " + Arrays.toString(strArr));
}

[실행 결과]

Sorted strArr[] : [a, B, c, D, e]

Java 8 소개에서 Java 8의 기능을 사용하는 개발자의 코드와 Java 8 이전 버전의 기능을 사용하는 개발자의 코드는 확연히 다르다고 말씀드렸습니다.

Java 8의 기능을 활용하면, 코드를 더 간결하게 구현할 수 있습니다.


특징 3. 병렬성과 공유 가변 데이터

Stream API를 사용하여 병렬 처리를 쉽게 수행할 수 있지만, 변경할 수 있는 공유 자원을 접근하는 경우 병렬 처리 작업은 피하는 것이 좋습니다.

예를 들어, 사용자A와 사용자B가 동시에 메모장.txt 파일을 접근하였으며, 사용자A는 메모장.txt 파일의 내용을 수정하였으며, 사용자B는 메모장.txt 파일을 삭제하는 경우 교착 상태(Deadlock)에 빠질 수 있습니다.

Java에서 제공하는 synchronized 키워드를 사용하여 공유 자원을 보호할 수 있지만, 시스템 성능에 악영향을 미치고 스레드가 공유 자원을 순차적으로 접근하기 때문에 병렬이라는 목적을 무력화시킵니다.

정리하자면, Stream API를 사용하여 병렬 처리를 쉽게 구현할 수 있지만, 성능적인 관점에서 만능은 아니라는 것입니다.


정리

  • Java 8의 기능을 사용하여 스레드로 구현하기 어려웠던 작업을 쉽게 구현할 수 있습니다.
  • 질의 구문에 특화된 Stream API를 활용하여 원하는 조건의 데이터를 쉽게 추출할 수 있습니다.
  • 람다 표현식 또는 메서드 참조 기능을 사용하면, 메서드를 구현하기 위해 객체를 생성하지 않아도 됩니다.
  • Stream API를 사용하여 병렬 처리를 쉽게 구현할 수 있지만, 성능적인 관점에서 만능은 아닙니다.
반응형

댓글