Date와 Time 소개

Java 8에서 새로운 날짜와 시간 API가 만들어진 이유

  • 이전에 사용하던 java.util.Date 클래스는 mutable하기 때문에 thread safe 하지 않다. (Set이 가능하다.)
    • 새로 나온 LocalDate는 immutable하다! (java.time.*에 있음)
  • 클래스 이름이 명확하지 않다. Date지만 시간을 다룬다.
  • 버그가 발생할 여지가 많다. (타입 안정성이 없고, 월이 0부터 시작한다... 등등)
  • 날짜 시간 처리가 복잡한 애플리케이션에는 보통 Joda Time을 쓰곤 했다. 

Month의 경우 MagicConstant로 매직넘버도 가능하고, 기존 달인 1~12가아닌 0~11로 주의해서 사용해야 한다.

 

Java 8에서 제공하는 Date-Time API

  • JSR-310 스펙의 구현체를 제공한다.
  • 디자인 철학
    • Clear
    • Fluent
    • Immutable
    • Extensible

 

주요 API

  • 기계용 시간 (machine time)과 인류용 시간 (human time)으로 나눌 수 있다.
  • 기계용 시간은 EPOCK (1970년 1월 1일 0시 0분 0초)부터 현재까지의 타임스탬프를 표현한다.
  • 인류용 시간은 우리가 흔히 사용하는 연,월,일,시,분,초 등을 표현한다.
  • 타임스탬프는 Instant를 사용한다.
  • 특정 날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)를 사용할 수 있다.
  • 기간을 표현할 때는 Duration(시간 기반)과 Period(날짜 기반)를 사용할 수 있다.
  • DateTimeFomatter를 사용해서 일시를 특정한 문자열로 formatting할 수 있다.

 

Date와 Time API
지금 시간을 기계 시간으로 표현하는 방법

  • Instant.now()
    • 현재 UTC (GMT)를 리턴
  • Universal Time Coordinated == Gereenwich Mean Time
Instant now = Instant.now();
System.out.println(now);        // 기준 UTC
System.out.println(now.atZone(ZoneId.of("UTC")));   // 위와 같음

System.out.println(ZoneId.systemDefault());     // 시스템 기준 시점
ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault()); // 시스템 기준 시점 시간
System.out.println(zonedDateTime);

 

인류용 일시를 표현하는 방법

  • LocalDateTime.now()
    • 현재 시스템 Zone에 해당하는 일시를 리턴 (로컬 시간)
  • LocalDateTime.of(int, Month, int, int, int, int)
    • 로컬의 특정 일시를 리턴
  • LocalDateTime.of(int, Month, int, int, int, int, ZoneId)
    • 특정 Zone의 특정 일시를 리턴
LocalDateTime now = LocalDateTime.now();    // Local은 시스템 정보 확인해서 시간을 가져옴
System.out.println(now);

LocalDateTime birthDay = LocalDateTime.of(1995, Month.MARCH, 10, 0, 0, 0);
ZonedDateTime nowInKorea = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
System.out.println("birthDay : " + birthDay);
System.out.println("korea : " + nowInKorea);

 

기간을 표현하는 방법

  • Period / Duration .between()

 

 

파싱 또는 포매팅

// 포맷
LocalDateTime now = LocalDateTime.now();
System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
System.out.println(now.format(MMddyyyy));       // 10/13/2022

// 파싱
DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
LocalDate birthDay = LocalDate.parse("03/10/1995", MMddyyyy);
System.out.println(birthDay);       // 1995-03-10

 

레거시 API 지원

  • GregorianCalendar와 Date 타입의 인스턴스를 Instant나 ZonedDateTime으로 변환 가능
  • java.util.TimeZone 에서 java.time.ZoneId로 상호 변환 가능
Date date = new Date();
Instant instant = date.toInstant();
Date newDate = Date.from(instant);

GregorianCalendar gregorianCalendar = new GregorianCalendar();
LocalDateTime now = gregorianCalendar.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[JAVA 8] ParallelSort  (0) 2022.10.15
[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06

Optional 소개

자바 프로그래밍에서 NullPointerException과 같은 에러들을 종종 볼 수 있다. 이러한 에러의 경우 컴파일 에러가 아닌 런타임 에러로 프로젝트에서 발생하면 치명적일 수 있는 에러이다. 

 

보통 NullPointerException의 경우 아래와 같은 상황에서 발생한다.

  • Null 리턴 (설계상)
  • Null 체크 실수 (설계상)

나도 코딩테스트를 보며 NullPointerException을 가끔 본다. 이럴 때마다 설계의 중요성을 한번씩 깨닫게 되는 것 같다..

 

하지만, 메소드에서 작업 중 특별한 상황에서 값을 제대로 리턴을 할 수 없는 경우가 있을 수 있다. 이러한 경우에는 아래와 같은 방법이 있을 것이다.

  • 예외를 던진다. (비싸다.. 스택트레이스(에러가 발생하기 전까지의 콜스타일 저장)를 찍어두기때문이다.)
    • 로직을 처리할 때 Exception처리는 좋은 로직이 아니다!
  • null을 리턴한다. (비용 문제가 없지만, 코드를 사용하는 클라이언트 코드에서 주의해야 한다.)
  • Optional을 리턴한다. (클라이언트에 코드에게 명시적으로 빈 값일 수도 있다는 것을 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)

null을 리턴하는 경우 설계상 실수가 있을 수 있지만, Optional을 사용한다면 실수를 없앨 수 있다고 생각이 든다. 그렇다면 Optional을 알아보자.

 

Optional

  • 오직 값 한개가 들어있을 수도 없을 수도 있는 컨테이너

 

주의할 점

  • 리턴값으로만 쓰기를 권장 (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)
    • 메소드 매개변수 타입) 매개변수로 받았지만, 메소드 안에서 다시 체크를 해야하기 때문에 코드가 길어지는 문제가 발생
    • 맵의 키 타입) 맵 키의 특징 중 하나는 키가 Null이 아닌 것 이다. 이를 무너뜨린다..
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말자.
  • 프리미티브 타입용 Optional은 따로 있다. OptionalInt, OptionalLong....
    • 프리미티브 타입의 경우 Optional.of(10)과 같이 사용하면 박싱, 언박싱이 안에서 이루어지기 때문에 성능이 안좋다. 이를 해결하고자 OptionalInt, OptionalLong, OptionalDouble과 같은 Optional을 사용하자.
  • Collection, Map, Stream Array, Optional은 Optional로 감싸지 말자.
    • 이미 container의 성격을 갖고 있다. 굳이 감쌀 이유가 없다.

 

