> Hello World !!!

     

@syaku

Java 8 정리 - java 8 lambda Stream Optional Null LocalDate Time example 자바 람다 스트림 타입 패키지

Java 8 정리

Github

https://github.com/syakuis/java8

키워드

java 8 lambda Stream Optional Null LocalDate Time 자바 람다 스트림 타입 패키지

자바 설치 - OpenJDK 8

macOS - brew package manager

AdoptOpenJDK 는 IBM 과 RedHat 이 공동으로 배포하는 open jdk 이다.

$ brew tap AdoptOpenJDK/openjdk
$ brew cask install adoptopenjdk8

windows - chocolatey package manager

> choco install zulu8

람다 표현식

장점

  • 자바의 메서드들은 일반적으로 먼저 실행되어 왔다. 하지만 자바8 에서 람다는 지연 연산 혹은 게으른 이라는 표현을 사용하는 필요에 의해 메서드가 실행된다.

    // 가장 적절한 예로 조건문을 들 수 있다.
    if (false || test()) {
      // true
    } else {
      // false
    }

    일반적인 조건문에서 false 선행되면 작업이 종료되는 데. 위와 같은 경우에는 test 메서드가 실행되고 조건을 판단한다. 불필요한 작업을 하고 있다.

  • 자료형을 추론할 수 있어 코드가 간결해진다.

  • 동작 가능한 코드를 인수(아규먼트)로 전달할 수 있다.

public void test(String a) {} // 여기 a 는 인수 : argument test("테스트"); // 여기서 '테스트' 는 인자, 매개변수 : parameter

람다식 예시

( parameters ) -> expression body
( parameters ) -> { expression body }
() -> { expression body }
() -> expression body

함수 인터페이스

자바는 변수에는 값과 객체만 담을 수 있다. 즉 일급 함수를 지원하지 않았다. 하지만 자바8 부터 인터페이스를 이용하여 변수에 함수를 담을 수 있다. 기존에 있던 인터페이스와 같지만 이것은 람다를 위한 인터페이스이고 람다를 위한 인터페이스만 일급 함수로 사용할 수 있다. 직접 함수 인터페이스를 만들때는 @FunctionalInterface 를 설정해야 람다를 위한 인터페이스로 인식할 수 있다. 또한 람다용 인터페이스는 한개의 추상 메서드만 가질수 있다.

함수 인터페이스를 이용하여 동작 파라메터화를 구현할 수 있다. 동작 파라메터화란 동작을 수행할 수 있는 코드를 인수로 전달하는 것을 말한다.

예제

package org.syaku.study.java8.lambda;

public class Functional {
 @FunctionalInterface
 interface StringUtils {
   String concat(String value, String value2);
}

 @Test
 public void 인터페이스() {
   StringUtils string = (v1, v2) -> v1 + v2;
   assertEquals(string.concat("Hello", " World!!!"), "Hello World!!!");
}

 private String concat(String value, String value2, Function<String, String> fn) {
   return fn.apply(value + value2);
}

 @Test
 public void 메서드() {
   assertEquals(concat("Hello", " World!!!", p -> p), "Hello World!!!");
}

 @Test
 public void 직접() {
   Function<String, String> string = v -> v + " World!!!";
   assertEquals(string.apply("Hello"), "Hello World!!!");
}
}

함수 인터페이스를 만들기 위한 Function 인터페이스를 사용했다. 인터페이스에는 한개의 abstract method, 2개의 default method 그리고 1개의 static method가 있다.

디폴트 메서드를 보면 compose 와 andThen 이 있는 데 이둘은 연산 순서를 의미한다.

  @Test
 public void composeAndThen() {
   Function<Integer, Integer> one = v -> 10 * v;
   Function<Integer, Integer> two = v -> v * v;

   assertEquals(one.compose(two).apply(2).intValue(), 40);
   assertEquals(one.andThen(two).apply(2).intValue(), 400);
}

결과와 같이 componse 는 자신의 two 인자부터 실행되고 andThen 는 two 안자가 나중에 실행된다.

