내가 쓰는 언어의 버전에서 어떤걸 사용할 수 있는지 알아보고자 정리하였다.
먼저 자바 8에서 추가된 주요 기능들을 나열하면 아래와 같다.
1. Optional
2. 인터페이스의 기본 메소드(Default method)
3. 날짜와 시간 관련 클래스들 추가
4. 병렬 배열 정렬
5. StringJoiner 추가
6. 람다(Lambda) 표현식
7. 함수형 인터페이스(Functional Interface)
8. 스트림(Stream)
Optional(옵셔널)
- 선택적으로 뭔가를 처리 할 때 사용하는 것으로 객체를 편리하게 처리하기 위해서 만든 클래스다.
Optional
클래스는null
처리를 보다 편리하게 하기 위해서 만들어졌다.NullPointerException
이 발생할 수 있는 상황에서 문제를 보다 간편하고 명확하게 처리하기 위해 사용된다.java.util
패키지에 속해있다.
Optional
은 final
클래스로 선언되어 있으며 Generic
한 클래스이다.
final
클래스로 선언되어 있기 때문에 추가적인 확장(상속)이 불가능하다. 즉, 자식 클래스를 만들 수 없다는 의미다.
Optional 클래스는 객체를 생성할 때 Optional 클래스를 리턴하는 empty()
, of()
, ofNullable()
메소드들을 사용하여 생성한다.
String nullableStr = null;
String mandStr = "required";
Optional<String> empty = Optional.empty(); //1
Optional<String> nullable = Optional.ofNullable(nullableStr); //2
Optional<String> optionalStr = Optional.of(mandStr); //3
- 데이터가 없는
Optional
객체를 생성한다. Null
이 추가될 수 있는 상황에 사용한다.(Null 허용)- 반드시 데이터가 들어갈 수 있는 상황에서 사용한다.
Optional
객체가 비어있는지 확인하려면 isEmpty()
가 아닌 isPresent()
메소드를 사용한다.
Java 11부터는 isEmpty()
를 사용하여 체크할 수 있다.
System.out.println(empty.isPresent());
System.out.println(optionalStr.isPresent());
첫번째 라인의 결과는 false
로 나오고 두번째 라인의 결과는 true
가 나온다.
Optional 객체를 print해보면 아래와 같이 Optional객체로 나오게 된다.
Optional객체가 아닌 값을 꺼내는 방법은 "T"
를 리턴하는 메소드들을 살펴보면된다.
get()
, orElse()
, orElseGet()
, orElseThrow()
메소드가 값을 리턴하는 메소드들이다.
String mandStr = "required";
Optional<String> empty = Optional.empty();
Optional<String> optionalStr = Optional.of(mandStr);
Supplier<String> stringSupplier = new Supplier<String>() {
@Override
public String get() {
return "supplier get";
}
};
System.out.println(optionalStr.get()); //1
System.out.println(empty.orElse("empty")); //2
System.out.println(empty.orElseGet(stringSupplier)); //3
System.out.println(empty
.orElseThrow(() -> new IllegalArgumentException("null exception"))
); //4
get()
을 통해 Optional객체에 있는 데이터를 가져온다. 만약 Optional 객체가 null이라면NoSuchElementException
이 발생한다.- Optional 에 값이 없을 경우
orElse()
를 사용하여 기본 값을 지정할 수 있다. Supplier<T>
라는 인터페이스를 사용해서 값이 없는 경우 이를 대신해 반환할 값을 생성한다.
- 익명 클래스를 사용하는 대신
람다 표현식
으로 사용 할 수 있다. empty.orElseGet(() -> "람다로 가능")
- 익명 클래스를 사용하는 대신
- 값이 없는 경우 예외를 발생시킬때는
orElseThrow()
메소드를 사용한다. 아래와 같이 좀더 간결하게 표현 할 수 있다.optionalStr.orElseThrow(NoSuchElementException::new)
ifPresent()
를 사용하면 값이 있을 경우 해당 값을 사용하여 로직을 수행하고 값이 없을 때는 아무 동작도 하지 않는다.
optionalStr.ifPresent((str) -> System.out.println("str = " + str));
Default method
인터페이스에 default method
라는 것이 추가되었다.
A라는 인터페이스가 있을때 해당 인터페이스를 구현한 B,C,D 클래스가 있다고 예를 들어보자. 여기서 A 인터페이스에 추가기능이 필요해서 추상 메서드를 추가하고 새로 E라는 클래스로 인터페이스를 구현했다. 여기서 문제는 E클래스를 위해 추가된 추상 메서드를 B,C,D 클래스에 추가로 구현해야한다. 이러한 이유때문에 default method가 추가되어 하위호환성
과 유연성
을 높여주고 있다.
사용 방법은 아래 예제와 같이 사용된다.
public interface DefaultInterface{
String id = "bang";
String getId();
//추가된 부분
default String getEmail(){
return id + "@gmail.com";
}
}
default method
는 재정의하여 사용할 수 있다.
default method
외에도 static method
도 추가되었는데 유틸 메서드를 사용하기 위해서 추가된거같다...
interface에서 생성된 static method
는 재정의가 불가능하다.
날짜와 시간 관련 클래스
Java 8에서는 기존 Date나 Calendar의 문제를 해결 할 수 있는 새로운 날짜와 시간 관련 클래스들이 추가되었다.
아래는 Date클래스나 Calendar클래스등 이전에 사용하던 날짜와 시간관련 클래스들의 문제점들이다.
- 불변 객체가 아니어서 지속적으로 값이 변경 가능하다
java.util.Date
클래스는 날짜 계산을 할 수 없으며,java.util.Calendar
클래스는 불변객체가 아니므로 연산시 객쳉 자체가 변경되었다.
- SimpleDateFormat은 Thread safe하지 않고 느리다.
- 멀티 쓰레드 상황에서 static으로
impleDateFormat
을 사용하고 parse를 할 때NumberFormatException
이 발생하게 된다. - DateFormat을 javadoc에서 찾아보면 아래와 같은 내용이있다.
- 멀티 쓰레드에서는 동기화를 관리해야한다는 내용이다.
- 멀티 쓰레드 상황에서 static으로
- int 상수 필드의 남용
Calendar
에add
메서드를 사용할 때 정수를 사용한다. calendar.add(Calendar.YEAR, 1);- 여기서
Calendar.YEAR
를 다른 정수로 변경해도 에러가 나지 않는다. calendar.add(Calendar.DECEMBER, 1);
- 직관적이지 않은 월 지정
- Calendar의 월은 1월이 0부터 시작한다.
- 일관성없는 요일 상수
Calendar
의get
으로 요일을 가져왔을때와Date
의getDay()
로 요일을 가져왔을 때 결과값이 다르다.getDay()
는@Deprecated
로 지정되어있다.
//일요일 기준
Calendar calendar = Calendar.getInstance();
calendar.get(Calendar.DAY_OF_WEEK); // 7
Date date = new Date();
date.getDay(); // 6
- 오류에 둔감한 timezone id 지정
TimeZone.getTimeZone
메서드에 timezone id가 들어가는데 여기서Asia/Seoul
을Seoul/Asia
로 입력해도 오류가 나오지 않는다.
TimeZone zone = TimeZone.getTimeZone("Asia/Seoul");
zone.getID(); //Asia/Seoul
TimeZone testZone = TimeZone.getTimeZone("Seoul/Asia");
testZone.getID(); //GMT
이러한 여러가지 이슈들 때문에 Java 8에서는 java.time
이라는 패키지가 추가되었다.
- java.time 패키지에
LocalDate
,LocalTime
,LocalDateTime
등 불변객체를 제공하고 있다.- 모든 클래스가 연산용 메소드를 갖고 있으며, 연산시 새로운 불변 객체를 return한다.
- 쓰레드에 안전하다.
DateTimeFormatter
를 사용하여 빠르고 Thread safe하게 코드를 작성할 수 있다.ZoneId
를 통해 timezone을 가져올 수 있는데Asia/Seoul
을Seoul/Asia
로 입력하면ZoneRulesException
이 발생한다.java.time.temporal
패키지에ChronoField
나ChronoUnit
등의 enum타입을 제공한다. 연산을 하거나 날짜/시간의 데이터를 가져올때 사용되어 보다 직관적으로 사용할 수 있다.
병렬 배열 설정
Java 8에서는 parallelSort()
라는 정렬 메서드가 제공되고 있다. 기존의 sort()
의 경우 단일 쓰레드로 수행되고 있으며 parallelSort()
는 필요에 따라 여러 개의 쓰레드로 나눠서 작업을 수행한다. 멀티 쓰레드로 작업을 수행하기 때문에 CPU를 더 많이 사용하지만 속도적인 측면에서는 더 빠르다.
parallelSort
는 Fork-Join
이라는 프레임워크가 내부적으로 적용된 메서드다.
Fork-Join은 Java 7에서 새로 추가된 기능으로 CPU를 더 쉽고 효율적으로 사용하기 위해서 만들어진 기능이다. 작업을 여러개로 나누어서 처리한 후 합친다는 의미로 Fork-Join에는 Work Stealing이라는 개념이 포함되어 있다. 예를 들어 여러개의 작업을 나눠서 진행하고 있을 때 어떤 작업은 바쁜반면 다른 작업은 한가할 경우가 있다. 이 때 한가한 작업이 바쁜 작업의 대기하고 있는 일을 대신 가져가서 처리해 주는 것이다.
length를 증가시키면서 random한 정수를 가진 배열을 가지고 테스트 진행 결과 배열의 길이가 길어질 수록 parallelSort
메서드가 더 빨라지는 모습을 볼 수 있었다.
sort() | parallelSort() |
length = 10 startTime = 17:59:58.343, endTime = 17:59:58.344 1 length = 100 startTime = 17:59:58.351, endTime = 17:59:58.352 1 length = 1000 startTime = 17:59:58.352, endTime = 17:59:58.352 0 length = 10000 startTime = 17:59:58.353, endTime = 17:59:58.356 3 length = 100000 startTime = 17:59:58.365, endTime = 17:59:58.377 12 length = 1000000 startTime = 17:59:58.401, endTime = 17:59:58.524 123 length = 10000000 startTime = 17:59:58.720, endTime = 17:59:59.358 638 length = 100000000 startTime = 18:00:01.602, endTime = 18:00:07.490 5888 |
length = 10 startTime = 18:00:35.692, endTime = 18:00:35.692 0 length = 100 startTime = 18:00:35.702, endTime = 18:00:35.702 0 length = 1000 startTime = 18:00:35.703, endTime = 18:00:35.704 1 length = 10000 startTime = 18:00:35.705, endTime = 18:00:35.718 13 length = 100000 startTime = 18:00:35.729, endTime = 18:00:35.755 26 length = 1000000 startTime = 18:00:35.790, endTime = 18:00:35.940 150 length = 10000000 startTime = 18:00:36.144, endTime = 18:00:36.386 242 length = 100000000 startTime = 18:00:38.708, endTime = 18:00:40.571 1863 |
배열의 길이가 엄청 많지 않는 이상 굳이 parallelSort()
를 사용할 필요는 없어 보인다. 상황에 맞게 사용하는게 맞는거같다.
for (int i = 10; i < 1000000000; i*=10) {
System.out.println("length = " + i);
int[] randArr = new int[i];
for (int j = 0; j < randArr.length; j++) {
randArr[j] = (int)(Math.random() * 10000);
}
LocalTime startTime1 = LocalTime.now();
Arrays.parallelSort(randArr);
//Arrays.sort(randArr);
LocalTime endTime1 = LocalTime.now();
System.out.println("startTime = " + startTime1 + ", endTime = " + endTime1);
System.out.println(ChronoUnit.MILLIS.between(startTime1, endTime1));
System.out.println();
}
StringJoiner
배열이나 리스트등 어떤 구분자로 구분하여 출력할 때 사용 할 수 있다.
List<String> list = Arrays.asList("A","B","C");
StringJoiner stringJoiner = new StringJoiner(",","(",")");
for (String s : list) {
stringJoiner.add(s);
}
System.out.println("join string = " + stringJoiner);
// join string = (A,B,C)
StringJoiner
는 두개의 생성자를 제공한다.
public StringJoiner(CharSequence delimiter) {...}
public StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix){...}
위 코드에서 구분자만 사용하여 StringJoiner
를 사용할 경우 괄호 없이 A,B,C
가 나온다.
Collectors
라는 클래스의 joining
메서드를 사용하면 아래처럼 간단하게 사용할 수 있다.
list.stream().collect(Collectors.joining(",", "(", ")"));
[Reference]
자바의 신 2nd - 32장
'Backend > JAVA' 카테고리의 다른 글
Java 불변 객체(immutable object) (0) | 2024.05.16 |
---|---|
Java 8에 추가된 것들 2 (0) | 2021.09.17 |
객체지향 5원칙 (SOLID) (0) | 2021.07.28 |
객체지향 프로그래밍(OOP) (0) | 2021.07.27 |
JAVA JWT payload 가져오는 방법 / 만료시간 체크 (0) | 2021.06.09 |