Java 8 버전에 추가된 주요 기능

calis_ws·2024년 1월 19일
0
post-custom-banner

Lambda 표현식

코드를 간결하게 만들고, 함수형 프로그래밍 스타일을 도입하여 개발자가 더 효과적으로 코드를 작성할 수 있도록 하기 위해 추가되었다.

반복 코드를 줄이고, 더 간단하게 로직을 표현할 수 있어 코드 이해가 쉬워진다.

// 기존의 방식
반환티입 메소드명 (매개변수, ...) {
	실행문
}

// 예시
public String hello() {
    return "Hello World!";
}
// 람다 방식
(매개변수, ... ) -> { 실행문 ... }

// 예시
() -> "Hello World!";

하지만 디버깅이 어렵고, 남발하게 되면 비슷한 함수가 중복 생성되어 코드가 지저분해질 수 있다는 단점이 있다. (무조건 좋은건 아님)

Default Method

자바8 이전에는 오직 추상메소드만 선언할 수 있었다.
인터페이스는 호환성과 유연성을 높이고, 코드의 재사용성과 확장성을 높이는데 목적이 있다.
하지만 상황에 따라 인터페이스에 모든 추상메서드를 구현해야 했으므로 중복된 코드와 간결해지기 힘들었다.

default method를 만든이유?
'하위 호환성' 때문이다. 많은 사람이 사용하고 있는 인터페이스에 새로운 메소드를 추가해야 할 때 기존 방식대로 추가하면 이미 사용하고 있는 사람들은 전부 오류가 발생하고 수정해야하는 일이 발생한다. 이럴 때 사용하는 것이 default 메소드다.
+ Lambda 표현식을 사용하기 위해서 만들어졌다.(lambda 표현식을 사용하려면 메소드가 1개여야 하는데 그 이외의 메소드는 default method로 구성되게 만들면 되기 때문이다)

함수형 인터페이스

함수형 프로그래밍 패러다임을 지원하며, 데이터를 다루는데 효과적인 방법을 제공하기 위해 추가되었다.

간단한 문법으로 데이터를 처리하고 조작할 수 있으며, 병렬 처리를 쉽게 수행할 수 있어 성능을 향상시킬 수 있다.

람다식으로 순수 함수를 선언하지만 순수 함수와 일반 함수를 다르게 취급한다. 이를 구분하기 위해 함수형 인터페이스가 등장하게 되었다.

함수를 1급 객체처럼 다룰수 있게 해주는 어노테이션으로, 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할을 한다.

// 대표적인 함수형 인터페이스 Comparator
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    
    boolean equals(Object obj);
    
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    // ...
}
  • @FunctionalInterface : 이 인터페이스가 함수형 인터페이스라는 걸 알려주는 어노테이션이다. 인터페이스 위에 이걸 붙이고 2개 이상의 추상 메서드를 선언하려 하면 컴파일러가 에러를 띄워준다. 어노테이션은 컴파일러에게 정보를 주는 표식일 뿐, 이걸 붙이지 않았다고 해도 추상 메서드 1개인 인터페이스면 함수형 인터페이스다.

  • compare() : 인터페이스 안에 선언한 메서드고 body가 없으므로 이것은 추상 메서드이며, '이 인터페이스는 이 작업을 위해 존재하는 것입니다, body는 여러분이 작성하세요' 라는 뜻이다.

  • default : Java 8 이전까지 인터페이스는 추상 메서드만 가져야 했으나, Java 8에서는 default라는 키워드를 붙이면 인터페이스 내의 메서드도 body를 가질 수 있게 된다. 이를 디폴트 메서드라 하며, 디폴트 메서드는 추상 메서드가 아니므로 함수형 인터페이스의 정의와는 관계가 없다.

Predicate<T>, Consumer<T>, Supplier, Function<T, R> ...

Stream

자바의 스트림은 필터링, 변환 등 다양한 방식으로 배열이나 컬렉션 (List, Map 등) 을 반복적으로 처리하는 데 사용한다.

기존에 for 문을 돌리면서 처리하던 것을 Stream으로 변환하고, mapping, sorting, filtering 등 을 메서드로 할 수 있게 해준다.

int passedStudents = 0;
for (Student s: students) {
	if (s.score >= 80)
    	passedStudents++;
}