자바 8부터 default 메서드를 사용할 수 있다. 디폴트 메서드를 이용하여 인터페이스에 구현 메서드를 정의할 수 있다. 이전에는 중복되는 메서드를 클래스나 추상 클래스에 구현하여 사용했지만 이젠 인터페이스에 디폴트 메서드를 구현하여 사용하면 된다. 떠힌 상속한 클래스에 디폴트 메서드를 오버라이드할 수 있다.

오버라이딩: 부모의 메서드를 재정의할 수 있다.

오버로딩: 같은 클래스에 같은 메서드를 결과와 인수의 형식을 다르게하여 여러개 정의할 수 있다.

자주 사용되는 함수 인터페이스를 자바에서 기본적으로 제공된다.

공식 https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

예제 http://www.java2s.com/Tutorials/Java_Lambda/

인수가 두개인 경우

  • biConsumer: 원하는 유형 두개의 인수를 받고 결과는 없다.

  • biFunction: 원하는 유형 두개의 인수를 받고 원하는 유형의 결과를 반환한다.

  • binaryOperator: 원하는 유형 두개의 인수를 받고 결과를 반환한다. 단 인수와 결과의 유형이 모두 동일해야 한다.

  • biPredicate: 원하는 유형 두개의 인수를 받고 결과를 boolean 으로 반환한다.

인수가 한개인 경우

  • Consumer: 원하는 유형 한개의 인수를 받고 결과는 없다.

  • Function: 원하는 유형 한개의 인수를 받고 원하는 유형의 결과를 반환한다.

  • UnaryOperator: 원하는 유형 한개의 인수를 받고 결과를 반환한다. 단 인수와 결과의 유형이 모두 동일해야 한다.

  • Predicate: 원하는 유형 한개의 인수를 받고 결과를 boolean 으로 반환한다.

인수가 없는 경우

  • supplier: 인수가 없고 원하는 유형의 결과를 반환한다.

@Log4j2
public class Functional {
 @FunctionalInterface
 interface StringUtils {
   String concat(String value, String value2);
}

 @Test
 public void 인터페이스() {
   StringUtils string = (v1, v2) -> v1 + v2;
   assertEquals(string.concat("Hello", " World!!!"), "Hello World!!!");
}

 private String concat(String value, String value2, Function<String, String> fn) {
   return fn.apply(value + value2);
}

 @Test
 public void 메서드() {
   assertEquals(concat("Hello", " World!!!", p -> p), "Hello World!!!");
}

 @Test
 public void 직접() {
   Function<String, String> string = v -> v + " World!!!";
   assertEquals(string.apply("Hello"), "Hello World!!!");
}

 @Test
 public void composeAndThen() {
   Function<Integer, Integer> one = v -> 10 * v;
   Function<Integer, Integer> two = v -> v * v;

   assertEquals(one.compose(two).apply(2).intValue(), 40);
   assertEquals(one.andThen(two).apply(2).intValue(), 400);
}

 @Test
 public void biConsumer() {
   BiConsumer<String, String> func = (v1, v2) -> {
     assertEquals(v1, "a");
     assertEquals(v2, "b");
  };
   func.accept("a", "b");
}

 @Test
 public void biFunction() {
   BiFunction<String, String, String> func = (v1, v2) -> v1 + v2;
   assertEquals(func.apply("syaku", "syaku"), "syakusyaku");
}

 @Test
 public void binaryOperator() {
   BinaryOperator<List<String>> func = (v, v2) -> {
     List<String> result = new ArrayList<>(v);
     result.addAll(v2);
     return result;
  };

   List<String> kr = Arrays.asList("가", "나");
   List<String> us = Arrays.asList("a", "b", "c");

   List<String> result = new ArrayList<>(kr);
   result.addAll(us);
   assertEquals(func.apply(kr, us), result);

   BinaryOperator<Integer> max = BinaryOperator.maxBy(Comparator.naturalOrder());
   assertEquals(max.apply(1, 2).intValue(), 2);

   BinaryOperator<Integer> min = BinaryOperator.minBy(Comparator.naturalOrder());
   assertEquals(min.apply(1, 2).intValue(), 1);
}

