쉽게 배우는 자바 8~12

kdew0308·2022년 10월 26일
0

자바

목록 보기
5/8

8) I/O와 Stream

입출력(Input/Output)

  • 컴퓨터 내부 또는 외부 장치와 프로그램 사이에서 데이터를 주고 받는 것

Stream

  • Java에서 I/O을 위해 두 대상을 연결하여 데이터를 송/수신하는데 사용되는 연결통로
  • 중간에 끊김이 없이 연속적으로 데이터를 송/수신

8-1) Stream의 종류

  • I/O Stream은 한 종류의 일만 할 수 있음(읽거나 쓰거나)
    • InputStream : 입력이 가능한 Stream(read()로 입력)
    • OutputStream : 출력이 가능한 Stream(write()로 출력)
  • 데이터 종류에 따라서도 바이트 기반 Stream과 문자 기반 Stream이 존재
    • 각 Stream에는 보조 Stream을 이용해 기반 Stream의 겅능을 높임

📌Byte 기반 Stream


📌Byte 기반 보조 Stream

📌문자 기반 Stream


📌문자 기반 보조 Stream

📌표준 입출력

  • 키보드와 콘솔(화면)을 통해 입력과 출력이 되는 걸 뜻함

    • System.in : 콘솔로부터 입력을 받을 때 사용
    • System.out : 콘솔로 메세지 출력할 때 사용
    • System.err : System.out과 유사하지만 Error 메세지를 출력할 때 사용
  • 선언 부분은 PrintStream이지만 실제로는 버퍼를 이용하는 BufferedInput/OutputStream의 인스턴스를 사용

📌파일 입출력

File 클래스

  • File의 생성이나 삭제, 혹은 Directory를 다루는데 사용되는 클래스
  • 파일의 내용을 읽기/쓰기 기능을 원한다면 file Stream을 이용해야 함.

RandomAccessFile 클래스

  • 파일에 대한 I/O를 하나의 클래스로 사용할 수 있도록 구현할 클래스
  • 파일에 특정 위치에 쓰거나 읽는 것이 가능

📌직렬화(Serialization)

  • 직렬화 : 객체에 저장된 데이터를 스트림에 쓰기 위해 연속적인 데이터로 변환하는 것 <-> 역직렬화
    • ObjectInputStream/ObjectOutputStream을 이용해 객체 직렬화/역직렬화
    • Serializable 인터페이스를 구현해야함.
    • transient 키워드로 특정 필드를 직렬화에 배제할 수 있음.

8-2) 오류와 예외

  • 프로그램의 에러는 크게 3가지
    • 컴파일 에러
    • 런타임 에러
    • 논리적 에러
  • Java에서의 런타임 에러
    • 오류(Error) : 로직 상에서 수습될 수 없는 심각한 오류
    • 예외(Exception) : 로직 상에서 수습될 수 있는 미약한 오류

📌Exception 분류

  • Exception 클래스들은 사용자로부터 발생되는 예외(Checked Exception)
    • Complie 시점에 체크
    • DataFormatException, FileNotFoundException 등
  • RuntimeException 클래스들은 개발자의 실수로 발생되는 예외(Unchecked Exception)
    • Complie 시점에 체크할 수 없음
    • NullPointerException, ArithmeticException 등

📌Exception 클래스 계층

8-3) 예외 처리

📌try-catch-finally문

📌try-catch-finally문

📌try-with-resources문

📌예외 선언

  • 메소드에 예외를 선언하는 법
    • 예외를 처리하는 방법으로는try-catch-finally문 뿐 아니라 throws로 메소드를 호출한 메소드에게 처리를 위임하는 방법
    • 여러 예외인 경우 ,(쉼표)로 구분하며 Exception만 붙여주면 모든 예외가 발생할 가능성이 있다는 뜻
    • 위임하는 처리 방법으로 통해 호출 관계를 알 수 있음

📌예외 발생과 catch 블록

📌사용자 정의 예외

기존 Exception 혹 RuntimeException 클래스를 상속받아 구현

📌연결된 예외의 처리

  • 여러 예외 중 원인 예외를 지정해서 처리하는 방식
    • A예외가 B예외를 일으켰다면 A예외를 원ㅇ니 예외로 지정
    • initCause 메소드로 지정
  • 연결된 예외 처리를 하는 이유
    • 여러 작은 Exception을 하나의 큰 exceptiond으로 묶어서 처리
    • Checked exception을 unchecked exception으로 변경해서 처리