Optional 만들기

  • Optional.of(instance)
    • .of 는 instance가 Null이 무조건 아닐 때 사용
  • Optional.ofNullable(instance) 
    • .ofNullable 은 instance가 null일 수도 있을 때 사용
  • Optional.empty()

 

Optional에 값이 있는지 없는지 확인하기

  • isPresent()
  • isEmpty()
    • Java 11부터 제공

 

Optional에 있는 값 가져오기

  • get()
  • 만약에 비어있는 Optional에서 무언가를 꺼낸다면??
    • 런타임 에러가 발생한다. (NoSuchElementException)

 

Optional에 값이 있는 경우에 그 값을 가지고 ~~를 하라.

  • isPresent(instance)
  • ex) Spring으로 시작하는 수업이 있으면 id를 출력하라

 

Optional에 값이 있으면 가져오고 없는 경우에 ~~를 리턴하라.

  • orElse(T)
  • ex) JPA로 시작하는 수업이 없다면 비어있는 수업을 Return

 

Optional에 값이 있으면 가져오고 없는 경우에 ~~를 하라.

  • orElseGet(Supplier)
  • ex) JPA로 시작하는 수업이 없다면 새로 만들어서 Return

 

Optional에 갑싱 있으면 가져오고 없는 경우 에러를 던져라

  • orElseThrow()

 

Optional에 들어있는 값 걸러내기

  • Optional filter(Predicate)

 

Optional에 들어있는 값 변환하기

  • Optional map(Function)
  • Optional flatMap(Function)
    • Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리 (안에 있는 Optional이 한번 까져서 나옴)

코드로 확인해보자.

 

 

package me.qazyj.java8;

import java.time.Duration;
import java.util.*;

public class OptionalTest {

    public static void main(String[] args) {

        List<OnlineClass> springClass = new ArrayList<>();
        springClass.add(new OnlineClass(1, "spring boot", true));
        springClass.add(new OnlineClass(6, "rest api", false));

        Optional<OnlineClass> findStartSpring = springClass.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();       // 찾은 첫번째 값을 return
        System.out.println(findStartSpring.isPresent());        // id 1이 spring으로 시작함 ==>> true
        //값이 있는지 없는지 확실하지 않을 때 ifPresent로 확인 후 있을 떄만 실행
        //findStartSpring.ifPresent(oc -> System.out.println(oc.getTitle()));

        //없으면 생성 x(있어도 생성하고 Return 함) orElse
        Optional<OnlineClass> findStartJpa = springClass.stream()
                .filter(oc -> oc.getTitle().startsWith("jpa"))
                .findFirst();       // 찾은 첫번째 값을 return
        OnlineClass onlineClass = findStartJpa.orElse(new OnlineClass(10, "New class", false));
        System.out.println(onlineClass.getTitle());

        // 에러
        //OnlineClass onlineClass1 = findStartJpa.orElseThrow(IllegalStateException::new);

        // is.empty - 비어있을 떄 true / 있을 떄 false
        Optional<OnlineClass> onlineClass2 = findStartSpring.filter(oc -> oc.getClosed());
        System.out.println("empty : " + onlineClass2.isEmpty());

    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Date와 Time  (0) 2022.10.12
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23

Stream 소개

Stream이란 용어가 익숙하지 않을 수 있다. Stream이란 컨베이너 벨트에 데이터를 놓고 컨베이너 벨트를 지나가며 원하는 형식에 따라 데이터 처리를 하는 것이라고 보면 된다.

 

Stream

  • sequence of elements supporting sequential and parallel aggregate operations
  • 데이터를 담고 있는 저장소(컬렉션)가 아니다.
  • Functional in nature, 스트림이 처리하는 데이터 소스를 변경하지 않는다. (기존 데이터는 그대로라고 보면 된다.)
  • 스트림으로 처리하는 데이터는 오직 한번만 처리한다.
  • 무제한일 수도 있다. (Short Circuit 메소드를 사용해서 제한할 수 있다.)
  • 중개 오퍼레이션은 근본적으로 lazy하다. (lazy 함은 종료 오퍼레이션이 오기전까지, 작업을 수행하지 않는다. 라고 보면 좋다.)
  • 손쉽게 병렬 처리 할 수 있다.

 

스트림 파이프라인

  • 0 또는 다수의 중개 오퍼레이션 (intermediate operation)과 한개의 종료 오퍼레이션 (terminal operation으로 구성한다.
  • 스트림의 데이터 소스는 오직 터미널 오퍼레이션을 실행할 때에만 처리한다.

 

중개 오퍼레이션

  • Stream을 리턴한다.
  • Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다. (대부분은 Stateless지만 distinct나 sorted 처럼 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다.)
  • filter, map, limit, skip, sorted, ....

 

종료 오퍼레이션

  • Stream을 리턴하지 않는다. (Stream을 다른 자료구조로 변경하기 때문)
  • collect, allMatch, count, forEach, min, max, ....

 

이제 코드로 확인해보자.

package me.qazyj.java8;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamTest {

    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("kyj");
        names.add("yj");
        names.add("kim");
        names.add("young_jin");

        Stream<String> stringStream = names.stream().map(String::toUpperCase);
        names.forEach(System.out::println); // 기존 데이터는 소문자 그대로이다.

        System.out.println("==============");

        // 중개 오퍼레이션이 lazy함의 증거로 아무출력도 일어나지 않는다.
        names.stream().map((s) -> {
            System.out.println(s);
            return s.toUpperCase();
        });

        // 중개 오퍼레이션 1개와 종료 오퍼레이션 1개로 stream 처리한 예시
        // 종료 오퍼레이션이 왔기때문에 출력이 발생한다.
        // 종료 오퍼레이션은 아래와 같이 Stream이 아닌 List나 Set과 같은 자료형임을 볼 수 있다.
        List<String> collect = names.stream().map((s) -> {
            System.out.println(s);
            return s.toUpperCase();
        }).collect(Collectors.toList());

        // 병렬 처리 예시
        List<String> collect1 = names.parallelStream().map(String::toUpperCase)
                .collect(Collectors.toList());
        collect1.forEach(System.out::println);
    }
}

병렬 처리할 때 사용하는 parallelStream의 경우는 오히려 성능을 저하시킬 수 있다. 상황에 따라서 병렬로 하는게 빠를 수 있고, 더 느릴 수 있기 때문에 병렬을 쓸 때와 쓰지 않았을 때의 시간 차이를 보고 적절한 방법으로 사용하는걸 권장한다.

 

병렬처리 테스트에 관련한 자료는 아래

 

[JAVA 8] ParallelSort

Arrays.parallelSort() Fork/Join 프레임워크를 사용해서 배열을 병렬로 정렬하는 기능을 제공한다. 병렬 정렬 알고리즘 배열을 둘로 계속 쪼갠다. 합치면서 정렬한다. 이전에 parallelStream을 설명할 때 말

qazyj.tistory.com

 

 

 

Stream API

스트림 API 사용 예시

 

걸러내기

  • Filter (Predicate)
  • 예) 문자열 시작이 kim인 데이터만 새로운 스트림으로

 

변경하기 

