본문 바로가기
  • 기업가 장준영
Java_2

Java 8_1

by 장준영 2020. 6. 22.

 

 

Java 8에서 추가되거나 변경된 것들 그 중에서 꼭 알아둬야 하는것들을 나열해 보면 다음과 같다

  • Lambda(람다) 표현식
  • Functional(함수형) 인터페이스
  • Stream(스트림)
  • Optional(옵셔널)
  • 인터페이스의 기본 메소드(Default method)
  • 날짜 관련 클래스들 추가
  • 병렬 배열 정렬
  • StringJoiner 추가

 

 

자바로 오랫동안 일하신 분들도 Java 8에서 추가되거나 달라진 부분에 대해서 쉽게 접하기 어렵다

 

왜냐하면, 람다나 스트림을 사용하면 코드의 포맷이 엄청 새롭게 변경되고

 

Java 8을 사용하여 개발/운영되는 시스템도 일부 개발을 주도한다고 하는 회사를 제외하고는 거의 없기 때문

 

그리고, 이렇게 새로운 문법들은 배워도 지속적으로 사용하지 않으면 잊게되기 때문

 

필자는 이렇기에 더욱 더 Java 8을 잘 알아야한다고 생각한다

 

Java 8_1 에서는

  • Optional(옵셔널)
  • 인터페이스의 기본 메소드(Default method)
  • 날짜 관련 클래스들 추가
  • 병렬 배열 정렬
  • StringJoiner 추가

Java 8_2 에서는 

  • Lambda(람다) 표현식
  • Functional(함수형) 인터페이스
  • Stream(스트림)

을 정리했다

 

 


 

 

Optional

 

Optional "선택적인"이라는 의미다

 

선택적으로 뭔가를 처리할 때 사용한다는 것을 예상할 수 있을 것이다

 

Optional은 Funtional 언어인 Haskell과 Scala에서 제공하는 기능을 따 온 것이다

 

즉, 객체를 편리하게 처리하기 위해서 만든 클래스라고 보면 된다

 

Optional 클래스는 java.util 패키지에 속해 있다

 

Optional 클래스의 선언 부분을 보자 

public final class Optional<T> extends Object

 

Optional 클래스에 대해서 이해하려면 Optional 클래는 하나의 깡통이라고 생각하면 된다

 

이 깡통에 물건을 넣을 수도 있고, 아무런 물건이 없을 수도 있다

 

기본적인 깡통을 만들기 위해서 Optional 클래스는 new Optional();과 같이 객체를 생성하지 않는다

 

API 문서를 살펴보면 Optional 클래스를 리턴하는 empty(), of(), ofNullable() 메소드들이 존재한다

 

이 메소드들을 사용하여 Optional 객체를 생성해보자

private void createOptionalObjects(){

    Optional<String> emptyString = Optional.empty();

    String common = null;

    Optional<String> nullableString = Optional.ofNullable(common);

    common = "common";

    Optional<String> commonString = Optional.of(common);

}

데이터가 없는 Optional 객체를 생성하려면 empty() 메소드를 사용

만약 null이 추가될 수 있는 상황이라면 ofNullable() 메소드를 사용

반드시 데이터가 들어갈 수 있는 상황에는 of() 메소드를 사용

 

 

 

Optional 클래스가 비어 있는지 확인하는 메소드는 isEmpty가 아닌 isPresent() 메소드다

private viod checkOptionalData(){

    System.out.println(Optional.of("present").isPresent());

    System.out.println(Optional.ofNullable(null).isPresent());

}

클래스가 안 비어있으면 True

비어있으면 false 리턴

 

 

 

 

값을 꺼내는 방법

API 문서에 "T"를 리턴하는 메소드들을 살펴보면 된다

get(), orElse(), orElseGet(), orElseThrow() 

 

private void getOptionalData(Optional<String> data) throw Exception{

    String defaultValue = "default";

    String result1 = data.get();

    String result2 = data.orElse(defaultValue);

    Supplier<String> stringSupplier = new Supplier<String>(){ // 익명 클래스

        @Override

        public String get(){

            return "JunnYeong"

        }

    };

    String result3 = data.orElseGet(StringSupplier);

    Supplier<Exception> exceptionSupplier = new Supplier<Exception>(){ // 익명 클래스

        @Override

        public Exception get(){

            return new Exception();

        }

    };

    String result4 = data.orElseThrow(exceptionSupplier);

}