 @Test
 public void biPredicate() {
   // (v, v2) -> Objects.equals(v, v2);
   BiPredicate<String, String> func = Objects::equals;
   assertFalse(func.test("A", "B"));
   assertTrue(func.test("A", "A"));
}

 @Test
 public void booleanSupplier() {
   BooleanSupplier func = () -> {
     log.debug("실행됨.");
     return false;
  };
   assertFalse(func.getAsBoolean());

   // 매개변수 한개가 false 이므로 두개다 실행되지 않는 다.
   BiFunction<BooleanSupplier, BooleanSupplier, Boolean> bool = (v, v2) -> v.getAsBoolean() && v2.getAsBoolean();
   bool.apply(func, func);
}

 @Test
 public void consumer() {
   Consumer<String> func = v -> assertEquals(v, "a");
   func.accept("a");
}

 @Test
 public void doubleBinaryOperator() {
   DoubleBinaryOperator func = (v, v2) -> v + v2;
   assertEquals(func.applyAsDouble(1.1, 1.1), 2.2, 2.2);
}

 @Test
 public void doubleConsumer() {
   DoubleConsumer func = v -> assertEquals(v, 1.1, 1.1);
   func.accept(1.1);
}

 @Test
 public void doubleFunction() {
   // v -> String.valueOf(v)
   DoubleFunction<String> func = String::valueOf;
   assertEquals(func.apply(1.2), "1.2");
}

 @Test
 public void doublePredicate() {
   DoublePredicate func = v -> v > 1.1;
   assertTrue(func.test(1.2));
}

 @Test
 public void doubleSupplier() {
   DoubleSupplier func = () -> 1.1;
   assertEquals(func.getAsDouble(), 1.1, 1.1);
}

 @Test
 public void doubleToIntFunction() {
   DoubleToIntFunction func = v -> new Double(v).intValue();
   assertEquals(func.applyAsInt(1.1), 1);
   assertEquals(func.applyAsInt(1.9), 1);
}

 @Test
 public void doubleToLongFunction() {
   DoubleToLongFunction func = v -> new Double(v).longValue();
   assertEquals(func.applyAsLong(1.1), 1L);
   assertEquals(func.applyAsLong(1.9), 1L);
}

 @Test
 public void doubleUnaryOperator() {
   DoubleUnaryOperator func = v -> v * 2;
   assertEquals(func.applyAsDouble(2.1), 4.2, 4.2);

   DoubleUnaryOperator func2 = v -> v * v;
   assertEquals(func.andThen(func2).applyAsDouble(5), 100, 100);
   assertEquals(func.compose(func2).applyAsDouble(5), 50, 50);
}

 @Test
 public void function() {
   Function<String, String> func = v -> v + "a";
   assertEquals(func.apply("a"), "aa");

   Function<String, String> func2 = v -> v + v;
   // func2 call
   assertEquals(func.compose(func2).apply("b"), "bba");
   // func call
   assertEquals(func.andThen(func2).apply("b"), "baba");
}

 // Int, Long, Obj Function 은 Double 유사하므로 생략한다.

 @Test
 public void predicate() {
   Predicate<String> func = v -> Objects.equals(v, "ok");
   assertTrue(func.test("ok"));
   // 부정
   assertFalse(func.negate().test("ok"));

   // v -> "ok".contains(v)
   Predicate<String> func2 = "aaaaa"::contains;
   assertFalse(func.and(func2).test("ok"));
   assertTrue(func.or(func2).test("ok"));
}

 @Test
 public void supplier() {
   String a = null;
   Supplier<Boolean> func = () -> {
     log.debug("실행됨.");
     return a != null;
  };

   // 매개변수 한개가 false 이므로 두개다 실행되지 않는 다.
   BiFunction<Supplier, Supplier, Boolean> bool = (v, v2) -> (boolean) v.get() && (boolean) v2.get();
   bool.apply(func, func);
}