  • Map(Function) 또는 FlatMap(Function)
  • 예) 각각의 User 인스턴스에서 String name만 새로운 스트림으로 
  • 예) List<Stream<String>>을 String의 스트림으로

 

생성하기

  • Generate(Supplier) 또는 Iterate(T seed, UnaryOperator)
  • 예) 2씩 증가하는 무제한 숫자 스트림
  • 예) 랜덤 Integer 무제한 스트림 (limit을 주지 않으면 무제한으로 간다.)

 

제한하기

  • limit(long) 또는 skip(long)
  • 예) 최대 5개의 요소가 담긴 스트림을 리턴한다.
  • 예) 앞에서 3개를 뺀 나머지 스트림을 리턴한다.

스트림에 있는 데이터가 특정 조건을 만족하는지 확인

  • anyMatch(), allMatch(), nonMatch()
  • 예) k로 시작하는 문자열이 있는지 확인한다. (true 또는 false return)
  • 예) 스트림에 있는 모든 문자열의 길이가 10보다 작은지 확인한다.

 

개수 세기

  • count()
  • 예) k로 시작하는 문자열의 개수를 센다.

 

스크림을 데이터 하나로 뭉치기

  • reduce(identity, BiFunction), collect(), sum(), max
  • 예) 모든 숫자 합 구하기
  • 예) 모든 데이터를 하나의 List 혹은 Set에 옮겨 담기

이제 코드로 알아보자.

 

package me.qazyj.java8;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OnlineClassTest {

    public static void main(String[] args) {
        List<OnlineClass> springClass = new ArrayList<>();
        springClass.add(new OnlineClass(1, "spring boot", true));
        springClass.add(new OnlineClass(2, "spring data jpa", true));
        springClass.add(new OnlineClass(3, "spring mvc", false));
        springClass.add(new OnlineClass(4, "spring core", false));
        springClass.add(new OnlineClass(5, "spring batch", true));
        springClass.add(new OnlineClass(6, "rest api", true));

        System.out.println("spring 문자열로 시작하는 OnlineClass의 id");
        springClass.stream().filter(oc -> oc.getTitle().startsWith("spring"))
                        .forEach(oc -> System.out.println(oc.getId()));
        System.out.println();

        System.out.println("close 되지 않은 OnlineClass의 id");
        springClass.stream().filter(oc -> !oc.getClosed())   // Predicate.not(OnlineClass::getClosed()) 도 가능
                        .forEach(oc -> System.out.println(oc.getId()));
        System.out.println();

        System.out.println("수업 이름만 모아서 스트림 만들기");
        springClass.stream().map(OnlineClass::getTitle)
                        .forEach(System.out::println);
        System.out.println();


        List<OnlineClass> javaClass = new ArrayList<>();
        javaClass.add(new OnlineClass(7, "The Java, Test", true));
        javaClass.add(new OnlineClass(8, "The Java, Code", true));
        javaClass.add(new OnlineClass(9, "The Java, 8 to 11", false));

        List<List<OnlineClass>> kyjEvents = new ArrayList<>();
        kyjEvents.add(springClass);
        kyjEvents.add(javaClass);


        System.out.println("두 수업 목록에 들어있는 모든 OnlineClass의 id");
        // flatMap이란 List 등과 같은 컬렉션의 데이터들을 풀어 놓는 것?? 이라고 보면된다. 현재는 OnlineClass 하나 하나 데이터들이 나오게 된다고 보면 된다.
        kyjEvents.stream().flatMap(Collection::stream)      // list -> stream 으로 flat
                        .forEach(oc -> System.out.println(oc.getId()));
        System.out.println();

        System.out.println("2씩 증가하는 무제한 스트림 중 앞에 2개 뺴고 최대 10개까지만");
        Stream.iterate(0, i -> i + 2)
                .skip(2)
                .limit(10)
                .forEach(System.out::println);
        System.out.println();

        System.out.println("자바 수업 중에서 Test가 들어있는 수업이 있는지 확인");
        boolean test = javaClass.stream().anyMatch(oc -> oc.getTitle().contains("Test"));
        System.out.println(test);

		//.collect(Collectors.toList()) 대신 count()를 쓰면 개수가 나옴
        System.out.println("스프링 수업 중에서 제목에 spring이 들어간 것만 모아서 List로 만들기");
        List<String> spring = springClass.stream().filter(oc -> oc.getTitle().contains("spring"))
                .map(OnlineClass::getTitle)
                .collect(Collectors.toList());
        spring.forEach(System.out::println);
        System.out.println();


    }
}

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[Java 8] Date와 Time  (0) 2022.10.12
[Java 8] Optional  (0) 2022.10.07
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23
ListIterator  (0) 2022.09.23