과제1

  • 콘솔로 10개의 입력 받은 수들의 합과 평균을 내는 로직을 작성하고, 수가 입력되지 않을 때 예외 처리 하는 로직을 작성하세요.

    • Scanner로 숫자 입력은 nextInt()
    • InputMismatchException 사용

과제2

  • 파일을 생성하고, 파일에 텍스트 5줄을 입력하는 로직을 작성하세요.

    • try-with-resoueces 구문 사용
    • RandomAccessFile 클래스 사용

9) Generics와 Enum

9-1) Generics

📌제네릭이란?

  • 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법
    • 즉, 개발 시점에서는 일반화한 타입을 지정
    • 이로서 형변환의 번거로움을 줄여주고, 컴파일 타입에 체크함으로써 안정성을 높임

📌제네릭 선언과 사용

📌제네릭 메서드

  • 매개 변수와 반환 값에 타입 파라미터를 갖는 메소드
    • 제네릭 클래스의 <T>와 제네릭 메서드의 <T>는 별개
    • static 변수에 제네릭 타입은 안되지만 메서드에는 가능
    • 제네릭 메서드는 호출할 때 마다 타입 매개변수에 타입을 대임(대부분 추론 가능)

📌제네릭 타입과 다형성

  • 참조 변수와 생성자로 대입된 타입은 일치해야 함
  • 부모 타입으로 지정된 자료 구조나 참조 변수에는 자식 클래스가 들어갈 수 있음
  • JDK 1.7부터는 타입 추론이 가능(생성자 타입을 추론 가능)
  • 대입된 타입이 다른 제네릭 타입 간의 형 변환은 불가능(와일드 카드인 경우 가능)

📌제한된 제네릭

  • 제네릭 타입에 'extends' 키워드를 사용하면 같은 부모 클래스와 부모 클래스를 상속받는 자식 클래스들만 가능
    • <T extends Animal> 와 같은 식으로 하면 Animal 클래스와 Animal이라는 부모 클래스를 상속받는 자식 클래스만 T에 들어갈 수 있음
    • 특정 인터페이스를 구현한 클래스로 제약이 필요하면 implements가 아닌 extends를 사용

📌와일드 카드

  • 메소드의 매개변수로 제네릭 클래스의 객체를 받을 때, 타입을 제한하기 위해 사용
    • <? extends T> 상한 제한(upper bound) : T와 그 자손들을 구현한 객체들만 매개변수로 가능
    • <? super T> 하한 제한(lower bound) : T와 그 조상들을 구현한 객체들만 매개변수로 가능
    • <?> : 제한 없음
    • 와일드 카드에는 "&"을 사용할 수 없음

9-2) Enum

📌열거형(Enum type)이란?

  • 관련된 상수들을 모아서 나열해 둔 타입을 뜻함
    • 자바의 열거형은 값과 타입을 모두 체크 -> 하나라도 다르면 컴파일 에러가 발생
    • 타입에 안전(Type safe)한 열거형을 제공
    • 열거형에는 기본적으로 0부터 시작되는 정수 값이 ordinal로 부여됨
    • 정수 값 외에 필드도 추가 할 수 있음

📌열거형 비교

  • 열거형 비교에서는 '=='과 compareTo 메서드 사용 가능
    • compareTo 메서드는 oridinal의 차이를 반환

📌java.lang.Enum

  • 모든 Enum의 조상격인 추상 클래스

과제

제네릭의 와일드 카드를 사용하여 sum이라는 함수를 구현하세요.

  • Sum 메소드에서 숫자 관련된 데이터 타입을 다 받을 수 있어야 함
  • 반환 타입을 double

10) Lambda와 Stream

10-1) 람다 표현식(Lambda Expression)

메서드를 하나의 식으로 표현한 것

  • 형태는 매개 변수를 가진 코드 블록이지만 런타임 시에는 익명 클래스를 생성
  • 간략하면서도 명확한 식으로 표현
  • 객체지향보다 함수 지향 언어와 가까움
  • (타입 매개변수) -> { 실행문; ... } 형태로 작성

📌람다 표현식의 특징

장점

  • 코드의 간결성
  • 지연연산 수행
  • 병렬처리 가능