 @Test
 public void unaryOperator() {
   UnaryOperator<String> func = v -> v + "a";
   assertEquals(func.apply("a"), "aa");
}
}

Optional

null 을 사용했을 때 문제점을 해결하기 위해 사용된다. null 을 사용하지 않고 값을 다룰수 있게 한다.

java.util.Optional<T> 에 대해 알아보겠다.

Optional 생성

  • empty - null 을 가진 Optional 객체를 생성한다.

  • of - 값을 가진 Optional 객체를 생성한다.

  • ofNullable - 매개변수가 null 인 경우 Optional.empty 로 아닌 경우 Optional.of 로 객체를 생성한다.

  • get - 값을 반환한다. null 인 경우 NoSuchElementException 발생한다.

Optional 조건

  • isPresent - 값이 null 인 경우 false, 아닌 경우 true 를 반환한다.

  • ifPresent - 값이 null 이 아닌 경우 지정한 Consumer 를 호출한다. 이전에는 if (val != null) { 실행영역 } 구문으로 사용했다.

  • orElse - 값이 null 인 경우 원하는 값으로 변경한다.

  • orElseGet - 값이 있는 경우 값을 반환하고 null 인 경우 지정된 Supplier 를 호출한다. 리터럴이 아닌 로직에 의한 값을 반환할 수 있다.

  • orElseThrow - 값이 있는 경우 값을 반환하고 null 인 경우 원하는 예외를 발생한다.

  • filter - 스트림의 filter 와 유사하나 값을 순차적으로 처리하지 않고 생성한 Optional 객체를 인수로 받고 Optional 로 반환한다.

  • map - 스트림의 map 과 유사하나 값을 순차적으로 처리하지 않고 생성한 Optional 객체를 인수로 받고 Optional 로 반환한다.

  • flatMap - 스트림의 flatMap 과 유사하나 값을 순차적으로 처리하지 않고 생성한 Optional 객체를 인수로 받고 Optional 로 반환한다.

OptionalInt, OptionalLong, OptionalDouble 에서 원시 자료형을 다룰 수 있다.

Stream

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

  • 순차 연산과 병렬 연산 처리가 있다.

  • 최종 연산을 하지 않으면 실제로 동작되지 않는 다.

  • 순차적으로 처리되므로 반복작업을 줄일 수 있는 연산부터 처리해야 한다.

  • 일반적인 순환문은 for 문이 좋다.

스트림 생성

  • builder - 빌더 패턴으로 스트림 생성

  • empty - 빈 스트림 생성

  • of - 하나 또는 그 이상의 매개변수를 설정하여 스트림을 생성한다.

  • iterate - 순환문을 이용하여 스트림을 생성한다.

  • generate

  • concat - 두 스트림을 하나로 만든다.

public class InitialStream {
   @Test
   public void builder() {
       List<Integer> result = Stream

               // 스트림은 제너릭 타입을 사용하므로 값의 타입을 추론할 수 있도록 파라미터 타입을 설정했다.
               // 타입을 설정하지 않으면 기본적 타입은 Object 로 선언된다
              .<Integer>builder().add(1).add(2).build()
               // 결과를 얻는 다.
              .collect(Collectors.toList());

       assertEquals(result, asList(1,2));
  }

   @Test
   public void empty() {
       List<Integer> result = Stream.<Integer>empty().collect(Collectors.toList());
       assertEquals(result, Collections.emptyList());
  }

   @Test
   public void of() {
       // todo of 는 파라미터 타입을 선언하지 않아도 추론이 가능하다. why?
       List<String> kor = Stream.of("가").collect(Collectors.toList());
       List<Integer> num = Stream.of(1, 2, 3).collect(Collectors.toList());

       assertEquals(kor, Collections.singletonList("가"));
       assertEquals(num, asList(1,2, 3));
  }

   @Test
   public void iterate() {
       // todo 어디에 쓸까?
       List<String> result = Stream
               // 무한 반복이 된다. limit 를 이용하여 횟수를 설정한다.
              .iterate("안녕", n -> n).limit(2)
              .collect(Collectors.toList());
       assertEquals(result, asList("안녕", "안녕"));
  }