// stream
int passedStudents = (int) students.stream().filter(s -> s.score >= 80).count();

Optional

객체를 편리하게 처리하기 위해서 만든 클래스이다. NPE 처리를 보다 간편하게 하기 위해서 만들어졌다.

List<String> names = getNames();
names.sort(); // names가 null이라면 NPE가 발생함

List<String> names = getNames();
// NPE를 방지하기 위해 null 검사를 해야함
if(names != null){
    names.sort();
}

Optional<T> 클래스는 Null이 올 수 있는 참조 객체를 Wrapping 하는 Wrapper 클래스이다.

// 객체의 선언
Optional<SampleObject> sampleObject;
Optional<String> optioanlText;

// 객체 생성
Optional<String> emptyObject = Optional.empty(); // null 값으로 초기화
Optional<String> textObject1 = Optional.of("Text"); // null이 아닌 경우
Optional<String> textObject2 = Optional.ofNullable(getText()); // null 또는 객체가 있을 수 있는 경우

filter

predicate를 사용하며, 해당 값이 참이면 필터를 통과시킨다.

Optional<SampleObject> sampleObject1 = Optional.of(getObject())
	.filter(object -> object.getName().equals("flature"));

Optional<SampleObject> sampleObject2 = Optional.of(getObject())
	.filter(object -> object.getName().equals("flature123"));

map

function을 사용하며, 입력값을 다른 값으로 변환하는 기능을 제공한다.

Optional<String> StringObject1 = Optional.of(getObject())
	.map(SampleObject -> sampleObject.getName());

isPresent / isEmpty

isPresent는 최종적으로 연산이 끝나고 객체가 존재하는지 확인하는 기능을 제공한다.
isEmptyisPresent와 반대로 객체가 존재하지 않으면 true를 리턴한다.

// isPresent
if (Optional.ofNullable(getObject()).isPresent()) {
	System.out.println("is present");
}

// isEmpty
if (Optional.ofNullable(getObject()).isEmpty()) {
	System.out.println("is empty");
}

get

최종적으로 나온 객체를 Optional 에서 꺼내는 기능을 제공한다.
객체가 존재하지 않다면 예외 발생

Optional<SampleObject> sampleObject3 = Optional.of(getObject());
System.out.println("is empty");

이외에도 flatmap, orElse, orElseGet, ifPresent ...

날짜 관련 클래스 추가

기존 Date 및 Calendar 클래스의 단점을 보완하고, 날짜 및 시간을 더 유연하게 다룰 수 있도록 하기 위해 추가되었다.

보다 직관적이고 사용하기 쉬운 API를 통해 날짜와 시간을 다룰 수 있다. (java.time 패키지)

Calendar, Date 클래스의 문제점

  1. 불변 객체가 아님

    set으로 변경이 가능하다는 점은 누군가 악의적으로 변경할 수 있기 때문에 get/set 메서드에서 직접 Date 클래스를 사용하는 것이 위험하다.

  2. 상수 필드 남용
    calendar.add(Calendar.SECOND, 2);

  3. 헷갈리는 월 지정

    1월을 0으로 표현하는 문제 + Calendar.OCTOBER로 월을 지정하지만 실질적인 값은 9(!=10)인 문제

  4. 일관성 없는 요일 상수

    어디서는 일요일이 0, 어디서는 일요일이 1

  5. Date와 Calendar 객체의 역할 분담

    다소 치명적인데 년/월/일 계산은 Date 클래스만으로는 부족해서 왔다갔다 하는 문제가 있다. 또한 Calendar 객체를 생성하고 Date 객체를 생성하는 프로세스를 거치기 때문에 번거롭고 생성비용이 비싸다.

  6. 기타 java.util.Date 하위 클래스의 문제

LocalDateTime

로컬 PC의 시스템 날짜와 시간을 반환한다.

import java.time.*;

public class LocalDateTimeEx1 {
    public static void main(String[] args) {

        LocalDate localDate = LocalDate.now();
        System.out.println("localDate = " + localDate);
        // localDate = 2022-02-20

        LocalTime localTime = LocalTime.now();
        System.out.println("localTime = " + localTime);
        // localTime = 15:56:51.294833

        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("localDateTime = " + localDateTime);
        // localDateTime = 2022-02-20T15:56:51.295105
    }
}