단점

  • 람다식의 호출이 까다로움
  • 람다 stream으로 단순 반복문 사용 시 성능 저하
  • 불필요한 사용 시, 가독성 저하

📌함수형 인터페이스

구현해야 할 추상 메소드가 하나만 정의된 인터페이스

  • 인터페이스 위에 @FunctionalInterface룰 붙여 표시
  • 컴파일러에서 추상 메서드를 갖춘 인터페이스인지 검사

📌java.util.function 패키지

자주 사용하는 형식에 대한 interface를 제공(>=JDK 1.8)

10-2) Stream API

📌스트림이란?

여러 종류의 데이터를 다양한 방식으로 다룰 수 있도록 제공하는 표준화된 방법 (>=JDK 1.8)

  • 배열이나 컬렉션 뿐만 아니라 파일 데이터도 가능
  • 반복문이나 반복자(iterator)를 사용하여 개발하지 않아도 되도록 지원

📌스트림 특징

  • 원본 데이터를 변경 불가능
  • 일회용
  • 내부 반복으로 작업 처리
  • 기본 데이터 형을 처리할 수 있는 래퍼 스트림 지원
    (Int/Double/LongStream)

📌스트림 연산 과정

스트림 생성 -> 중개 연산(스트림의 변환) -> 최종 연산(스트림의 사용)

📌스트림 생성

스트림을 사용하기 위해서는 아래의 여러 원본 데이터를 이용해 스트림 형태로 만들어야 함

  • 컬렉션, 배열
  • 가변 매개변수
  • 지정된 범위의 연속된 정수
  • 난수들
  • 람다 표현식
  • 파일
  • 빈 스트림

📌스트림 중개 연산

앞에서 생성된 초기 스트림을 전달받아 필터링이나 변환 등의 연산을 수행

  • Filtering
  • Mapping
  • Sorting
  • Splitting
  • Iterating

Filtering

  • filtering() : 주어진 조건에 참인 요소를 필터링
  • distinct() : 스트림의 중복 요소를 제거

Mapping

  • map() : map에 명령문을 인수로 전달하여, 반환값들로 구성된 새로운 스트림을 반환
  • flatMap() : 스트림 요소가 배열인 경우, 주어진 명령문을 적용한 새로운 스트림으로 반환

Sorting

  • sorted() : Comparator를 구현하여 넘겨주거나 natural order로 정렬

Splitting

  • limit() : 스트림에서 limit에 들어가는 수만큼의 새로운 스트림을 반환
  • skip() : 스트림의 첫 요소부터 skip에 들어가는 수를 제외한 새로운 스트림을 반환

Iterating

  • peek() : 각 요소를 돌면서 peek에 전달된 명령문을 수행

📌스트림 최종 연산

앞에서 수행된 중개 스트림을 전달받아 최종적인 연산을 진행하여 인전의 스트림을 사용 할 수 없게 됨

  • Calculating
  • Collecting
  • Reducing
  • Matching
  • Iterating

Calculating

  • count(), min(), max() : 요소의 개수와 최댓값, 최솟값을 반환
  • sum(), average() : 요소들의 합과 평균 값을 반환

Collecting

  • collect() : 매개변수로 Collectors의 구현 메소드를 받아 메소드의 동작대로 요소를 수집하여 반환

Reducing

  • reduce() : 스트림의 첫 두 요소를 가지고 연산 후, 이후 요소들로 연산한 값을 반환

Matching

  • anyMatch() : 스트림이 일부 요소가 특정 조건을 만족하면 true를 반환
  • allMatch() : 스트림이 모든 요소가 특정 조건을 만족하면 true를 반환
  • noneMatch() : 스트림이 모든 요소가 특정 조건을 만족하지 않으면 true를 반환
  • findFirst() : 스트림의 첫 요소를 반환
  • findAny() : 스트림의 첫 요소를 반환하는 것 같지만 parallelStream의 경우에 쓰임

Iterating

  • foreach() : 각 요소를 돌면서 foreach에 전달된 명령문을 수행

📌Optional 클래스

Null값으로 인한 예외를 회피할 수 있는 래퍼 클래스

  • 모든 타입의 참조 변수를 저장가능
  • 메소드를 이용하여 NullPointerException을 회피 가능
  • Stream과 비슷하게 기본 데이터 형을 처리할 수 있는 래퍼 클래스 지원