인터페이스 기본 메소드와 스태틱 메소드

기본메소드

  • 인터페이스에 메소드 선언이 아니라 구현체를 제공하는 방법
  • 해당 인터페이스를 구현한 클래스를 깨트리지 않고 새 기능을 추가할 수 있다.
  • 기본 메소드는 구현체가 모르게 추가된 기능으로 그만큼 리스크가 있다.
    • 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다. (nullpointException과 같은)
    • 반드시 문서화 할 것 (@implSpec 자바독 태그 사용)
  • Object가 제공하는 기능 (equals, hasCode)은 기본 메소드로 제공할 수 없다.
    • 구현체가 재정의해야 한다.
  • 본인이 수정할 수 있는 인터페이스에만 기본 메소드를 제공할 수 있다.
  • 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다.
  • 인터페이스 구현체가 재정의 할 수도 있다.

 

스태틱 메소드

  • 해당 타입 관련 헬터 또는 유틸리티 메소드를 제공할 때 인터페이스에 스태틱 메소드를 제공할 수 있다. 

 

위의 내용들을 코드로 이해해보자.

package me.qazyj.java8;

public interface FooInterface {
    void printName();

    /**
     * @implSpec
     * 이 구현체는 getName()으로 가져온 문자열을 대문자로 바꿔 출력한다.
     */
    default void printNameUpperCase(){
        System.out.println(getName().toUpperCase());
    }

    String getName();
}
package me.qazyj.java8;

public class DefaultFoo implements FooInterface{
    String name;

    public DefaultFoo(String name){
        this.name = name;
    }

    // 재정의도 가능하다.
    @Override
    public void printNameUpperCase(){
        System.out.println(getName().toUpperCase());
    }

    @Override
    public void printName() {
        System.out.println(this.name);
    }

    @Override
    public String getName() {
        return name;
    }


}

위의 코드에서 기본 메소드란 printNameUpperCase()를 말한다. name을 대문자로 출력하는 기능을 추가했지만, 인터페이스에만 코드를 추가해주면 된다. (default 선언 필수!! 하지 않으면 컴파일 에러가 발생한다.) 물론, 재정의도 가능하다. 하지만, 이전에 리스크가 있다는 말을 했다. 이유는 name이 Null값인 경우 NullPointException과 같은 에러가 발생할 수 있다. 개발자도 사람인지라 정리를 잘 해놓지 않으면 후에 에러를 알게되었을 때 찾기 쉽지 않을 수 있다. (꼭! 문서화 하여 정리하자! 정리 방법은 위의 코드를 보면 예시가 있다.)

 

그렇다면, 두개의 interface를 구현하는 구현체가 있다고 가정해보자. 두개의 interface에는 같은 이름으로된 기본메소드가 있다면 어떻게 될까?? 당연히 컴파일에러가 발생한다. 해결 방법으로는 간단하다. 하나의 인터페이스 기본메소드를 없애주거나, 구현체에서 기본메소드를 재정의해주면 된다. 

 

Object가 제공하는 기능 (equals, hasCode)은 기본 메소드로 제공할 수 없다고했다. 직접 확인해보자.

기본메소드로 선언하면 컴파일 에러가 뜬다.
구현체에서 재정의해서는 사용할 수 있다.

위를 보면 기본메소드로는 선언이 불가하고, 구현체에서 재정의해서는 사용할 수 있는 것을 볼 수 있다.

 

 

static 메소드는 어떻게 사용할까.

package me.qazyj.java8;

public interface FooInterface {
    
    ...

    static void printAnything() {
        System.out.println("kyj");
    }

    ...
}
package me.qazyj.java8;

public class FooTest {
    public static void main(String[] args) {
        DefaultFoo foo = new DefaultFoo("kyj");
        foo.printName();
        foo.printNameUpperCase();

        FooInterface.printAnything();		// 생성해주지 않아도 바로 사용가능하다.
    }
}

static으로 선언해주면 별다른 생성없이 바로 사용할 수 있는 것을 볼 수 있다.

 

 

 

 

자바 8에서 추가한 기본 메소드로 인한 API 변화

Iterable의 기본 메소드

  • forEach()
  • spliterator()

 

Collection의 기본 메소드

  • stream() / parallelStream()
  • removeIf(Predicate)
  • spliterator()

 

Comparator의 기본 메소드 및 스태틱 메소드

  • reversed()
  • thenComparing()
  • static reverseOrder() / naturalOrder()
  • static nullsFirst() / nullsLast()
  • static comparing()