   @Test
   public void generate() {
       // todo 어디에 쓸까?
       List<String> result = Stream
               // Supplier 함수 인터페이스를 사용 - 인수 없고 결과 있음
              .generate(() -> "안녕")
              .limit(2).collect(Collectors.toList());
       assertEquals(result, asList("안녕", "안녕"));
  }

   @Test
   public void concat() {
       List<Integer> result = Stream.concat(Stream.of(1,2), Stream.of(3,4)).collect(Collectors.toList());
       assertEquals(result, asList(1,2,3,4));
  }
}

중간 연산

  • filter - 순차적으로 지정한 Perdicate 를 호출한다. 스트림으로 반환한다.

  • map

  • mapToInt

  • mapToLong

  • mapToDouble

  • flatMap

  • flatMapToInt

  • flatMapToLong

  • flatMapToDouble

  • distinct

  • sorted

  • peek

  • limit

  • skip

  • boxed

    박싱은 int -> Integer 변경하는 것이고 언박싱은 반대이다. 즉 원시 유형을 참조 유형으로 변경하는 것을 의미하면 이를 자동으로 변경하는 것을 오토박싱이라고 한다.

@Log4j2
public class IntermediateStream {
   private List<String> color = Arrays.asList("Green", "Black", "Yellow", "Red", "Black", "Yellow");
   private List<Integer> number = Arrays.asList(4, 5, 1, 3, 6, 5, 1);

   @Test
   public void filter() {
       List<String> result =
               // stream() 컬랙션을 스트림으로 사용한다.
               color.stream()
                       // 필터를 이용하여 R 을 가진 데이터만 추출한다.
                      .filter(n -> n.contains("R"))
                      .collect(Collectors.toList());

       assertEquals(result, Collections.singletonList("Red"));
  }

   @Test
   public void map() {
       List<Integer> result = color.stream()
               // Function 함수 인터페이스를 사용하고 원하는 자료형의 데이터로 반환한다.
               // n -> n.length() 의 축약
              .map(String::length)
              .collect(Collectors.toList());

       assertEquals(result, Arrays.asList(5, 5, 6, 3, 5, 6));
  }

   @Test
   public void mapToInt() {
       // mapToInt 는 최종 데이터를 숫자로 반환하는 것을 목표하는 것 같다.
       // 컬랙션의 모든 합과 같은 최종 결과를 숫자로 표기할때 사용된다.
       int total = color.stream()
               // mapToInt 는 IntStream 으로 반환하며 IntStream 원시 자료형 int 로 반환한다.
              .mapToInt(String::length).sum();

       assertEquals(total, 30);
  }

   // mapToLong , mapToDouble 생략함

   @Test
   public void flatMap() {
       // 테스트
       // 2차 배열을 1차 배열로 만든다라는 설명으로 이해할 수 없었다.
       List<List<String>> list =
           Arrays.asList(Arrays.asList("a"), Arrays.asList("b", "c"));

       // 이해하기 위해 두가 코드로 작성하고 반환 자료형을 확인하였다.

       // map 과 flatMap 의 차이.
       // map 은 인자 그대로 반환하고 즉 아래처럼 스트림으로 만들면 스트림으로 반환된다.
       // flatMap 은 반환 자료형은 스트림의 제너릭 자료형으로 스트림의 값을 반환한다.
       list.stream()
               // n -> n.stream()
              .map(Collection::stream)
               // List<Stream<String>>
              .collect(Collectors.toList()).forEach(log::debug);
       List<String> result = list.stream().flatMap(Collection::stream)
               // List<String>
              .collect(Collectors.toList());
       result.forEach(log::debug);

       assertEquals(result, Arrays.asList("a", "b", "c"));
  }

   @Test
   public void flatMapToInt() {
       List<List<String>> list =
           Arrays.asList(Collections.singletonList("a"), Arrays.asList("b", "c"));

       int total = list.stream()
               // flatMapToInt 은 IntStream 으로 반환해야 한다. 즉 반환 자료형은 int 여야 한다.
               // mapToInt 를 이용하면 스트림의 값을 IntStream 으로 변환할 수 있다.
              .flatMapToInt(n -> n.stream().mapToInt(String::length)).sum();

       assertEquals(total, 3);
  }