과제1

특정 수를 입력 받아 1부터 해당 수까지 factorial 연산과 합을 구하는 람다 표현식을 작성하세요.

  • Interface 정의
  • 하나의 메소드로 두가지 기능을 구현

과제2

특정 수를 입력 받고 해당 숫자 안의 모든 소수를 list로 만드는 스트림을 작성하세요.

11) Deep Dive into JVM

11-1) JVM 리마인드

📌Java 동작 원리

📌JDK vs JRE vs JVM

11-2) JVM 구조

📌Class Loader

class 파일을 JVM에 올려주고, 검증, 초기화해주는 역할

  • 클래스 파일을 JVM올려주는 과정을 Loading
  • 클래스 파일을 사용하기 위해 검증하고 기본값으로 초기화하는 과정을 Linking
  • 클래스 파일을 이용해 static 변수 등을 초기화하는 과정을 Initialization

📌Loading 동작과정

Bootstrap Class Loader

  • 가장 필수가 되는 Library class들을 load(rt.jar 등)

Extension Class Loader

  • Bootstrap class 다음으로 중요한 class를 load($JAVAHOME/jre/lib/ext/*.jar)

Application Class Loader

  • 개발자가 작성한 클래스 파일을 load(classpath에 위치)

📌Linking

Verification

  • 클래스 파일이 올바른지에 대한 검증

Preparation

  • 클래스와 인터페이스 등에 필요한 static field 메모리 할당 및 기본값 초기화

Resolution

  • Symbolic Reference 값을 Method Area의 Direct Reference값으로 변환

📌Initialization

앞의 과정이 끝나면 Class 파일의 코드를 읽음
클래스와 인터페이스의 값들을 지정한 값으로 초기화
멀티 쓰레드로 동작하기 때문에 동시성 고려

📌Execution Engine

Interpreter

  • 바이트 코드를 한 줄씩 해석, 실행하는 방식
  • 초기 방식으로 속도가 느림
    JIT(Just-In-Time) 컴파일러
  • 실제 실행(바이트코드를 실행하는 시점)하는 시점에 각 OS에 맞는 Native Code로 변환하여 실행 속도 향상
  • 모든 코드를 JIT 컴파일러 방식으로 실행하지 않고, 인터프리터 방식을 사용하다 일정 사용기준을 넘어가면 JIT 컴파일러 방식으로 실행
  • 실행할 때 컴파일한 코드를 캐싱

📌Runtime Data Area


Method Area

  • static 변수, 그리고 메소드에 사용되는 데이터와 같은 Class 메타데이터가 저장
  • JVM에 하나만 존재, JVM 구동 시에 해당 영역이 생성되고 JVM이 종료 시에 해제

Heap

  • Java로 구성된 인스턴스, 배열 등이 저장됨
  • 모든 Java Stack 영역에서 참조되어, Thread 간 공유
  • 해당 영역이 모두 차게 되면 OutOfMemoryException발생

Java Stack

  • Java의 메소드가 호출될 때 사용되는 메모리 공간
  • 변수, 오퍼레이션, reference 정보 등이 저장되어 있음

PC Register

  • Thread 별로 동시에 동작할 수 있도록 메모리 주소를 저장하는 공간

Native Method Stack

  • 시스템 자원을 사용하거나 Java로 구성된 코드만 사용될 수 없는 경우, 다른 프로그래밍 언어로 작성된 메소드를 호출 시 사용되는 영역

11-2) Garbage Collection

📌Garbage Collection(GC)란?

Java의 Heap 영역에서 참조되고 있지 않는 객체들의 메모리를 할당 해제하는 과정

  • 명시적으로 불필요한 데이터는 null로 선언
  • System.gc() 메소드로 GC를 할 수 있으나 프로그램의 성능에 큰 영향을 미침
  • GC과정에서 중요한 개념은 Reachability, Stop-the-world
  • 영역에 따라 Minor GC, Major GC로 나뉨

📌Reachability

GC에서 객체가Garbage인지 판별하는 개념

  • 어떤 객체로부터 유효한 참조가 있으면 ʻreachable’ <-> ʻunreachable’
  • 유요한 참조의 최초 셋을 Root set이라고 함
  • Heap 영역 내부의 객체들은 Method Area , java Stack , Native Stack 에서 참조되면 reachable로 판정

📌Stop-the-world

GC수행하기 위해 JVM이 애플리케이션 실행을 멈추는 것

  • Stop-the-world가 발생하면 GC 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 대기
  • 어떤 GC 알고르짐을 사용하더라도 stop-the-world는 발생
  • GC 튜닝이란 stop-the-world 시간을 줄이는 것

📌GC 기본 알고리즘

Mark & Swap Algorithm

  • Root Set으로부터 참조된 객체를 mark(Mark phase)
  • Mark가 없는 객체는 메모리에서 해제(Sweep phase)
  • Fragmentation 발생
  • Mark & Swap Algorithm에서 문제인 Fragmentation을 해결
  • Sweep단계에서 메모리를 해제하고, 메모리를 정리(Compact Phase)

📌발생 시점에 따른 GC

Minor GC

  • Young Generation 영역에서 일어나는 GC
  • Eden 영역이 꽉 차면 Minor GC가 발생
  • 사용되지 않는 메모리는 해제되고 Eden 영역에 존재하는 객체는 Survivor 영역으로 이동
  • 두 개의 Survivor 영역 중 한 영역은 반드시 비어야 함
  • Old Generation에서 일어나는 GC
  • 일반적으로 Minor GC 보다 오래 걸림
  • Young Gen으로부터 promotion된 객체로 인해 Old gen의 메모리가 부족하면 발생
  • Major GC가 오래 걸리면 어플리케이션에 치명적인 영향

📌여러가지 GC 방식

Serial GC

Parallel GC

CMS GC

G1 GC

12) Java의 유용한 패키지

12-1) lang 관련 패키지

📌java.lang 패키지

import없이 사용가능한 기본이 되는 가장 중요한 패키지

  • 모든 클래스의 최고 조상 클래스인 Object클래스
  • String관련 클래스
  • datatype 관련 클래스
  • System관련 클래스
  • Thread관련 클래스 등

📌Object 클래스

  • 모든 자바 클래스의 최고 조상 클래스
  • 아무것도 상속받지 않으면 자동으로 Object를 상속
  • Object 클래스는 필드가 없으며, 총 11개의 메소드만 존재

📌String 클래스

문자열을 다룰 때 많이 사용하는 클래스(참조형 데이터 타입)

  • 불변 객체
  • 생성자(new)로 변수를 선언하면 heap영역
  • 바로 문자열을 할당할 경우 stack영역

+연산자를 이용해 두 문자열을 합칠 수 있으나 많은 연산 시, 성능 저하

  • Stringbuffer / Stringbuilder 클래스 이용

📌StringBuffer퐻 StringBuilder

String 클래스 대신 연산이 많은 경우 사용되는 클래스

  • 가변 객체
  • 빈번한 연산 시, 성능 좋음(문자열을 합치거나 중간에 특정 문자를 제거하는 경우 등)

StringBuffer과 StringBuilder의 차이점은 동기화의 유무

  • 동기화를 하지 않는 StringBuilder가 StringBuffer보다 성능이 좋음
  • 멀티 쓰레드 환경에서는 StringBuffer로 동기화

📌String vs StringBuffer

📌Math 클래스

  • 수학적인 통계와 계산 이외에도 삼각 함수와 같은 다양한 기능을 제공하는 클래스
  • Math 클래스는 생성자가 private으로 되어 있기 때문에 객체를 생성할 수 없음
  • Math 클래스는 final 클래스 이기 때문에 상속이 불가능

📌Wrapper 클래스

8개의 기본 타입에 해당하는 데이터를 객체로 포장해 주는 클래스

📌boxing과 unboxing

12-2) util 관련 패키지

📌java.util 패키지

개발에 대부분 사용되는 자료구조나 날짜나 시간 등 유용하게 사용되는 클래스들이 모여 있는 패키지

  • Collection 클래스
  • Date, Calendar 클래스
  • Random , StringTokenizer 클래스 등

📌Collection 관련 클래스

다수의 데이터를 쉽고 효과적으로 처리할 수 있는 방법을 제공하는 클래스(>= JDK 1.2)

  • 이 집합을 컬렉션 프레임워크라고 함
  • Interface를 사용해 구현
  • 대표적으로 많이 사용되는 자료구조의 interface는 List, Set, Map
  • JDK 1.5부터 Queue interface도 들어옴

Collection들을 다루기 위한 Collections 클래스도 존재

📌List 컬렉션

List 컬렉션은 객체를 일렬로 늘어놓은 구조

  • 객체 자체를 저장하는 것이 아닌 객체의 번지를 참조
  • 순서가 있는 데이터의 집합으로 데이터의 중복을 허용

대표적인 클래스

  • ArrayList : 배열 기반의 자료구조로 동기화 처리가 없음
  • Vector: ArrayList와 비슷, 동기화 처리로 성능때문에 잘 사용하지 않음
  • Stack: Vector클래스를 상속받아 스택 메모리 구조의 클래스를 제공. 후입선출(LIFO) 형태
  • LinkedList : ArrayList의 단점인 추가, 삭제 기능을 노드를 이용하여 향상시킴

📌Set 컬렉션

Set 컬렉션은 집합과 같은 구조

  • 객체 자체를 저장하는 것이 아닌 객체의 번지를 참조
  • 순서가 없는 데이터의 집합으로 데이터의 중복을 허용하지 않음

대표적인 클래스

  • HashSet : Set을 구현한 대표적인 클래스, hashcode로 객체를 비교
  • TreeSet : 이진 탐색 트리(binary search tree)로 구현된 클래스, 범위 탐색과 정렬에 유리, 요소 추가 및 삭제 시 성능 감소
  • LinkedHashSet : HashSet에 순서 보장 기능 향상

📌Map 컬렉션

Map 컬렉션은 key, value의 pair의 집합 구조

  • 순서가 없음
  • Key는 중복 허용x, value는 중복 허용

대표적인 클래스

  • HashMap : ArrayList와 LinkedList의 장점을 합쳐서 만든 클래스이며, 해싱기법을 사용하여 검색이 빠름
  • TreeMap : TreeSet과 비슷한 구조로 Key를 정렬하고 Entry를 저장하여 사용. 범위 검색 시 성능 좋음
  • HashTable : HashMap과 비슷하지만 동기화 처리로 성능때문에 잘 사용하지 않음
  • LinkedHashMap : HashMap에 순서 보장 기능 향상

📌Collection 다이어그램

📌Random 클래스

난수를 발생시키는 클래스

  • 여러 기본 데이터 형에 맞춰 난수 생성
  • 일정 범위 내의 난수 생성 가능

📌StringTokenizer 클래스

문자열을 특정한 구분 문자(Delimiter)로 여러 개의 토큰(Token) 문자열로 분리
• 특수 문자뿐만 아니라 공백(space), 한 문자부터 문자열도 가능

12-3) 시간 날짜 관련 패키지

Date 클래스

  • JDK 1.1부터 지원
  • 현재는 대부분 Deprecated

Calendar 클래스

  • JDK 1.1부터 지원
  • 3가지 정도의 문제점을 내재하고 있음

java.time 패키지

  • JDK 1.8부터 지원
  • 날짜, 시간 외에 타임존이나 표현 형식 관련 지원

📌Date 클래스

주로 날짜를 다룰 때 쓰는 클래스(JDK1.0)

  • 대부분 메소드가 Deprecated

특정 시점, 현재 시점을 기준으로 Date객체를 만들어 사용
SimpleDateFormat 클래스와 같이 많이 사용됨.

📌Calendar 클래스

주로 날짜와 시간을 다룰 때 쓰는 추상 클래스(JDK1.1)

  • Date 클래스 대신 사용됨.
  • GregorianCalendar 클래스
  • Calendar 클래스의 모든 필드는 클래스 변수(static variable)
    몇가지의 문제점을 내재하고 있음.
  1. 윤년, 윤달 등 특이한 케이스를 고려하지 않음.
  2. 불변 객체가 아님 -> 멀티 쓰레드에서 하나의 객체를 볼 경우, 문제 발생 가능성
  3. 월 계산의 헷갈림(1월~12월 -> 0 ~ 11)

📌java.time 패키지

날짜, 시간 뿐 아니라 시간대까지 표현가능한 패키지(JDK 1.8)
Calendar 클래스의 문제점을 해결

  • 불변 객체
  • 멀티 쓰레드 환경에서 안전
  • LocalDate, LocalTime, LocalDateTime, ZonedDateTime 클래스 4가지

0개의 댓글