위의 내용을 코드로 이해해 보자.

 

    public static void main(String[] args) {

        List<String> name = new ArrayList<>();
        name.add("kyj");
        name.add("yj");
        name.add("qazyj");


        System.out.println("=====Iterable=====");
        // Iterable
        name.forEach(System.out::println);		// 출력 kyj + "\n" + yj + "\n" + qazyj

        System.out.println();

        Spliterator<String> spliterator = name.spliterator();
        Spliterator<String> spliterator2 = spliterator.trySplit();// 앞에서부터 반으로 쪼갠걸 가져옴 (나머지 반은 원래 변수에)
        while(spliterator2.tryAdvance(System.out::println));	// 출력 kyj
        System.out.println("==========");
        while(spliterator.tryAdvance(System.out::println));		// 출력 yj + "\n" + qazyj



        System.out.println("=====Collection=====");
        //Collection
        name = new ArrayList<>();
        name.add("kyj");
        name.add("yj");
        name.add("qazyj");

        long count = name.stream().map(String::toUpperCase)
                .filter(s -> s.startsWith("K"))
                .count();
                //.collect(Collectors.toSet());
        System.out.println(count);		// 출력 1   (kyj 하나만 있기 때문) 

        name.removeIf(s -> s.startsWith("k"));
        name.forEach(System.out::println);		// 출력 yj + "\n" + qazyj


        System.out.println("=====Comparator=====");
        //Comparator
        name = new ArrayList<>();
        name.add("kyj");
        name.add("yj");
        name.add("qazyj");

        name.sort(String::compareToIgnoreCase); // 오름차순
        Comparator<String> compareToIgnoreCase = String::compareToIgnoreCase;
        name.sort(compareToIgnoreCase.reversed());  // 내림차순 추가적으로 더 정렬하고 싶으면 .thenComparing() 사용
        

    }

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23
ListIterator  (0) 2022.09.23
SnakeGame  (0) 2022.07.16

함수형 인터페이스 (Functional Interface)

  • 추상 메소드를 1개만 가지고 있는 인터페이스
  • SAM (Single Abstract Method) 인터페이스
@FunctionalInterface    // 자바가 제공해주는 functionalInterface (인터페이스를 견고하게 관리)
public interface RunSomthing {

    void doIt();    // 하나만 있으면 함수형 인터페이스 (2개이상부터 X)
}

@FunctionalInterface 어노테이션을 추가하면 함수형 인터페이스로 선언해줍니다. (추상 메소드를 하나 더 추가하려하면 개발자가 잘 못된 것을 알려줍니다.)  (이를통해, 디버깅 전에 에러를 확인할 수 있도록합니다.)

추상 메소드를 2개이상하는 경우 에러 표시

 

 

 

람다 표현식 (Lamda Expressions)

  • 함수형 인터페이스의 인스턴스를 만드는 방법으로 쓰일 수 있다.
  • 코드를 간결하게 할 수 있다.
  • 메소드 매개변수, 리턴 타입, 변수로 만들어 사용할 수 있다.
        // 익명 내부 클래스 (함수형 인터페이스가 아닌 경우)
        RunSomthing runSomthing = new RunSomthing() {
            @Override
            public void doIt() {

            }
        };

        // 함수형 인터페이스의 경우 java 8 부터 람다로 변경 가능
        RunSomthing runSomthingLamda = () -> System.out.println("Lamda");

함수형 인터페이스의 경우 람다 표현식으로 이용해 코드를 훨씬 간결하게 만들 수 있는 것을 볼 수 있다. 

 

 

 

자바에서 함수형 프로그래밍

  • 함수를 First class object로 사용할 수 있다.
  • 순수 함수 (Pure Function)
    • 사이드 이팩트가 없다. (함수 밖에 있는 값을 변경하지 않는다.) (같은 값을 넣으면 같은 값으로 결과가 나와야함)
    • 상태가 없다. (함수 밖에 있는 값을 사용하지 않는다.)
  • 고차 함수 (Higher-Order Function)
    • 함수가 함수를 매개변수로 받을 수 있고 함수를 리턴할 수도 있다.
  • 불변성

 

 

Java에서 제공하는 기본 함수형 인터페이스

 

java.util.function (Java Platform SE 8 )

Interface Summary  Interface Description BiConsumer Represents an operation that accepts two input arguments and returns no result. BiFunction Represents a function that accepts two arguments and produces a result. BinaryOperator Represents an operation u

docs.oracle.com

  1. Function<T,R>
    • T 타입을 받아서 R 타입을 리턴하는 함수 인터페이스
      • R apply(T t)
      • Function<Integer, Integer> plus10 = (i) -> i+10;  과 같이 람다를 이용해 쉽게사용할 수 있다.
    • 함수 조합용 메소드
      • andThen
      • compose
  2. BiFunction<T,U,R>
    • 두 개의 값 (T, U)를 받아서 R 타입을 리턴하는 함수 인터페이스 (Function에서 파라미터만 추가 된 느낌)
      • R apply(T t, U u)
  3. Consumer<T>
    • T 타입을 받기만 하는 함수 인터페이스 (리턴이 없다.)
      • void accept(T t)
  4. Suplier<T>
    • T 타입의 값을 제공하는 함수 인터페이스
      • T get()
  5. Predicate<T>
    • T 타입을 받아서 boolean을 리턴하는 함수 인터페이스
      • boolean test(T t)
  6. UnaryOperator<T>
    • Function<T,R>의 특수한 형태로 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스 (Function<T,T> 느낌)
  7. BinaryOperator<T>
    • BiFunction<T,R,U>의 특수한 형태로 동일한 타입의 입력값 두개를 받아 리턴하는 함수 인터페이스 (BiFunction<T,T,T> 느낌)
  8. ..... 등등 다양한 함수형 인터페이스들을 제공해 주고있다.

 

 

 

 

람다 표현식의 Scope