가장 많이 사용되는 get(), 데이터가 없을 경우에는 null 리턴

만약 값이 없을 경우애는 orElse() 메소드를 사용해 기본값을 지정할 수 있음

Supplier<T> 인터페이스를 활용하는 방법으로 orElseGet()  메소드를 사용한다

만약 데이터가 없을 경우에 예외를 발생시고 싶다면, orElseThrow() 메소드를 사용한다

Exception도 Supplier<T> 인터페이스를 사용한다

 

Supplier<T>는 람다 표현식에서 사용하려는 용도로 만들어 졌으며, get() 매소드가 선언되어 있다

 

 

 

 

언제 Optional 클래스가 필요할까??

 

Optional 클래스는 null 처리를 보다 간편하게 하기 위해서 만들어졌다

 

자칫 잘못하면 NullpointException이 발생할 수도 있는데

 

이 문제를 보다 간편하고 명확하게 처리하려면 Optional을 사용하면 된다

 

단, Optional 클래스에 값을 잘못 넣으면 NoSuchElementException이 발생할 수도 있으니 유의해서 사용해야만 한다

 

 

 


Default method

 

Java 8 부터 default 메소드가 추가됌

 

PreviousInterface 라는 인터페이스가 있다

 

public interface PreviousInterface{

    static final String name = "Junn";

    static final int since = 2000;

    String getName();

    int getSince();

}

 

지금까지 배운 인터페이스와 전혀 다르지 않다

다음 인터페이스를 보자

 

public interface DefaultStaticInterface{

    static final String name = "Junn";

    static final int since = 2000;

    String getName();

    int getSince();

    default String getEmail(){

        retrun name +"helloolleh2153@naver.com";

    }

}

 

지금까지 배운 인터페이스들은 구현된 메소드가 있으면 안 됐다

그런데 이 인터페이스는 default라는 키워드와 함께 메소드가 구현되어 있다

 

Java 8에서는 컴파일이 잘된다

 

이러한 메소드를 default 메소드라고 한다

이 인터페이스를 구현해보자

 

public class DefaultImplementedChild implements DefaultStaticInterface{

 

    @Override

    public String getName(){

        return name;

    }

 

    @Override

    public String getSince(){

        return since;

    }

}

 

왜 default 메소드를 만들었을까??

 

"하위 호완성" 떄문이다.

 

하위 호완성이라는 말이 어려운 사람도 있을 것이다

 

예를 들어 설명하자면, 오픈 소스 코드를 만들었다고 가정하자. 그 오픈 소스가 엄청 유명해져서 전 세계 사람들이

 

다 사용하고 있는데, 인터페이스에 새로운 메소드를 만들어야 하는 상황이 발생했다.

 

자칫 잘못하면 내가 만든 오픈 소스를 사용한 사람들은 전부 오류가 발생하고 수정을 해야 하는 일이 발생할 수도 있다.

 

이럴 때 사용하는 것이 바로 defualt 메소드다

 

이렇게 간단하게 라이브러리를 공유하지 않더라도, 비행기나 자율 주행차에 포함되는 프로그램이 수정되어야 한다면

 

더 심각한 문제를 야기할 수도 있기 때문에 default 메소드가 나왔다고 기억하면 더 이해가 빠를 것이다

 

 

 


 

날짜 관련 클래스들

 

 

Java 8에서 추가된 날짜 관련 클래스들이 있다

 

이전에는 Date나 SimpleDateFormatter라는 클래스를 사용하여 날짜를 처리해 왔다

 

이 클래스들은 쓰레드에 안전하지도 않다

 

그래서 하나의 클래스에 생성해 놓은 이들 클래스는 여러 쓰레드에서 접근할 때 예기치 못한 값들을 리턴할 수도 있었다

 

그리고, 불변 객체도 아니어서 지속적으로 값이 변경 가능했다

 

게다가 API 구성도 복잡하게 되어 있어서 1900년 부터 시작하도록 되어 있고

 

달은 1부터, 일은 0부터 시작한다, 그래서 1900년 1월 1일은 1900, 1, 0을 매개 변수로 넘겨줘야만 한다

 

 

