티스토리 뷰

IT/개발

Java8 Feature

K.Nero 2019. 3. 4. 23:24

이 글은 tutorials point(https://www.tutorialspoint.com/java8)를 번역한 글입니다.

JAVA 8 은 Java 프로그래밍 언어 개발의 주요한 릴리즈 버전으로 2014년 3월 18일에 초기 버전이 나왔습니다. Java 8 출시와 함께 Java 는 함수형 프로그래밍 지원, 새로운 Javascript engine, date time 을 정교하게 다루기 위한 APIs, 새로운 스트림 API 등을 제공하게 되었습니다.

새로운 특징

Lambda expression - 함수 처리 능력
Method references - 함수를 직접 호출하지 않고 이름으로 함수를 참조. 파라미터로 함수를 사용
Default method - interface 는 기본적인 method 의 구현체를 가질 수 있다.
New tools - 종속성을 찾기 위해 jdeps 처럼 새로운 Complier tools 과 utility 가 추가된다.
Stream API - 파이프 라인 처리를 용이하게하는 새로운 스트림 API.
Date Time API - 향상된 날짜, 시간 API
Optional - 적절하게 Null 값을 처리하기 위한 최고의 방법에 중점을 둔다.
NaShorn, Javascript Engine - Javascript code 를 실행하기 위한 Java 기반 엔진


Lambda Expression

람다 표현법들은 Java 8에서 소개되었고 가장 큰 특징으로 내세워 졌습니다. 람다 표현식은 함수형 프로그래밍을 쉽게 해주고 개발의 많은 부분을 간소화 해 줄 것입니다.
람다 표현식은 다음 구문으로 특징 지어집니다.

parameter -> expression body

다음은 람다 표현식의 중요한 특성들입니다.

Optional type declaration - 파라미터의 타입을 정의할 필요가 없습니다. 컴파일러는 파라미터의 값으로 부터 같은 타입이라고 추측할 수 있습니다.
Optional parenthesis around parameter - 하나의 파라미터는 괄호안에 정의할 필요가 없습니다. 여러 개의 파라미터일 경우에는 괄호가 필요합니다.
Optional curly barces - 만약 한 줄로 구현된 함수일 경우 중괄호는 생략 가능합니다.
Optional return keyword - 함수가 값을 반환하는 한 줄의 표현식으로 구현되었다면 컴파일러는 자동적으로 값을 반환합니다. 표현식이 값을 반환한다는 것을 나타 내기 위해서는 중괄호가 필요합니다.

Example

scheduler.keySet().forEach(quartzKey -> {
Object value = scheduler.get(quartzKey).toString();
sp.put(quartzKey, value);
});

(다양한 예제는 https://www.tutorialspoint.com/java8/java8_lambda_expressions.htm 참고)

다음은 위 예제에서 고려해야할 중요한 점들 입니다.

- 람다 표현식은 주로 함수형 인터페이스의 인라인 구현, 즉 단일 메소드 인터페이스를 정의하는데 사용됩니다. 위 예제에서 우리는 MathOperation 인터페이스의 연산 함수를 정의하기 위해 람다 표현식의 다양한 타입을 사용했다. 방금 GreetingService 의 sayMessage 구현을 정의했습니다.

- 람다 표현식은 익명 클래스의 필요성을 제거하고 매우 간단하고 강력한 함수형 프로그래밍 기능을 Java 에게 제공합니다.

Scope

람다 표현식을 사용할 때, 개발자는 어떠한 final 변수 (한 번만 값을 대입할 수 있는)를 참조할 수 있습니다. 만약 변수에 한 번 더 대입연산을 사용할 경우 컴파일 에러가 발생할 것입니다.

추가로 익명 클래스의 경우 scope 밖의 변수를 참조할 경우 final 이 선언되지 않으면 컴파일 에러가 발생하게 되지만 람다에서는 final 이 선언되지 않은 변수도 참조를 할 수 있습니다.. 하지만 람다는 묵시적으로 final 이 있다고 보는 것이기 때문에 대입연산이 불가능 한 것은 익명 클래스와 동일합니다.


Method references

메소드 참조는 메소드를 이름으로 사용할 수 있도록 해줍니다. 메소드 참조는 "::" 를 사용하는 것을 말하며 아래의 메소드 타입에 사용할 수 있습니다.

- static methods
- Instance methods
- Constructors using operator (TreeSet::new)

example

Runnable r = Thread::new; // functional interface 에만 가능

List<String> names = new ArrayList<>();
names.add("Mahesh");
names.forEach(System.out::println);


Functional Interfaces

함수형 인터페이스는 하나의 기능을 한다고 볼 수 있습니다. 예를 들어 Comparable interface 는 비교하기 위해서 "compareTo" 하나의 메소드만 사용됩니다. Java 8은 람다 표현식에서 확장되어 사용될 수 있는  많은 함수형 인터페이스들이 정의되어 있습니다. (https://www.tutorialspoint.com/java8/java8_functional_interfaces.htm 에서 Given below is the list of interfaces in Java8 을 클릭하시면 java.util.Function 패키지에 정의된 함수형 인터페이스 설명을 볼 수 있습니다.)

추가로 Functional Interface 는 꼭 하나의 메소드만 존재해야 하는 것은 아닙니다. 하나를 제외한 나머지 메소드가 모두 default method 로 정의되어 있다면 가능합니다.

@FunctionalInterface
interface A {
void a();

default void b() {

}

default void c() {

}
}


Default methods

Java 8은 interface 에서 default method 를 구현할 수 있다는 새로운 개념을 도입했습니다.. 이 기능은 하위 호환성을 위해 추가되어 이전 인터페이스를 사용하여 Java 8의 람다 표현 기능을 활용할 수 있습니다.

예를 들어 List 나 Collection 인터페이스는 forEach 메소드 선언을 할 수 없습니다. 따라서 어떤 메소드가 추가되는 것은 collection framework 의 구현을 간단하게 망가트릴 수 있습니다. Java 8 은 List / Collection 인터페이스가 forEach 메소드의 기본 구현을 갖을 수 있도록  default method 를 도입했고 이 인터페이스로 구현된 클래스는 중복해서 같은 기능을 구현할 필요가 없어졌습니다.

개인적으로 사용해 봤을 때 설명과 같이 그 전에 사용하던 인터페이스에 새로운 메소드를 추가할 경우 default 구현체를 만들게 되면 이전에 구현된 클래스 들에 영향을 주지않고 인터페이스를 변경할 수 있었습니다.


Streams

스트림은 Java 8 에서 소개된 새로운 추상 레이어로 SQL 구문을 선언하는 방법과 비슷하게 데이터를 처리할 수 있게 해줍니다. 예를 들어 다음 SQL 구문 을 고려해본다면.

Select max(salary), employee_id, employee_name from Employee

위 SQL은 자동적으로 개발자가 어떠한 계산을 하지 않아도 최대 급여의 직원 정보를 반환해 줍니다. Java 에 있는 collections framework 를 사용 하는 것은 개발자들이 반복문을 사용하고 반복적으로 체크를 해야 합니다. 또다른 관심사로는 효율성으로 멀티 코어 프로세서를 쉽게 사용할 수 있으므로 Java 개발자는 오류가 발생하기 쉬운 병렬 코드 처리를 작성해야합니다.

이런 이슈들을 해결하기 위해 개발자가 데이터를 명확하게 처리하고 특정 코드를 작성할 필요없이 멀티 코어 아키텍처를 활용할 수 있는 스트림 개념이 Java 8에서 소개되었습니다.

What is Stream?

스트림은 Source 로 부터 개체들의 sequence를 표현할 수 있고 결합하는 기능을 지원해 줍니다. 스트림의 특징들은 다음과 같다.

* Squnce of elements - 스트림은 순차적 방식의 특별한 유형의 요소들의 집합을 제공합니다.  스트림은 요구에 맞게 가져오고 처리하며 절대 요소들을 저장하지 않습니다.

* Source - 스트림은 Collections, Arrays, or I/O 자원을 입력 소스로 사용합니다.

* Aggregate operations - 스트림은 filter, map, limit, reduce, find, match 등등과 같은 결과를 합치는 기능들을 지원합니다.

* Pipelining - 대부분의 스트림 기능들은 스트림 자체를 반환하기 때문에 결과가 파이프 라인이 될 수 있습니다. 이 기능들은 중간단계의 기능들이라고 불리며 그것들의 function은 입력값을 가져오고 처리하며 결과갑을 반환합니다. collect() 메소드는 스트림의 마지막을 마크하기 위해서 파이프라인 기능의 끝에 사용되는 터미널 기능이다.

* Automatic iterations - 스트림 조작은 명시 적 반복이 필요한 콜렉션과는 달리 제공된 소스 요소에 대해 내부적으로 반복을 실시합니다.

Generating Streams

Collection 인터페이스는 2개의 스트림을 생성하기 위한 메소드를 가지고 있습니다.

* stream() - 집합의 소스를 고려한 순차적인 스트림을 반환합니다.
* parallelStream() - 집합의 소스를 고려한 병렬 스트림을 반환합니다.

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

Methods

forEach스트림의 각 요소들을 순회하는 방법을 제공합니다.

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

map - 각 요소들을 해당 결과에 매핑하는데 사용된다.

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
//get list of unique squares
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter - 정해진 기준을 기반으로 요소들을 제외시키는데 사용된다.

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
int count = strings.stream().filter(string -> string.isEmpty()).count();

limit - 스트림의 크기를 줄이는데 사용되며 반환된 결과물의 개수를 한정지을 수 있다.

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

sorted - 스트림을 정렬하는데 사용된다.

Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

Parallel Processing - 병렬처리를 위한 대안으로 순차적인 것과  병렬적인 스트림을 아주 쉽게 변경할 수 있습니다. 병렬처리를 통해서 빈 문자열을 카운팅하는 예제를 다음 코드를 통해 확인할 수 있습니다.

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Collectors - 스트림의 요소들을 처리한 결과를 병합하는 것으로 사용됩니다. Collectors 는 목록이나 문자열을 반환할 수 있습니다.

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

System.out.println("Filtered List: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);

Statistics - statistics collectors 는 스트림처리가 완료되었을 때 모든 통계적인 요소들을 계산하는 기능으로 소개되었습니다.

List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();

System.out.println("Highest number in List : " + stats.getMax());
System.out.println("Lowest number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());


Optional Class

Optional 은 not-null 객체를 포함하기 위해서 사용하는 컨테이너 입니다. Optional 객체는 null 로 값이 없음을 표현하기 위해서 사용됩니다. 이 클래스는 null 값을 체크하는 대신에 사용할 수 있거나 사용할 수 없음을 간단한 코드로 값들을 다룰 수 있는 유틸리티 메소드를 가지고 있습니다. 이것은 Java 8에서 Guava 의 Optional 과 비슷하게 소개되었습니다.
메소드의 자세한 설명은 https://www.tutorialspoint.com/java8/java8_optional_class.htm 참고해주세요.


Nashorn Javascript

Java8 의 Nashorn 은 개선된 javascript 엔진으로 소개되었고 기존의 Rhino 와 교체되었습니다. Nashorn 은 2-10 배 더 좋은 성능을 보여주며 메모리의 코드를 바로 컴파일하고 bytecode 를 JVM 에서 전달합니다. Nashorn은 Java 7에서 소개 된 invoke dynamics 기능을 사용하여 성능을 향상시킵니다.

jjs

Nashorn 엔진을 위해서 javascript 코드를 콘솔에서 실행시키기 위한 Java8 에서 소개된 새로운 명령어줄 도구입니다. 

sample.js 의 내용이 print('Hello World') 라면
/home/...> jjs sample.js
결과 : Hello World!

콘솔을 열고 다음 명령어를 통해 사용할 수 있습니다.

C:\JAVA>jjs
jjs> print("Hello, World!")
Hello, World!
jjs> quit()
>>
C:\JAVA> jjs -- a b c
jjs> print('letters: ' +arguments.join(", "))
letters: a, b, c
jjs>

그리고 자바의 클래스를 호출할 수도 있습니다.

var BigDecimal = Java.type('java.math.BigDecimal');

function calculate(amount, percentage) {

   var result = new BigDecimal(amount).multiply(new BigDecimal(percentage)).divide(
      new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
   
   return result.toPlainString();
}
var result = calculate(568000000000000000023,13.9);
print(result);


New Date/Time API

소개된 새로운 Date-Time API 는 다음에 나오는 과거의 date-time API 의 단점을 보완하기 위해서 소개되었습니다.

* Not thread safe - java.util.Date 는 스레드에 안전하지 않아서 개발자들은 date 를 다루는 동안 동시성 문제를 다뤄야 했습니다. 새로운 API 는 불변이며 setter 메소드를 가지고 있지 않습니다.
* Poor design - 기본 Date 는 1900 에서 시작되고 1로 시작되는 달과 날짜는 0시작되어 균일하지 않았습니다. 오래된 API 는 날짜를 다루기 위해서 직관성이 부족하였습니다. 새로운 API 는 각 기능들을 위해서 아주 많은 편의성 메소드를 제공합니다.
* Difficult time zone handling - 개발자는 timezone 이슈를 다루기 위해서 많은 코드를 작성했어야 합니다. 새로운 API는 도메인 특정 디자인을 염두에두고 개발되었습니다.

새로운 API 는 java.time 패키지 밑으로 생성되었으며 다음에 중요한 몇몇 클래스들을 소개하겠습니다.

Local - timezone을 다루기 위한 복잡하지 않고 간단한 date-time API

Zoned - 다양한 timezone 을 다루기 위한 특별한 API

이 부분은 사이트의 예제를 보시는 것을 권장드립니다.(https://www.tutorialspoint.com/java8/java8_datetime_api.htm)


Base64

마침내 Java 8 에서 드이어 Base64 가 나왔습니다. Java8 은 현재 내부적으로 Base64 코딩을 위한 인코더와 디코더를 갖고 있습니다. 우리는 3종류의 Base64 인코딩을 사용할 수 있습니다.

Simple - 인코딩의 결과는 A-Z a-z 0-9 +/ 문자의 결합으로 나옵니다. 이 인코더는 결과에 줄 바꿈을 포함하지 않고 디코더는 인코딩 결과 외의 어떠한 문자도 거부됩니다.
URL - 결과는 A-Z a-z 0-9 +_ 문자의 결합으로 나오며 URL 과 파일이름에 안전합니다.
MIME - 출력은 MIME 형식에 매핑됩니다. 출력은 각각 76 문자 이하의 라인으로 표현되며 캐리지 리턴 '\ r'다음에 라인 구분 기호로 '\ n'을 사용합니다. 인코딩 된 출력의 끝 부분에는 줄 분리 기호가 없습니다.

static Base64.Decoder getDecoder()
static Base64.Encoder getEncoder()
static Base64.Decoder getMimeDecoder()
static Base64.Encoder getMimeEncoder()
static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)
static Base64.Decoder getUrlDecoder()
static Base64.Encoder getUrlEncoder()

'IT > 개발' 카테고리의 다른 글

Java9 - Module System  (0) 2019.03.12
Java default timezone  (0) 2019.03.07
local Docker 개발 환경 만들기  (0) 2018.10.19
Spring @Transactional 사용하기  (0) 2018.10.18
Spring + JWT  (0) 2018.08.31
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
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
글 보관함