private void run() {
    final int baseNumber = 10;

    // 로컬 클래스
    class LocalClass {
        void printBaseNumber() {
            System.out.println(baseNumber);
        }
    }

    // 익명 클래스
    Consumer<Integer> integerConsumer = new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            System.out.println(baseNumber);
        }
    };

    // 람다
    IntConsumer printInt = (i) -> {
        System.out.println(i+baseNumber);
    };

    printInt.accept(10);
}

run 메소드에서 로컬클래스, 익명클래스, 람다를 통해 run 메소드에서 정의한 변수를 사용하면 정상적으로 사용하는 것을 볼 수 있었다. 하지만 로컬클래스, 익명클래스와 람다는 다른 점이 하나 있다. 로컬클래스와 익명클래스는 쉐도잉이 되는 반면, 람다는 쉐도잉이 되지 않는다는 것이다. 그렇다면 쉐도잉이 무엇일까??

말하자면 그림과 같다. 위의 코드를 보면 run 에서 baseNumber이라는 변수를 선언했다. 그 후 baseNumber라는 변수를 로컬클래스, 익명클래스에서 사용한다면 새로운 값으로 대체할 수 있다. 하지만, 람다의 경우에는 같은 변수명을 사용할 수 없다는 에러가 발생한다. 

실제 람다에서 변수를 선언했을 때 나는 에러

그렇다면 이유는 무엇일까?? 이유는 로컬클래스와 익명클래스는 Scope이 선언해주는 곳과 같지 않기때문이고, 람다의 경우는 Scope이 선언해주는 곳과 같기 때문이다.

 

 

 

메소드 레퍼런스

람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현할 수 있다.

메소드 참조하는 방법

스태틱 메소드 참조 타입::스태틱 메소드
특정 객체의 인스턴스 메소드 참조 객체 레퍼런스::인스턴스 메소드
임의 객체의 인스턴스 메소드 참조 타입::인스턴스 메소드
생성자 참조 타입::new
  • 메소드 또는 생성자의 매개변수로 람다의 입력값을 받는다.
  • 리턴값 또는 생성한 객체는 람다의 리턴값이다.

예를들어 보겠다. 먼저 아래와 같은 Greeting이라는 클래스가 있다.

package me.qazyj.java8;

public class Greeting {

    private String name;

    public Greeting() {}

    public Greeting(String name){
        this.name = name;
    }

    public String hello(String name) {
        return "hello "+ name;
    }

    public static String hi(String name){
        return "hi " + name;
    }
}

사용한 예시를 보면 아래와 같다.

    public static void main(String[] args) {
        // static 메소드
        UnaryOperator<String> hi = Greeting::hi;

        // non-static 메소드
        //UnaryOperator<String> hi = Greeting::hello; (불가)
        Greeting greeting = new Greeting();
        UnaryOperator<String> hello = greeting::hello;

        // 임의 객체의 인스턴스 메소드 참조
        String[] names = {"k", "y", "j"};
        Arrays.sort(names, String::compareToIgnoreCase);//타입::인스턴스 메소드

        // 입력값이 없는 생성자로 생성
        Supplier<Greeting> newGre = Greeting::new;

        // 입력값이 있는 생성자로 생성
        Function<String, Greeting> kyjGree = Greeting::new;
        Greeting kyj = kyjGree.apply("kyj");
    }

 

'JAVA' 카테고리의 다른 글