기존 클래스와 신규 클래스들의 차이를 알아보자

시간을 처리하는 편의를 위한 클래스와 enum들이 추가되었다

 

추가로 시간을 나타내는 클래스는 Local, Offset, Zoned로 3가지 종류가 존재한다

  • Local : 시간대가 없는 시간. 예를 들어 "1시"는 어느 지역의 1시인지 구분되지 않는다
  • Offset : UTC(그리니치 시간대)와의 오프셋(차이)을 가지는 시간, 한국은 "+09:00"
  • Zoned : 시간대("한국 시간"과 같은 정보)를 갖는 시간, 한국의 경우 "Asia/Seoul"

Local과 Locale는 다른 클래스다

 

Locale은 지역을 의미하는 클래스이며, Local은  시간을 이야기하는 것이다

 

지금은 국제화 시대다. 그래서 시스템을 개발할 때에도 사용자의 시간이 매우 중요하다

 

누군가 SNS에 글을 올리면, 그 글이 저장되는 시간은 언제로 표시되어야 할까??

 

그 글이 저장되는 시간은 글쓴이의 Locale(지역) 정보와 함께 저장되어야 한다

 

그렇지 않으면 24시간이 다른 전 세계의 시간이 모두 꼬이게 될 것이다

 

그래서 보통은, 글을 쓴 사람은 자신의 시간대로 글이 보이게 되며, 글을 읽는 사람도 자신의 시간대에 맞게 글이 올라간 시간이 보인다

 

그래서 ZonedDateTime이라는 클래싀와 LocalDate가 추가된 것이다

 

Java 8에 추가된 클래스 중에 DayOfWeek라는 클래스가 있다

 

지금까지는 요일을 표현하기 위해서 한글을 배열에 넣는 등의 작업이 필요했었지만, 이제는 DayOfWeek 클래스를 사용하면 된다

 

DayOfWeek은 enum이다

private void printDayOfWeek(){

    DayOfWeek[] dayOfWeeks = DayOfWeek.values();

    Locale locale = Locale.getDefault();

    for(DayOfWeek day : dayOfWeeks){

        System.out.print(day.getDisplayName(TextStyle.FULL, locale)+" ");

        System.out.print(day.getDisplayName(TextStyle.SHORT, locale)+" ");

        System.out.println(day.getDisplayName(TextStyle.NARROW, locale));

    }

}

DayOfWeek 클래스는 MONDAY부터 SUNDAY까지의 상수가 enum에 선언되어 있다

 

각 요일을 가져다 쓸 때에는 DayOfWeek.MONDAY처럼 사용하면 된다

 

values()라는 메소드를 사용해서 모든 요일을 가져왔다

 

DayOfWeeK 클래스의 getDisplayName() 메소드를 사용해서 해당 요일을 출력할 수 있다

 

이 메소드에는 TextStyle과 Locale을 전달해 줘야만 한다

 

TextStyle엔 FULL, SHORT, NARROW라는 이미 정의되어 있는 스타일들이 존재한다

 

실행 결과는 다음과 같다

월요일 월 월

화요일 화 화

수요일 수 수

목요일 목 목

금요일 금 금

토요일 토 토

일요일 일 일 

 

FULL로 하면 "요일"도 같이 붙는 것을 볼 수 있다

 

한글에서는 SHORT나 NARROW의 차이가 없다.

 

다른 나라의 요일을 확인해 보자

private void printDayOfWeekOfLocales(){

    DayOfWeek day = DayOfWeek.SUNDAY;

    Locale[] loacles = Locale.getAvailableLocales();

    for(Loclae locale : locales){

        System.out.print(locale.getCountry()+" ");

        System.out.print(day.getDisplayName(TextStyle.FULL, locale)+" ");

        System.out.print(day.getDisplayName(TextStyle.SHORT, locale)+" ");

        System.out.println(day.getDisplayName(TextStyle.NARROW, locale));

    }

}

SUNDAY로 고정한 후 각 지역별로 FULL, NARROW, SHORT를 출력하도록 했다

결과는 다음과 같다

US Sunday Sun s

...

KR 일요일 일 일

...

GB Sunday Sun s

지금까지 살펴본 날짜와 관련된 내용이 전부가 아니며, 아래의 링크에서 보다 상세한 내용을 확인해보기 바란다