   // flatMapToLong , flatMapToDouble 생략

   @Test
   public void distinct() {
       List<String> result = color.stream().distinct().collect(Collectors.toList());
       assertEquals(result, Arrays.asList("Green", "Black", "Yellow", "Red"));
  }

   @Test
   public void sorted() {
       List<Integer> result = number.stream().sorted().collect(Collectors.toList());
       assertEquals(result, Arrays.asList(1, 1, 3, 4, 5, 5, 6));

       List<Integer> result2 = number.stream().sorted((o1, o2) -> o2 - o1).collect(Collectors.toList());
       assertEquals(result2, Arrays.asList(6, 5, 5, 4, 3, 1, 1));
       result.forEach(log::debug);
       result2.forEach(log::debug);
  }

   @Test
   public void peek() {
       // peek = void consumer(T s)
       List<String> result = color.stream().peek(s -> {
           assertTrue(color.indexOf(s) > -1);
      }).collect(Collectors.toList());
       assertEquals( result,color);
  }

   @Test
   public void limit() {
       List<String> result = color.stream().limit(2).collect(Collectors.toList());
       assertEquals(result, Arrays.asList("Green", "Black"));
  }

   @Test
   public void skip() {
       List<String> result = color.stream().skip(1).collect(Collectors.toList());
       assertEquals(result, Arrays.asList("Black", "Yellow", "Red", "Black", "Yellow"));
  }
}

최종 연산

  • forEach

  • forEachOrdered

  • toArray

  • collect

  • min

  • max

  • count

  • anyMatch

  • allMatch

  • noneMatch

  • findFirst

  • findAny

  • reduce

@Log4j2
public class TerminalStream {
 private List<String> color = Arrays.asList("Green", "Black", "Yellow", "Red", "Black", "Yellow");
 private List<Integer> number = Arrays.asList(4, 5, 1, 3, 6, 5, 1);


 @Test
 public void forEach() {
   // peek 은 중간 연산이고 forEach 는 최종 연산이다.
   // peek 는 최종 연산을 하지 않으면 실행되지 않는 지연 연산에 속한다.
   color.forEach(s -> {
     log.debug(s);
     assertTrue(color.indexOf(s) > -1);
  });
}

 @Test
 public void forEachOrdered() {
   // forEach 병렬 스트림에서 정렬을 보장받지 못한다. 보장받기 위해 forEachOrdered 를 사용해야 한다.
   color.forEach(s -> {
     log.debug(s);
     assertTrue(color.indexOf(s) > -1);
  });
}

 @Test
 public void toArray() {
   String[] result = color.stream().toArray(String[]::new);
   assertArrayEquals(result, color.toArray());
}

 @Test
 public void collect() {
   List<String> result = number.stream().map(String::valueOf).collect(Collectors.toList());
   for(int i = 0; i < number.size(); i++) {
     assertEquals(result.get(i), String.valueOf(number.get(i)));
  }
}

 @Test
 public void min() {
   // orElse 는 null 인 경우 0 을 반환한다.
   int result = number.stream().min(Integer::compareTo).orElse(0);
   assertEquals(result, 1);
}

 @Test
 public void max() {
   int result = number.stream().max(Integer::compareTo).orElse(0);
   assertEquals(result, 6);
}

 @Test
 public void count() {
   long result = number.stream().count();
   assertEquals(result, 7);
}

 @Test
 public void anyMatch() {
   boolean aTrue = number.stream().anyMatch(s -> s == 1);
   assertTrue(aTrue);

   boolean aFalse = number.stream().anyMatch(s -> s == 0);
   assertFalse(aFalse);
}

 @Test
 public void allMatch() {
   // anyMatch 와 다름 점은 and 조건이다.
   boolean aFalse = number.stream().allMatch(s -> s == 1);
   assertFalse(aFalse);

   List<String> color = Arrays.asList("Green", "Black");
   boolean aTrue = color.stream().allMatch(s -> s.length() == 5);
   assertTrue(aTrue);
}