Instant

EPOCH 부터 경과된 시간을 나노초 단위로 표현한다.

Instant는 항상 UTC(+00:00)을 기준으로 하기 때문에, LocalTime과는 차이가 있을 수 있다. 우리나라의 경우에는 시간대가 '+09:00'이므로 Instant와 LocalTime 간에는 9시간의 차이가 있다.

Instant를 생성할 때는 아래와 같이 now()ofEpochSecond()를 사용한다.

import java.time.Instant;

public class InstantEx1 {
    Instant now = Instant.now();
    Instant now2 = Instant.ofEpochSecond(now.getEpochSecond());
    Instant now3 = Instant.ofEpochSecond(now.getEpochSecond(), now.getNano());

	// 2024-01-18 21:46 기준 출력 결과
    // now = 2024-01-18T12:46:00.787576600Z
    // now2 = 2024-01-18T12:46:00Z
    // now3 = 2024-01-18T12:46:00.787576600Z
}

ZoneDateTime

LocalDateTime 에 시간대를 추가하면 ZoneDateTime이 된다.
새로운 패키지에서는 ZoneId라는 클래스를 사용하는데 ZoneId는 일괄 절약 시간(Daylight Saving Time)을 자동적으로 처리해주어 더 편하다.

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZoneIdEx1 {
    public static void main(String[] args) {
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("localDateTime = " + localDateTime);
        // localDateTime = 2022-02-20T16:00:26.857433

        ZoneId zid = ZoneId.of("Asia/Seoul");
        ZonedDateTime zdt = localDateTime.atZone(zid);
        System.out.println("ZonedDateTime = " + zdt);
        // ZonedDateTime = 2022-02-20T16:00:26.857433+09:00[Asia/Seoul]
    }
}
  • 해당 서버 기준 시스템 시간을 가져올 때
    -> LocalDateTime 클래스 사용

  • UTC 기준 시간을 가져올 때
    -> Instant 클래스 사용

  • 특정 기준 시의 시간을 가져올 때
    -> ZonedDateTime 클래스 사용

병렬 배열 정렬

java 8 에서는 parallelSort()라는 정렬 메소드가 제공되며, Fork-Join 프레임워크가 내부적으로 사용된다.

일반적인 sort()의 경우 단일 쓰레드로 수행되며, parallelSort()는 필요에 따라 여러 개의 쓰레드로 나뉘어 작업이 수행된다. CPU를 더 많이 사용하게 되겠지만 처리속도는 더 빠르다.

int[] intValues = new int[10];
// 배열 값 지정
Arrays.parallelSort(intValue);

String Joiner

문자열 처리 class 중에서 순차적으로 나열되는 문자열을 처리할 때 사용된다. (java.util)

String[] stringArray = new String[]{"StudyHard", "GodOfJava", "Book"}
(StudyHard, GodOfJava, Book)
public void joinStringOnlyComma(String[] stringArray){
        StringJoiner joiner = new StringJoiner(",");
        for(String string:stringArray){
            joiner.add(string);
        }
        System.out.println(joiner);
    }
StudyHard,GodOfJava,Book
public void joinString(String[] stringArray){
        StringJoiner joiner = new StringJoiner(",", "(", ")");
        for(String string:stringArray){
            joiner.add(string);
        }
        System.out.println(joiner);
    }

방금 살펴본 예제와 다른 점은 생성자뿐이다. 생성자에 맨 앞에 들어갈 prefix와 뒤에 들어갈 suffix 값을 지정해주면 된다. 결과는 예상한 대로 다음과 같이 출력된다.

(StudyHard,GodOfJava,Book)
// Collectors라는 클래스의 joining 메소드를 사용
public void withCollector(String[] stringArray){
        List<String> stringList = Arrays.asList(stringArray);
        String result = stringList.stream().collect(Collectors.joining(","));
        System.out.println(result);
    }

출처

https://mangkyu.tistory.com/70 [MangKyu's Diary:티스토리]
https://medium.com/@inhyuck/java-8에-추가된-것들-8c66023cbbae

profile
반갑습니다람지
post-custom-banner

0개의 댓글