https://docs.oracle.com/javase/tutorial/datetime/iso/index.html

 

 


병렬 배열 정렬(parallel array sorting)

 

배열을 정렬하는 가장 간편한 방법은 java.util 의 Arrays를 사용하는 것이다

 

Arrays 클래스에는 다음과 같은 static 메소드들이 존재한다

  • binarySearch() : 배열 내에서의 검색
  • copyOf() : 배열의 복제
  • equals() : 배열의 비교
  • fill() : 배열 채우기
  • hashCode() : 배열의 해시코드 제공
  • sort() : 정렬
  • toString() : 배열 내용을 출력

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

 

사용방법은 간단하다

int[] intValues = new int[10];

//배열 값 지정

Arrays.parallelSort(intValues);

이렇게 해당 배열을 매개 변수로 집어 넣으면 정렬이 된다

 

sort() 메소드를 사용해야할까? 아니면 parallelSort() 메소드를 사용해야 할까??

 

sort()의 경우 단일 쓰레드로 수행되며, parallelSort()는 필요에 따라 여러 개의 쓰레드로 나뉘어 작업이 수행된다

 

따라서 parallelSort()가 CPU를 더 많이 사용하게 되겠지만, 처리속도는 더 빠르다

 

실제 두 개의 성능을 비교 테스트한 결과를 보면 5,000개 정도부터 parallelSort()의 성능이 더 빨라지는 것을 볼 수 있다

 

따라서, 개수가 많지 않은 배열에서는 굳이 parallelSort()를 사용할 필요는 없다고 봐도 무방하다.

 

 


 

 

StringJoiner

 

문자열을 처리하는 방법에 String, StringBuilder, StringBuffer 등에 대해서 설명했다

 

문자열을 예쁘게 처리하기 위한 java.util 패키지의 Formatter라는 클래스도 있다

 

Java 8에서는 StringJoiner라는 클래스가 새롭게 추가되었다

 

java.util에 포함되어 있으며, 순차적으로 나열되는 문자열을 처리할 때 사용한다

 

String[] stringArray = new String[]{"StudyHard", "Java", "Konwlendge"};

이와 같은 배열을 다음과 같이 변환하려고 하면 어떻게 해야 할까??

(StudyHard,Java,Konwlendge)

 

String에 계속 더하거나, StringBuilder나 StringBuffer를 사용할 것이다

 

기본적으로 콤마(,)에 대한  처리를 해주지 않으면, 다음과 같은 결과가 나올것이다

(StudyHard,Java,Konwlendge,)

Konwlendge뒤에 있는 콤마를 처리하기 위해서 if문을 넣거나 substring으로 콤마를 잘라 주어야 한다

 

이러한 단점을 보완하기 위해서 StringJoiner가 만들어졌다

 

배열의 구성요소 사잉 콤마만 넣고자 하려면 다음과 같이 사용하면 된다

public void joinStringOnlyComma(String[] stringArray){

    StringJoiner joiner = new StringJoiner(",");

    for(String string : stringArray){

        joiner.add(string);

    }

    System.out.println(joiner);

}

이 메소드의 결과는 다음과 같다

StudyHard,Java,Konwlendge

 

이번에는 앞 뒤에 소괄호를 넣도록 메소드를 수정해보자

public void joinStringOnlyComma(String[] stringArray){

    StringJoiner joiner = new StringJoiner("," , "(" , ")");

    for(String string : stringArray){

        joiner.add(string);

    }

    System.out.println(joiner);

}

다른 점은 생성자뿐, 생성자에 맨 앞에 들어갈 prefix와 뒤에 들어갈 suffix 값을 지정해주면 된다, 결과는 다음과 같다

(StudyHard,Java,Konwlendge)

 

이렇게 StringJoiner를 사용하는 방법도 있지만, 스트림과 람다 표현식을 사용해서도 작성할 수 있다

 

 

 

 


지금까지의 내용은 빙산의 일각이며, 가장 새로운 부분인 람다에 대해서 Java 8_2에서 살펴보자

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Java_2' 카테고리의 다른 글

Java 7_2  (0) 2020.06.17
Java 7_1  (0) 2020.06.10
Java_2_7  (0) 2020.06.02
Java_2_6  (0) 2020.05.28
Java_2_5  (0) 2020.05.26

댓글