 @Test
 public void noneMatch() {
   // allMatch 의 반대이다.
   boolean aTrue = color.stream().noneMatch(s -> s.equals("Gray"));
   assertTrue(aTrue);
}

 @Test
 public void findFirst() {
   // 비어있지 않는 첫번째 값을 반환한다.
   String result = color.stream().filter(s -> s.equals("Black")).findFirst().orElse("none");
   assertEquals(result, "Black");
}

 @Test
 public void findAny() {
   // 순서와 상관없이 일치하는 값을 하나 반환한다.
   String result = color.stream().filter(s -> s.equals("Black")).findFirst().orElse("none");
   assertEquals(result, "Black");
}

 @Test
 public void reduce() {
   // 다양한 결과를 만들 수 있다.
   Map<String, Integer> result = color.stream().reduce(new HashMap<>(), (map, value) -> {
     map.put(value, value.length());
     return map;
  }, (map1, map2) -> {
     map1.putAll(map2);
     return map1;
  });

   color.forEach(s -> assertEquals(s.length(), result.get(s).intValue()));
}
}

Time package - 새로운 타임 패키지

이전의 Date, Calendar 클래스에는 오랜전에 개발되어 다양한 문제를 가지고 있다. 이를 극복하기 위해 새로운 클래스가 등장하게 되었다. LocalDate, LocalTime 과 LocalDateTime 에 대해 알아본다.

LocalDate

LocalDate 클래스 아래와 같은 특징이 있다.

  • 불변으로 스레드에 대해서 안전하다.

  • ISO-8601 (YYYY-MM-DD) 으로 시간이 없다.

  • 값 기반 클래스이므로 참조 동등성(==)이 아닌 동일성(eqauls)으로 비교해야 한다.

  • 모든 메서드는 정적으로 작성되어 있다.

사전 정의 된 포매터