[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
ListIterator  (0) 2022.09.23
SnakeGame  (0) 2022.07.16

List 인터페이스의 경우 Collection의 구현체 외 iterator , add , remove , equals  hashCode 메소드 의 계약에 추가 규정을 둡니다. 편의를 위해 다른 상속된 메서드에 대한 선언도 여기에 포함됩니다. 

 

오늘 공부할 것은 Iterator에서 ListIterator에 관한 것 입니다. 

 

ListIterator은 아래와 같이 커서(^)와 같은 위치 값을 갖고 있습니다. 이를 next()와 previous()를 통해 움직입니다. hashNext(), hashprevious()를 통해 다음값이 있으면 true, 없다면 false를 반환 받습니다. 또한, add(E), remove(), set(E)를 통해 값을 넣고, 빼고, 바꿀 수 있습니다.

위와 같이보면 위치를 가리키고있는 것을 제외하면 ArrayList, LinkedList와 유사하다고 생각합니다.

 

 

시간복잡도는 어떨까요. 먼저, ArrayList와 LinkedList의 시간복잡도 입니다.

  Method ArrayList LinkedList
add at last index add() O(1) O(1)
add at given index add(index, value) O(N) O(1)
remove by index remove(index) O(N) O(1)
remove by value remove(value) O(N) O(1)
get by index get(index) O(1) O(N)
search by value indexOf(value) O(N) O(N)

추가, 삭제 시 ArrayList보다 LinkedList가 훨씬 빠릅니다. 하지만, LinkedList의 경우 index로 맨앞, 맨뒤가 아닌 중간에 값을 추가, 삭제할 경우에 탐색해야하기 때문에 사실상 O(N)이 걸린다고 봐야합니다.

 

결론적으로 중간에 데이터를 추가, 삭제하는 작업이 많은 경우 ArrayList, LinkedList 모두 좋은 자료구조가 아니라고 볼 수 있습니다. 

 

index를 이동하며, 데이터를 추가, 삭제해야하는 경우에 적합한 자료구조는 바로 ListIterator입니다.

 

ListIterator의 경우 index를 한칸씩 이동하는 커서가있고 previous(), next() 메소드를 가지고 있습니다. 또한, List 인터페이스가 LinkedList 구현체로 되어있다면 추가, 삭제에 O(1)의 시간복잡도를 사용합니다. (이 부분이 이해되지 않는다면 ArrayList와 LinkedList의 차이점을 공부해봅시다.)

 

 

위의 개념을 완벽하게 이해하고있다면 쉽게 풀 수 있는 문제가 있습니다. 

 

1406번: 에디터

첫째 줄에는 초기에 편집기에 입력되어 있는 문자열이 주어진다. 이 문자열은 길이가 N이고, 영어 소문자로만 이루어져 있으며, 길이는 100,000을 넘지 않는다. 둘째 줄에는 입력할 명령어의 개수

www.acmicpc.net

 

 

 

참고

listIterator 공식문서

 

Java Platform SE 8

 

docs.oracle.com

Iterator 공식문서

 

Iterator (Java Platform SE 8 )

An iterator over a collection. Iterator takes the place of Enumeration in the Java Collections Framework. Iterators differ from enumerations in two ways: Iterators allow the caller to remove elements from the underlying collection during the iteration with

docs.oracle.com

list 공식문서

 

List (Java Platform SE 8 )

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the lis

docs.oracle.com

 

'JAVA' 카테고리의 다른 글

[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23
SnakeGame  (0) 2022.07.16

우연히 youtube를 보다가 java로 만든 뱀 게임이 있길래 새벽에 심심해서 따라해 봤다...하하하하하

 

영상의 길이는 40분이 살짝 넘어서 따라해볼만 한 것 같았다.

 

 

혹시나 궁금해하는 분들이 있을 수도 있으니 아래 링크와 코드의 흔적을 남겨놓겠다,,, 간단하면서 나름 재밌는 프로그램이니 따라해봐도 좋을 것??? 같다

 

 

Main

public class SnakeGame {
    public static void main(String[] args) {
        GameFrame frame = new GameFrame();
    }
}

Frame

import javax.swing.*;

public class GameFrame extends JFrame {
    GameFrame(){

        this.add(new GamePanel());
        this.setTitle("Snake");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);
        this.pack();
        this.setVisible(true);
        this.setLocationRelativeTo(null);
    }
}

Panel

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;

public class GamePanel extends JPanel implements ActionListener {

    static final int SCREEN_WIDTH = 600;
    static final int SCREEN_HEIGHT = 600;
    static final int UNIT_SIZE = 25;
    static final int GAME_UNITS = (SCREEN_WIDTH*SCREEN_HEIGHT)/UNIT_SIZE;
    static final int DELAY = 75;
    final int x[] = new int[GAME_UNITS];
    final int y[] = new int[GAME_UNITS];
    int bodyParts = 6;
    int applesEaten;
    int appleX, appleY;
    char direction = 'R';
    boolean running = false;
    Timer timer;
    Random random;

    GamePanel(){
        random = new Random();
        this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
        this.setBackground(Color.black);
        this.setFocusable(true);
        this.addKeyListener(new MyKeyAdapter());
        startGame();
    }

    public void startGame() {
        newApple();
        running = true;
        timer = new Timer(DELAY, this);
        timer.start();
    }

    public void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        draw(graphics);
    }

    public void draw(Graphics graphics){

        if(running) {
            /*
            for (int i = 0; i < SCREEN_HEIGHT / UNIT_SIZE; i++) {
                graphics.setColor(Color.GRAY);
                graphics.drawLine(i * UNIT_SIZE, 0, i * UNIT_SIZE, SCREEN_HEIGHT);
                graphics.drawLine(0, i * UNIT_SIZE, SCREEN_WIDTH, i * UNIT_SIZE);
            }*/
            graphics.setColor(Color.red);
            graphics.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);

            for (int i = 0; i < bodyParts; i++) {
                if (i == 0) {
                    graphics.setColor(Color.green);
                    graphics.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
                } else {
                    //graphics.setColor(new Color(45, 180, 0));
                    graphics.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
                    graphics.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
                }
            }

            // Score Board
            graphics.setColor(Color.red);
            graphics.setFont(new Font("Ink Free", Font.BOLD, 40));
            FontMetrics metrics = getFontMetrics(graphics.getFont());
            graphics.drawString("Score : " + applesEaten,
                    (SCREEN_WIDTH - metrics.stringWidth("Score : " + applesEaten))/2,
                    graphics.getFont().getSize());
        } else {
            gameOver(graphics);
        }
    }

    public void move() {
        for(int i = bodyParts; i>0; i--){
            x[i] = x[i-1];
            y[i] = y[i-1];
        }

        switch (direction){
            case 'U':
                y[0] = y[0] - UNIT_SIZE;
                break;
            case 'D':
                y[0] = y[0] + UNIT_SIZE;
                break;
            case 'R':
                x[0] = x[0] + UNIT_SIZE;
                break;
            case 'L':
                x[0] = x[0] - UNIT_SIZE;
                break;
        }
    }

    public void newApple() {
        appleX = random.nextInt(SCREEN_WIDTH/UNIT_SIZE)*UNIT_SIZE;
        appleY = random.nextInt(SCREEN_HEIGHT/UNIT_SIZE)*UNIT_SIZE;
    }

    public void checkApple() {
        if((x[0] == appleX) && (y[0] == appleY)) {
            bodyParts++;
            applesEaten++;
            newApple();
        }
    }

    public void checkCollisions() {
        // check if head collids with body
        for(int i = bodyParts; i > 0; i--){
            if((x[0] == x[i]) && (y[0] == y[i])) {
                running = false;
                break;
            }
        }

        // check if head touches left, right border
        if(x[0] < 0 || x[0] > SCREEN_WIDTH) {
            running = false;
        }
        // check if head touches top, bottom border
        if(y[0] < 0 || y[0] > SCREEN_HEIGHT){
            running = false;
        }

        if(!running){
            timer.stop();
        }
    }

    public void gameOver(Graphics graphics){
        // Score
        graphics.setColor(Color.red);
        graphics.setFont(new Font("Ink Free", Font.BOLD, 40));
        FontMetrics metrics = getFontMetrics(graphics.getFont());
        graphics.drawString("Score : " + applesEaten,
                (SCREEN_WIDTH - metrics.stringWidth("Score : " + applesEaten))/2,
                graphics.getFont().getSize());
        // Game Over Text
        //graphics.setColor(Color.red);
        graphics.setFont(new Font("Ink Free", Font.BOLD, 75));
        FontMetrics metrics2 = getFontMetrics(graphics.getFont());
        graphics.drawString("Game Over",
                (SCREEN_WIDTH - metrics2.stringWidth("Game Over"))/2,
                SCREEN_HEIGHT/2);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if(running){
            move();
            checkApple();
            checkCollisions();
        }
        repaint();
    }

    public class MyKeyAdapter extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e){
            switch (e.getKeyCode()) {
                case KeyEvent.VK_LEFT:
                    if(direction != 'R'){
                        direction = 'L';
                    }
                    break;
                case KeyEvent.VK_RIGHT:
                    if(direction != 'L') {
                        direction = 'R';
                    }
                    break;
                case KeyEvent.VK_UP:
                    if(direction != 'D') {
                        direction = 'U';
                    }
                    break;
                case KeyEvent.VK_DOWN:
                    if(direction != 'U') {
                        direction = 'D';
                    }
                    break;
            }
        }
    }
}

