본문 바로가기

JAVA

JAVA 8 핵심만 정리했다

들어가며
JAVA 12 가 나오는 마당에 아직도 어쩔수 없이(?) JAVA 8을 사용하지 못하는 사람들을 위해 JAVA 8 의 기본 사항만 짧게 정리해 봤다. 

이런 강좌(?) 같은 글을 처음 써 보기 때문에 확신할 수 는 없지만, 대략, 자바 초보부터 전문가 까지 두루두루 읽고 이해할 수 있게 작성하는 것이 목표다.


새로운 기능
JAVA 8 은 JAVA 의 중요한 릴리즈 버전 중 하나를 말한다. 2014년에 최초 릴리즈 되었으며, 다음의 기능을 지원한다. 

  • Lambda expression - 자바에서 Functional 프로그래밍이 일정 부분 가능해졌다.
  • Method reference - 함수를 파라메터로 사용할 수 있다.
  • Default method - 인터페이스에서 기본 메소드를 구현할 수 있다.
  • New tools - 새로운 컴파일러 도구와 유틸리티가 추가 되었다. (이건 잘 모름)
  • Stream API - 자바에서 파이프라인 처리가 가능하다.
  • Date Time API - 드디어… 날짜 시간 함수가 쓰레드에서 안전해졌다.
  • Optional - null 값 처리에 대한 자바 나름대로의 표준을 제공한다.
  • Nashorn, Javascript Engine - 자바스크립트 코드를 자바에서 실행 한다. (이건 잘 모름)


설치
이건… 알아서 하자…



Lambda expression
Syntax
람다의 표현식은 다음과 같다. 
parameter -> expression body

특징
  • 람다는 파라메터의 타입을 선언할 필요가 없다. 컴파일러가 파라메터의 값을 보고 판단한다. 
  • 파라메터가 한개인 경우는 괄호로 감싸지 않아도 된다. 단, 두개 이상인 경우라면 괄호로 감싸야 한다.
  • expression body 가 단일 명령어 행이면, 중괄호({})를 생략해도 된다. 단일 명령어 행이 아니면, 중괄호를 꼭 써야 한다. 
  • 중괄호의 존재 여부에 따라 return 구문을 생략할 수 있다. 

// 타입 선언
(int a, int b) -> a + b;

// 타입 선언 생략
(a, b) -> a + b;

// return 구문사용 (중괄호 필요)
(a, b) -> { return a * b; };

정리하면
  • 람다는 단일 메소드만을 가진 인터페이스를 정의하는 데 주로 사용한다. 
  • 람다 표현은 익명 클래스의 필요성을 제거하고, 매우 간단하게 funtional 프로그래밍을 할 수 있게 한다. 


Method reference
Syntax
메소드 참조의 표현식은 다음과 같다. 
Class::new

메소드 참조는 다음의 경우에 사용한다. 
  • 정적 메소드 호출
  • 인스턴스 메소드 호출
  • new 연산자를 사용한 객체 생성

Sample Code
List names = new ArrayList();

names.add(“지우”);
names.add(“호준”);
names.add(“연석”);
names.add(“세종”);

names.forEach(System.out::println);



Default method
Java 8은 인터페이스에서 기본 메소드라는 새로운 개념을 도입했다. 

Syntax
public interface vehicle {

    default void print() {
        System.out.println("I am a vehicle!");
    }

}

Multiple Default
인터페이스의 기본 메소드를 사용하면, 두개의 인터페이스를 구현하는 클래스에서 어떠한 기본 메소드를 사용해야 할지 정해야 한다. 
public interface vehicle {

    default void print() {
        System.out.println("I am a vehicle!");
    }

}


public interface fourWheeler {

    default void print() {
        System.out.println("I am a four wheeler!");
    }

}


public class car implements vehicle, fourWheeler {

}

첫번째 방법으로는 기본 메소드를 재 정의 한다. 
public class car implements vehicle, fourWheeler {
    
    public void print() {
        System.out.println(“I am a four wheel vehicle”);
    }

}

두번째 방법으로는 super 연산자를 사용하여 지정한다.
public class car implements vehicle, fourWheeler {

    default void print() {
        vehicle.super.print();
    }

}

정적 기본 메소드
Java 8 의 인터페이스는 정적 기본 메소드도 가능하다.
public interface vehicle {