포매터기술
ofLocalizedDate(dateStyle)로케일의 날짜 스타일이있는 포매터'2011-12-03'
ofLocalizedTime(timeStyle)로케일에서 시간 스타일이있는 포매터'10 : 15 : 30 '
ofLocalizedDateTime(dateTimeStyle)로케일에서 날짜와 시간 스타일을 가진 포매터'3 Jun 2008 11:05:30'
ofLocalizedDateTime(dateStyle,timeStyle)로케일의 날짜 및 시간 스타일이있는 포매터'3 Jun 2008 11:05'
BASIC_ISO_DATE기본 ISO 날짜'20111203'
ISO_LOCAL_DATEISO 현지 날짜'2011-12-03'
ISO_OFFSET_DATE오프셋이있는 ISO 날짜'2011-12-03 + 01 : 00'
ISO_DATE오프셋이 있거나없는 ISO 날짜'2011-12-03 + 01 : 00'; '2011-12-03'
ISO_LOCAL_TIME오프셋없는 시간'10 : 15 : 30 '
ISO_OFFSET_TIME오프셋 시간'10 : 15 : 30 + 01 : 00 '
ISO_TIME오프셋이 있거나없는 시간'10 : 15 : 30 + 01 : 00 '; '10 : 15 : 30 '
ISO_LOCAL_DATE_TIMEISO 지역 날짜 및 시간'2011-12-03T10 : 15 : 30'
ISO_OFFSET_DATE_TIME오프셋이있는 날짜 시간2011-12-03T10 : 15 : 30 + 01 : 00 '
ISO_ZONED_DATE_TIME지역 날짜 시간'2011-12-03T10 : 15 : 30 + 01 : 00 [유럽 / 파리]'
ISO_DATE_TIMEZoneId의 날짜 및 시간'2011-12-03T10 : 15 : 30 + 01 : 00 [유럽 / 파리]'
ISO_ORDINAL_DATE연중 무휴'2012-337'
ISO_WEEK_DATE년 및 주2012-W48-6 '
ISO_INSTANT순간의 날짜와 시간'2011-12-03T10 : 15 : 30Z'
RFC_1123_DATE_TIMERFC 1123 / RFC 822'Tue, 3 Jun 2008 11:05:30 GMT'
Symbol  Meaning                     Presentation      Examples
------ -------                     ------------     -------
  G       era                         text             AD; Anno Domini; A
  u       year                       year             2004; 04
  y       year-of-era                 year             2004; 04
  D       day-of-year                 number           189
  M/L     month-of-year               number/text       7; 07; Jul; July; J
  d       day-of-month               number           10

  Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
  Y       week-based-year             year             1996; 96
  w       week-of-week-based-year     number           27
  W       week-of-month               number           4
  E       day-of-week                 text             Tue; Tuesday; T
  e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
  F       week-of-month               number           3

  a       am-pm-of-day               text             PM
  h       clock-hour-of-am-pm (1-12) number           12
  K       hour-of-am-pm (0-11)       number           0
  k       clock-hour-of-am-pm (1-24) number           0

  H       hour-of-day (0-23)         number           0
  m       minute-of-hour             number           30
  s       second-of-minute           number           55
  S       fraction-of-second         fraction         978
  A       milli-of-day               number           1234
  n       nano-of-second             number           987654321
  N       nano-of-day                 number           1234000000

  V       time-zone ID               zone-id           America/Los_Angeles; Z; -08:30
  z       time-zone name             zone-name         Pacific Standard Time; PST
  O       localized zone-offset       offset-O         GMT+8; GMT+08:00; UTC-08:00;
  X       zone-offset 'Z' for zero   offset-X         Z; -08; -0830; -08:30; -083015; -08:30:15;
  x       zone-offset                 offset-x         +0000; -08; -0830; -08:30; -083015; -08:30:15;
  Z       zone-offset                 offset-Z         +0000; -0800; -08:00;

  p       pad next                   pad modifier     1

  '       escape for text             delimiter
  ''     single quote               literal           '
  [       optional section start
  ]       optional section end
  #       reserved for future use
  {       reserved for future use
  }       reserved for future use

출처: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

@Log4j2
public class LocalDateTest {

   @Test
   public void instance() {
       LocalDate localDate = LocalDate.now();
       log.debug(localDate);
  }

   @Test
   public void format() {
       LocalDate localDate = LocalDate.now();
       log.debug(localDate.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")));
  }

   @Test
   public void ymd() {
       LocalDate localDate = LocalDate.now();
       log.debug("{} 년 {} 월 {} 일({}) 올해 {} 일째 이번달 총 {}일",
               localDate.getYear(),
               localDate.getMonthValue(),
               localDate.getDayOfMonth(),
               localDate.getDayOfWeek(),
               localDate.getDayOfYear(),
               localDate.lengthOfMonth());
  }
}

타임 패키지는 엄청 쉽고 이해하기 좋게 잘 만들어졌다. 솔직히 쓸 내용도 없다 메뉴얼 보고 쓱쓱 짜면 된다. 만세!!!

https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html

https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html

https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html

Nashorn (나즈혼) - 자바스크립트 엔진

나즈혼이 먼가 했더니 자바의 자바스크립트 엔진이다. 프론트엔드 SPA 를 개발하다보면 모든 코드가 자바스크립트로 개발되어 있어 검색엔진이 이를 해독하지 못하게 된다. 그래서 SEO 라는 걸 구현해야 하는 데. 주로 Node.js 를 활용하는 게 일반적인다. 하지만 백엔드 서버 프로그램이 자바로 개발되었는 데 SEO 때문에 Node.js 구축해야 한다는 건 비효율적이다. 자바 8 나즈혼으로 SEO 를 원할하게 서비스 할 수 있는 지는 나도 모른다.

나즈혼은 ECMAScript 5.1 까지 지원되며 앞으로 표준으로 따라 지원을 확장해나간다고 했다.

예제 코딩은 따로 다뤄야할 것 같다. 웹팩을 이용하여 번들링된 리액트 코드를 나즈혼으로 실행해볼 생각이다.