참고로 Apple은 Snake가 먹는 먹이이며, 먹으면 뱀의 길이가 1 증가한다. 

 

 

 

 

  • 참고

 

'JAVA' 카테고리의 다른 글

[Java 8] Optional  (0) 2022.10.07
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23
ListIterator  (0) 2022.09.23

사용하는 이유

로그를 사용하고 싶을 때, System.out.println()을 사용해도 원하는 기능을 얻을 수 있을 것이다. 그럼에도 Slf4j를 사용하여 얻을 수 있는 이점이 있다.

 

1. 더 많은 내용을 얻을 수 있다.

System.out.println()
Slf4j

System.out.println()과 Slf4j를 사용했을 때의 로그 차이이다. 차이를 보면 다음과 같다.

1. 찍히지 않는 실행한 스레드

2. 클래스 이름

3. message가 출력이 된다.

 

 

2. 로그의 레벨을 정할 수 있다.

log는 trace, debug, info, warn, error를 제공합니다.

 

예를들어, 아래와 같은 기준을 두며 로그의 레벨을 정할 수 있습니다.

debug는 개발서버에서 보는 것이다.
info는 비즈니스로직이기 때문에 운영시스템에서 봐야한다.

 

또한, application.properties 설정에서 패키지별로 로그의 레벨을 따로 정할 수 있습니다.

# hello.springmvc 패키지와 그 하위 로그 설정
logging.level.hello.springmvc=trace

위와같이 코드를 작성하면, hello.springmvc 하위 패키지에 있는 log는 trace로그부터 찍히도록 할 수 있습니다.

 

참고로 기본은 info이고, trace -> debug -> info -> warn -> error 순으로 찍을 수 있습니다.

 

예를들어, trace로 설정하면 trace, debug, info, warn, error의 모든 로그가 찍히고,

debug로 설정하면 debug, info, warn, error의 모든 로그가 찍힙니다.

 

그렇기때문에 spring boot 로그는 기본을 info로 두고있습니다. 실제로 trace로 변경해보면 기존에 보이지않던 수 많은 로그가 찍히는 것을 볼 수 있습니다.

 

 

3. 콘솔에만 출력하는 것이 아닌, 파일이나 네트워크 등 별도의 위치에 남길 수 있다.

 

4. 성능이 우세하다.

 

 

 

 

사용하는 방법

어노테이션 X

package hello.springmvc.basic;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogTestController {

    @GetMapping("/log-test")
    public String logTest() {
        String name = "spring";

        log.trace("trace log={}", name);
        log.debug("debug log={}", name);
        System.out.println("info log =" + name);
        log.info("info log= {}", name);
        log.warn("warn log={}", name);
        log.error("error log= {}", name);
        return "ok";
    }
}

 

어노테이션 O

package hello.springmvc.basic;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LogTestController {

    @GetMapping("/log-test")
    public String logTest() {
        String name = "spring";
        
        log.trace("trace log={}", name);
        log.debug("debug log={}", name);
        System.out.println("info log =" + name);
        log.info("info log= {}", name);
        log.warn("warn log={}", name);
        log.error("error log= {}", name);
        return "ok";
    }
}

 

어노테이션을 사용한 코드와 사용하지 않은 코드의 차이점을 보면

private final Logger log = LoggerFactory.getLogger(getClass());

가 있습니다. 실제로 @Slf4j의 기능은 위의 한줄의 코드를 줄여준다고 보면 됩니다.

 

 

"trace log ="+name으로 로그를 찍지 않는 이유       

 

만약 로그 레벨을 info로 설정한 경우 trace와 debug의 로그는 출력이 되지 않을 것 입니다.

 

하지만 로그를 "info log ={}", name의 방식이 아닌 "trace log ="+name으로 사용하는 경우, java는 "trace log ="+name를 읽고 cpu와 memory를 할당하지만, 실제로는 로그가 찍히지 않기 때문에 불필요한 cpu와 메모리를 낭비하게 됩니다.

 

반면, "info log = {}", name 방식을 사용하는 경우는 cpu와 메모리를 미리 할당하지 않는다고 합니다. 그렇기때문에 "info log ={}", name과 같은 방식을 사용합시다!

 

 

 

 

나중 공부를 위한 링크

1. slf4j

 

SLF4J

Simple Logging Facade for Java (SLF4J) The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framewor

www.slf4j.org

2. logback

 

Logback Home

Logback Project Logback is intended as a successor to the popular log4j project, picking up where log4j 1.x leaves off. Logback's architecture is quite generic so as to apply under different circumstances. At present time, logback is divided into three mod

logback.qos.ch

3. 스프링부트가 제공하는 로그 기능

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io

 

+ Recent posts