    default void print() {
        System.out.println("I am a vehicle!");
    }
        
    static void blowHorn() {
        System.out.println("Blowing horn!!!");
    }

}



Stream API
Stream 은 Java 8 에서 새로 도입된 추상 레이어다. Stream 을 사용하면, SQL 과 비슷한 선언적 방식으로 데이터를 처리 할 수 있다. 

Java 8은 두가지의 Stream 을 생성하는 메소드를 가지고 있다. 
  • stream - 순차 처리
  • parallelStream - 병렬 처리
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream()
                               .filter(string -> !string.isEmpty())
                               .collect(Collectors.toList());

forEach
Stream 은 forEach 메소드를 제공하여 각 요소를 반복 한다. 아래 코드는 forEach 를 사용하여 10개의 난수를 출력하는 방법을 보여준다.
Random random = new Random();
random.ints()
      .limit(10)
      .forEach(System.out::println);

Map
map 은 각 요소를 결과에 맵핑하는데 사용한다. 아래 코드는 map 을 사용하여 고유한 정사각형을 출력한다.
List<Integer> numbers = Arrays.asList(1,2,3,4,3,5,2,1,2,3,5,3);
List<Integer> squaresList = numbers.stream()
                                   .map( i -> i * i )
                                   .distinct()
                                   .collect(Collectors.toList());

Filter
filter 는 기준에 맞지 않는 요소를 제거하는데 사용한다. 
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
int count = strings.stream()
                   .filter(string -> string.isEmpty())
                   .count();

Limit
limit 은 스트림의 크기를 줄이기 위해 사용한다.
Random random = new Random();
random.ints()
      .limit(10)
      .forEach(System.out::println);

Sorted
sorted 는 스트림을 정렬하는데 사용한다.
Random random = new Random();
random.ints()
      .limit(10)
      .sorted()
      .forEach(System.out::println);

ParallelStream
parallelStream 은 병렬처리를 위해 사용한다. 
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
long count = strings.parallelStream()
                    .filter(string -> string.isEmpty())
                    .count();

Collect
스트림의 처리 결과를 결합하는데 사용한다. 
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
Java 8 에서는 스트림 처리가 완료될 때 모든 통계를 계산하기 위한 통계 처리기가 도입되었다. 
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());



Date Time API
Java 8 에서는 새로운 Date, Time API 가 도입되어 아래와 같은 기존의 Date, Time API 의 단점을 보완한다.
  • 쓰레드로 부터 안전하지 않음. - java.util.Date 는 쓰레드로 부터 안전하지 않아, 개발자가 동시성 문제를 해결해야 했다. 
  • 이상한 디자인 - java.util.Date 는 1900 년 부터 시작하고, 월은 1에서 시작하지만, 날짜는 0에서 시작하는 등 일관성이 없었다. 또한 날짜 조작에 대한 메소드가 적었다. 
  • 어려운 시간대 처리 - 시간대 문제를 해결하기 위해 개발자가 많은 노력을 기울여야 했다. 

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;

public class Java8Test {

   public static void main(String args[]) {
      Java8Test java8test = new Java8Test();
      java8test.testLocalDateTime();
   }
        
   public void testLocalDateTime() {

      LocalDateTime currentTime = LocalDateTime.now();
      System.out.println("Current DateTime: " + currentTime);
                
      LocalDate date1 = currentTime.toLocalDate();
      System.out.println("date1: " + date1);
                
      Month month = currentTime.getMonth();
      int day = currentTime.getDayOfMonth();
      int seconds = currentTime.getSecond();
                
      System.out.println("Month: " + month +"day: " + day +"seconds: " + seconds);
                
      LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
      System.out.println("date2: " + date2);
                
      LocalDate date3 = LocalDate.of(2019, Month.DECEMBER, 12);
      System.out.println("date3: " + date3);
                
      LocalTime date4 = LocalTime.of(22, 15);
      System.out.println("date4: " + date4);
                
      LocalTime date5 = LocalTime.parse("20:15:30");
      System.out.println("date5: " + date5);
   }
}



Optional
optional 은 null 이 아닌 객체를 포함하는데 사용하는 컨테이너 객체다. 
Guava 의 Optional 과 유사하다.