JAVA8 버전의 특징에 대해서 알아보자😎 ( ☝표시 중요)
1. ☝람다 표현식(lambda expression) : 함수형 프로그래밍
- 람다 표현식이란 메소드를 하나의 ‘식’으로 표현한 것인데 주목적은 명확하면서 간략한표현을 추구한다.
장점
- 메소드의 구현을 간결하게 하여 가독성을 높인다
- 람다로 구현하여 코드 줄 소비를 줄임
- 지연 연산을 이용하여 퍼포먼스 향상
단점
- 재활용이 불가능하기때문에 여러곳에서 반복적으로 사용된다면 람다를 사용하지않는것이 바람직하다.
- 람다식이 남용된다면 유지보수 및 코드 이해에 어려움을 준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
| //(예제1)
//기존의 익명 내부 함수
Runnable r1 = new Runnable(){
@Override
public void run(){
System.out.println("Hello world!!");
}
};
r1.run();
//람다를 이용한 구현
Runnable r2 = () -> System.out.println("Hello world!!");
r2.run();
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| //(예제2)
// 1번 표현식 한개
Comparator<String> c1 = (String lhs, String rhs) -> lhs.compareTo(rhs);
int result1 = c1.compare("Hello", "World");
// 2번 표현식 두개
Comparator<String> c2 = (String lhs, String rhs) -> {
System.out.println("Comparing "+ lhs + " and " + rhs);
return lhs.compareTo(rhs);
};
int result2 = c2.compare("Hello","World");
//3번 타입 추론
Comparator<String> c3 = (lhs, rhs) -> lhs.compareTo(rhs);
int result3 = c3.compare("Hello", "World");
|
-특징
- 1.람다식은 함수형 인터페이스를 간편히 구현하는 역할을 한다.
- 2.코드를 간결하게 만들어 줘서 코드 양을 줄여주고 가독성을 높임
- 3.람다식은 매개변수 집합 + 화살표 + 바디 로 구성 되어 있다.
- 4.(매개변수) -> {표현식};
- 5.람다식 바디의 표현식이 한개일 경우(코드가 한줄일 경우) 중괄호 생략가능하다.
- 6.매개변수에 타입을 따로 지정하지 않아도 된다
- 7.함수형 인터페이스는 메소드를 하나만 가지고 있는 인터페이스이다.
2. ☝스트림 API(Stream API) : 데이터의 추상화
- 데이터를 처리하는데 자주 사용되는 메서드를 포함한 함수형 스타일의 API를 제공한다
- 컬렉션 데이터를 효과적으로 처리하고 병렬 처리를 쉽게 한다.
- 중간 및 최종 연산을 통해 데이터 파이프라인을 생성하고 조작한다.
- 간결하고 가독성 좋은코드 작성가능.
- 병렬처리 지원 : 멀티코어 환경에서 데이터를 병렬로 처리하기 좋다
스트림을 사용하기 않고 작성한 코드 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // Stream 사용 전
String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"}
List<String> nameList = Arrays.asList(nameArr);
// 원본의 데이터가 직접 정렬됨
Arrays.sort(nameArr);
Collections.sort(nameList);
for (String str: nameArr) {
System.out.println(str);
}
for (String str : nameList) {
System.out.println(str);
}
|
스트림을 사용하여 작성한 예
1
2
3
4
5
6
7
8
9
10
11
| // Stream 사용 후
String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"}
List<String> nameList = Arrays.asList(nameArr);
// 원본의 데이터가 아닌 별도의 Stream을 생성함
Stream<String> nameStream = nameList.stream();
Stream<String> arrayStream = Arrays.stream(nameArr);
// 복사된 데이터를 정렬하여 출력함
nameStream.sorted().forEach(System.out::println);
arrayStream.sorted().forEach(System.out::println);
|
세부특징
1
| List<String> sortedList = nameStream.sorted().collect(Collections.toList());
|
1
2
3
4
| userStream.sorted().forEach(System.out::print);
// 스트림이 이미 사용되어 닫혔으므로 에러 발생
int count = userStream.count();
|
1
2
| // 반복문이 forEach라는 함수 내부에 숨겨져 있다.
nameStream.forEach(System.out::println);
|
3. ☝Parallel Stream : 스트림 병렬처리
예제
1
2
3
4
5
| public long sequentialSum(long n) {
return Stream.iterate(1L, i -> i + 1) // 무한 자연수 스티림 생성
.limit(n) // n개 이하로 제한
.reduce(0L, Long::sum); // 모든 숫자를 더하는 스트림 리듀싱 연산
}
|
-> 만약 해당 코드의 n이 엄청나게 커진다면 병렬로 처리하는것이 좋다
순차 스트림을 병렬스트림으로 변환한 예제
1
2
3
4
5
6
| public long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel() // 스트림을 병렬 스트림으로 변환
.reduce(0L, Long::sum);
}
|
-> 리듀싱 연산으로 스트림의 모든 숫자를 더함 -> 예제의 코드와 변환된코드의 차이는 여러 청크로 분할되어있다는것
병렬스트림을 사용해야하는 기준
- 스트림에서 수행하는 전체 파이프라인 연산 비용을 고려한다.
- 소량의 데이터에서는 병렬 스트림이 도움 되지 않는다.
- 스트림을 구성하는 자료구조가 적절한지 확인한다. 예를 들어 ArrayList를 LinkedList보다 효율적으로 분할할 수 있다.
- 스트림의 특성과 파이프라인의 중간 연산이 스트림의 특성을 어떻게 바꾸는지에 따라 분해 과정의 성능이 달라질 수 있다.
4. java.time 패키지 : Joda-Time을 이용한 새로운 날짜와 시간 API(Calendar가 가지고 있는 단점을 보완)
- Java8 이전에 사용되는 날짜와 시간 API는 Date, Calendar, GregorianCalendar 등이 있었는데 여러모로 불편함을 유발했습니다.😥
문제점
- 클래스 이름이 명확하지 않다 (날짜 클래스 중 Date는 Date인데도 불구하고 시간도 사용 가능하고, TimeStamp도 표현할 수 있습니다)
1
2
| Date date = new Date();
long time = date.getTime();
|
- Date 객체가 mutable(변하기쉽다) 하여 thread unsafe(쓰레드 안전하지못함) 하다 -> 멀티스레드 환경에서 안전하게 사용할 수 없다!
1
2
3
4
5
6
7
8
9
10
11
12
13
| public static void main(String[] args) throws InterruptedException {
Date date = new Date();
long time = date.getTime();
System.out.println("date = " + date);
Thread.sleep(1000 * 3); // 3초 sleep
Date after3Seconds = new Date();
System.out.println("after3Seconds = " + after3Seconds);
// setTime()를 사용하면 객체의 시간값을 변경할 수 있다.
after3Seconds.setTime(time);
System.out.println("after3Seconds = " + after3Seconds);
}
-> set으로 값을 변경시켜버릴수있는 이러한객체를 mutable 객체 , 상태를 변경할수 없는 객체를 immutable객체라고 한다.
|
- Calendar 객체의 month가 0부터 시작하기때문에 헷갈림
JAVA 8버전에서 도입된 java.time 패키지가 제공하는 클래스
Instant : 타임스탬프 ( 기계용 시간, EPOCH )
LocalDate : 시간이 없는 날짜 ( 타임존에 대한 참조가 없음 )
LocalTime : 날짜가 없는 시간 ( 타임존에 대한 참조가 없음 )
LocalDateTime : 날짜와 시간을 결합한 클래스
ZonedDateTime : UTC 에서 시간대 및 타임존에 대한 참조를 가진 시간과 날짜를 다루는 클래스
유용한 메소드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| // 시간, 분, 초 나노초 얻기
loalTime.getHour(); // 11
localTime.getMinute(); // 25
localTime.getSecond(); // 38
localTime.getNano(); // 879975840
// 시간 비교
LocalTime time1 = LocalTime.of( 12, 30, 0 );
LocalTime time2 = LocalTime.of( 13, 40, 0 );
time1.isAfter( time2 ); // false ( @param LocalTime )
time1.isBefore( time2 ); // true
// 특정 시간에서 특정 부분 ( 시간, 분, 초 ) 변경한 객체 얻기
LocalTime localTime = LocalTime.of( 12, 30, 0);
localTime.with( ChronoField.MINUTE_OF_HOUR, 40 );
localTime.withHour( 13 ); // 13:30
localTime.withMinute( 40 ); // 12:40
localTime.withSecond( 40 ); // 12:30:40
localTime.withNano( 40 ); // 12:30:00.000000040
// 시간 더하기
localTime.plus( 2, ChronoUnit.HOURS ); // 14:30
localTime.plusHours( 2 ); // 14:30
localTime.plusMinutes( 30 ); // 13:00
localTime.plusSecond( 30 ); // 12:30:30
localTime.plusNanos( 30 ); // 12:30:00.000000030
// 시간 빼기
localTime.minus( 2, ChronoUnit.HOURS );
localTime.minusHours( 2 ); // 10:30
localTime.minusMinutes( 30 ); // 12:00
localTime.minusSecond( 30 ); // 12:29:30
localTime.minusNanos( 30 ); // 12:29:59.999999970
// 두 시간 차이
Duration duration = Duration.between( time1, time2 );
duration.getNano(); // 0
duration.getSeconds(); // 4200
long diffHours = ChronoUnit.HOURS.between( time1, time2 ); // 1
long diffMinutes = ChronoUnit.MINUTES.between( time1, time2 ); // 70
long diffSeconds = ChronoUnit.SECONDS.between( time1, time2 ); // 4200
|
5. ☝ Default Method : 인터페이스의 구현체를 인터페이스 자체에서 기본으로 제공 가능
- 지금까지 사용하던 Java의 인터페이스 기능은 추상 메서드와 달리 메서드 선언만 할 뿐 그 내부에 로직이 들어가는 일이 없었습니다. 하지만 Java 8부터 인터페이스에 메서드 선언뿐만 아니라 구현이 가능해졌는데 이 구현 방법으로는 default method(기본 메서드), static method(스태틱 메서드)가 있습니다.
(1) 기본 메서드(Default Method)
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Foo 인터페이스 생성
public interface Foo {
// 추상 메서드(abstract method)
void printName(); // void 앞에 abstract 생략 가능
}
// Foo 인터페이스를 상속받은 DefaultFoo 클래스 생성 - 구현체
public class DefaultFoo implements Foo {
@Override
public void printName() {
System.out.println("DefaultFoo!!");
}
}
|
여기서 Foo를 구현한 클래스에 공통적으로 사용할 기능을 추가해야 하는 상황이 생겨 Foo 인터페이스가 추상 메서드 1개를 추가한다면 Foo를 구현한 모든 구현체에서 Error 메세지가 출력됩니다.
Why🙄? => 구현체(DefaultFoo 클래스)에 따로 구현을 해주지 않았기 때문입니다. 기본 메서드(default method)는 이러한 상황 때문에 생기게 되었고 Error가 발생하지 않고 공통적으로 사용할 기능을 추가할 수 있는 방법이 바로 default method를 활용하는 방법입니다.
이처럼 default method를 사용하면 구현체(인터페이스를 상속받는 클래스)에서 따로 해당 메서드를 구현하지 않아도 사용 가능합니다. 하지만 default method는 구현체가 모르게 추가된 기능이기 때문에 구현체에서는 알 수 있는 방법이 없습니다. -> 일반적으로 선언된 메서드와 동일하게 오버라이드 해서 기능을 정의하면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public interface Foo {
void printName();
// 기본 메서드(Default Method)
default void printNameUpperCase() {
System.out.println("FOO");
}
}
public class Main {
public static void main(String[] args) {
Foo foo = new DefaultFoo();
foo.printName();
foo.printNameUpperCase();
}
}
class DefaultFoo implements Foo {
@Override
public void printName() {
System.out.println("DefaultFoo");
}
}
|
(2) 기본 메서드(Default Method)의 상속
- 아래 코드를 보면 Foo 인터페이스에 있는 hello() 메서드를 default 메서드로 두고 싶지 않을 때 Foo 인터페이스를 상속받는 Hello 인터페이스에서 이 메서드를 다시 추상 메서드로 변경할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public interface Foo {
void printName();
/**
* @ImplSpec
* 이 구현체는 "안녕하세요"를 출력한다.
*/
default void hello() {
System.out.println("안녕하세요!!");
}
}
// ====================================================
public interface Hello extends Foo {
// 추상 메서드 - abstract 생략 가능
@Override
default void hello() {
Foo.super.hello();
}
}
|
(3) 💎다이아몬드 문제
- 구현체가 상속 받은 두 인터페이스 모두 동일한 기본 메서드를 가진 경우 다이아몬드 문제가 발생한다. ( 다음과같은경우)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public interface Bar{
default void hello(){
System.out.println("Bar");
}
}
//====================================================
public interface Foo{
void printName();
default void hello(){
System.out.println("Foo");
}
}
//======================================================
public class DefaultFoo implements Foo, Bar{
private String name;
//다이아몬드 문제 해결을 위해 직접 재정의 해준다
@Override
public void printName(){
System.out.println("DefaultFoo");
}
}
|
(4) java8 에서 추가된 메서드
- forEach()
- spliterator()
- stream() / parallelStream()
- removeIf(Predicate)
- reversed()
- thenComparing()
- static reverseOrder() / naturalOrder()
- static nullsFirst() / nullsLast()
- static comparing
6. Optional
- JAVA8 부터 도입되었으며 NPE(NullPointerException)를 방지할수 있도록 도와주기위해 도입되었다.
JAVA 8 이전의 NPE를 막기위한 방법 예제
1
2
3
4
5
6
7
| public static void main(String[] args) {
OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
Progress progress = spring_boot.getProgress();
// 그냥 값이 null인지 아닌지 확인하고 null이 아니면 출력하는방법
if (progress != null) { System.out.println(progress.getStudyDuration());
}
}
|
- 다음과 같은 방식은 Error가 발생할 여지가 많다. 1.null체크와 같은 유효성 검사를 놓치는경우가 생길수있음 2.메서드가 null을 반환하는것 자체가 문제
- null인경우 의도적으로 exception을 터트리는 방법도 존재하나 Error 발생시 Stack Trace를 찍게되며 성능하락의 원인이 될수있다.
🎇 이러한 문제를 해결하기위해 Optional이 등장했다.
1
2
3
| public Optional<Progress> getProgress() {
return Optional.ofNullable(progress);
}
|
Optional은 클라이언트(Client)에 코드에게 명시적으로 빈 값일 수도 있다는 것을 알려주고 빈 값에 대한 처리를 강제합니다. 또한 Optional.ofNullable은 Optional로 감싸는 값이 null 일 수도 있고, 아닐 수도 있을 때 사용합니다.
JPA 사용시 Repositroy에서 리턴 타입을 Optional로 받을수 있도록 지원하고있다!
1
2
| userInfoRepository.findById(userId)
.orElseThrow(() -> new LoginInfoMisMatchRuntimeException("UnAuthorized - UserInfo is not exist / userId = " + userId));
|
1
2
3
4
| userInfoRepository.findById(payload.getUserId())
.ifPresent(userInfo -> {
throw new UserInfoDuplicationRuntimeException("Conflict - UserInfo is Duplicated / userId = " + payload.getUserId());
});
|
- ⚠️주의사항 - 문법적으로 Optional은 어디에 들어가도 문제되지 않으나 API 공식문서상 리턴값으로만 쓰기를 권장한다.
7. 나즈혼(Nashorn) : 자바스크립트의 새로운 엔진
- 지금까지 자바스크립트의 기본 엔진으로는 모질라의 리노(Rhino)가 사용되어 왔다. 하지만 자바의 최신 개선 사항 등을 제대로 활용하지 못하는 등 노후화된 모습을 보여주게 된다. 따라서 이번 Java SE 8 버전부터는 자바스크립트의 새로운 엔진으로 오라클의 나즈혼(Nashorn)을 도입하게 되었다. 나즈혼(Nashorn)은 기존에 사용되어 온 리노에 비해 성능과 메모리 관리 면에서 크게 개선된 스크립트 엔진 이다.