취준생일 때 우연치 않게 구글링하다가 '기억보단 기억을' 블로그 운영자이신 이동욱 님의 3번째 직장에 오기까지 시리즈를 읽게 되었다.
정말 그 당시 너무 감명 깊게 읽었다. 이동욱 님의 간절했고, 피나는 노력이 글 한 문장 한 문장에서 느껴졌다. 글이 살아있다는 느낌을 받았다.
지금까지 블로그 글을 읽고 나서 그런 감정을 느껴본 적은 없었다. 지금까지도... 내가 가고자 하는 길인데 의지박약인 내가 이 정도의 피나는 노력을 할 수 있을까.. 무섭고 이동욱 님이 경의로웠다. 그다음부터 자연스럽게 나의 롤 모델이 되었고, 블로그를 자주 들여다보게 되었다. 그 당시 이동욱 님의 블로그 글을 이해하기에는 부족한 실력을 가진 나였지만 한 번씩 들여다보았다.
그러던 중 '스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 책을 쓰셨다는 글을 읽었다.
고민도 하지 않고 바로 주문했다. 책 구성부터가 당시 나에게 너무 필요한 부분이었다. 혼자서 개발 환경 구성부터 배포까지 경험해본 적이 없었기 때문이다. 책을 따라서 해볼 생각에 엄청 설렜던 기억이 난다. 하지만.. 부족한 실력에도 불구하고 당시 취업을 해서 많은 시간이 지난 지금 다시 책을 폈다...
그래도 그 당시에는 목차에 있는 제목들이 많이 낯설었는데 시간이 지난 지금은 많이 익숙한 제목들이 보인다. 조금이나마 성장을 하고 있다는 뜻일까 ㅎㅎ,,
시간이 지난 지금도 사실 개발 환경 구성과 배포의 경험을 제대로 해본 적이 없다. 책을 조금 읽었는데 정말 이해하기 쉽고 따라 하기 쉽게 잘 구성되어져있다. 이 책을 통해서 차근차근 성장하는 개발자가 되고싶다.
과일 객체의 APPLE과 회사의 APPLE은 비교조차 안되어야 다른 개념인데 같은 int 타입이기 때문에 비교가 되어서 같은 값이 된다. 심지어 1로 같기 때문에 '과일 사과와 회사 사과가 같다고?'가 출력된다.
위와 같은 문제는 서로 다른 객체를 만들어줌으로써 해결이 가능하다.
똑똑한 intellij가 잘못된 비교라고 표시해주고 있다.
하지만 위와 같은 상황에서도 문제는 발생한다.
switch case문에서는 사용자 정의 타입이 들어갈 수 없다.
위와 같은 문제들을 해결하기 위해서 JDK1.5 버전 이상부터는 enum이라는 개념이 추가되었다.
enum 정의하는 방법
정의하는 방법은 아래와 같다.
enum이라는 키워드를 사용하여 열거체를 정의하면 된다.
보통 열거체는 대문자를 사용한다.
enum을 정의해 보았다면 사용해보겠다.
사용하는 방법은 아래와 같다.
위에서 사용하지 못했던 switch case문도 정상적으로 사용가능하다.
enum에 정의된 상수들은 사용자 정의 타입이 아니라 enum type의 객체이기 때문이다.
enum의 특징
1. 객체를 만들 수 없다.
enum의 생성자는 private로 강제화되어있어 외부에서 객체를 생성할 수 없다.
상수라는 건 다른 값으로 할당되면 안되기 때문에 private로 강제화 된 것 같다.
2. enum 또한 class 이기 때문에 생성자와 메소드를 추가할 수 있다.
생성자를 이용해서 요일의 한글을 표시해주고 한글 요일을 반환해주는 메소드를 생성해보았다.
3. 생성자가 호출될 때 모든 상수 하나당 각각의 인스턴스로 만들어진다.
생성자 호출 시 값을 출력해보았다.
MONDAY 상수를 생성하였지만 모든 상수의 인스턴스가 생성되어서 출력되는 것을 볼 수 있다.
enum이 제공하는 메소드 (values()와 valueOf())
values() 메소드는 enum 안에 선언된 모든 상수들을 배열로 반환한다.
valueOf() 메소드는 enum 안에 존재하는 상수를 가져올 때 사용한다.
enum에 존재하지 않는 상수를 가져오려고 한다면 에러가 발생한다.
enum ordinal() 메소드
상수가 정의된 순서를 반환한다.
ordinal 메소드는 enum 내부에서 사용하기 위해 만든 메소드이지 프로그래머가 이 메소드 사용하는 것은 안티패턴이다.
스프링에서 enum을 사용하게 되면 @Enumerated 애노테이션을 사용하는 경우가 발생하게 되는데, 이때 @Enumerated(EnumType.STRING)으로 해야지 @Enumerated(EnumType.ORDINAL)을 사용하게 되면 나중에 시스템이 유지 보수를 하면서 enum에 상수값들이 중간에도 더 추가될 수도 있는데, 그럴 때마다 순서가 바뀌게 되어 매우 큰 문제가 발생할 수 있다.
java.lang.Enum
enum 클래스는 java.lang.Enum 클래스를 상속 받도록 되어 있다.
사용자가 따로 Enum 클래스를 상속을 정의해 주지 않았지만 byte 코드를 열어보면 아래와 같이 java/lang/Enum을 상속받고 있는 것을 볼 수 있다. 그러기 때문에 다중 상속을 지원하지 않는 JAVA에서는 enum 클래스는 별도의 상속을 받을 수 없다.
EnumMap
EnumMap 클래스는 Map 구현체 중 Enum type 을 키로 사용하는 클래스입니다.
EnumMap은 Map 인터페이스에서 키를 특정 enum 타입만을 사용하도록 하는 구현체 입니다. enum 은 ordinal 이라는 순차적인 정수값을 가지고 있습니다. EnumMap 순차적인 정수값 내부 데이터를 Array에 저장합니다. 그러면 우리가 많이 사용하는 HashMap 처럼 해시를 만들고 해시 충돌(Hash Collision)에 대응하는 작업자체가 필요 없게 됩니다.
HashMap 과 비교해봤을 때, EnumMap은 성능상의 이점을 노릴 수 있습니다.
EnumSet
EnumSet은 열거형을 위한 set 인터페이스 구현체이다.
enum class를 set으로 사용하게 된다면 아래와 같이 HashSet을 사용하여서 만들어서 사용하게 되면 나중에 상수 값이 추가가 될 때마다 HashSet에도 추가를 해줘야 한다. 하지만 EnumSet을 사용하게 되면 그럴 필요가 없다.
추가적으로 ordinal 값의 순서대로 요소가 저장된다.
EnumSet 추가적인 사용법
EnumSet과HashSet을 비교했을 때, 보통 전자가 훨씬 빠르다. 값을 예측 가능한 순서로 저장하여, 각각의 계산에단 하나의 비트만 검사하면 되기 때문이다.
HashSet과는 달리 정확한 버킷을 찾기 위해 해시 코드를 연산할 필요가 없으며,또한 비트 벡터(bitvector)의 특성으로 인해EnumSet은 매우 작고 메모리를 덜 사용하므로 더 효율적이다.
자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
자바가 제공하는 예외 계층 구조
RuntimeException과 RE가 아닌 것의 차이는?
커스텀한 예외 만드는 방법
예외란? (Exception과 Error의 차이)
먼저 오류(Error)와 예외(Exception)의 개념을 정리하자
오류(Error)는 시스템에 비정상적인 상황이 생겼을 때 발생한다. 이는 시스템 레벨에서 발생하기 때문에 심각한 수준의 오류이다. 따라서 개발자가 미리 예측하여 처리할 수 없기 때문에, 애플리케이션에서 오류에 대한 처리를 신경 쓰지 않아도 된다.
오류가 시스템 레벨에서 발생한다면, 예외(Exception)는 개발자가 구현한 로직에서 발생한다. 즉, 예외는 발생할 상황을 미리 예측하여 처리할 수 있다. 예외는 개발자가 처리할 수 있기 때문에 예외를 구분하고 그에 따른 처리 방법을 명확히 알고 적용하는 것이 중요하다.
예외클래스
위 그림은 예외클래스 구조이다. 모든 예외클래스는 Throwable 클래스를 상속받고 있다.
Throwable을 상속받는 클래스는 Error와 Exception이 있다. Error는 시스템 레벨의 심각한 수준의 에러이기 때문에 시스템에 변화를 주어 문제를 처리해야 하는 경우가 일반적이고, Exception은 개발자가 로직을 추가하여 처리할 수 있다.
Exception은 수많은 자식 클래스를 가지고 있다. 그 중 RuntimeException을 주목해야 한다.
RuntimeException은 CheckedException과 UncheckedException을 구분하는 기준이다.
Exception의 자식 클래스 중 RuntimeException을 제외한 모든 클래스는 CheckedException이며, RuntimeException과 그의 자식 클래스들을 UncheckedException이라 부른다.
Checked Exception
Unchecked Exception
처리여부
반드시 예외를 처리해야 함
명시적인 처리를 강제하지 않음
확인시점
컴파일 단계
실행단계
예외발생 시 트랜잭션 처리
roll-back 하지 않음
roll-back 함
대표 예외
Exception의 상속받는 하위 클래스 중 Runtime Exception을 제외한 모믄 예외 ex) IOException, SQLException
RuntimeException 하위 예외 ex) NullPointerException, IllealArgumentException, SystemException, IndexOutOfBoundException
Checked Exception과 Unchecked Exception의 가장 명확한 구분 기준은 '꼭 처리를 해야 하느냐'이다.
Checked Exception이 발생할 가능성이 있는 메소드라면 반드시 로직을 try/catch로 감싸거나 throw로 던져서 처리해야 한다. 반면 Unchcked Exception은 명시적인 예외처리를 하지 않아도 된다. 이 예외는 피할 수 있지만 개발자가 부주의해서 발생하는 경우가 대부분이고, 미리 예측하지 못했던 상황에서 발생하는 예외가 아니기 때문에 굳이 로직으로 처리를 할 필요가 없도록 만들어져 있다.
자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
자바는 프로그램 실행중에 발생할 수 있는 예외 처리문을 제공한다.
자바 예외 처리 방법
메소드 내에서 직접 처리하는 방법
예외가 발생한 메소드를 호출한 곳으로 예외 객체를 넘겨주는 방법
사용자 정의 예외를 생성하여 처리하는 방법
try, catch
기본적으로 try-catch 문에서 예외가 발생한 경우와 발생하지 않았을 때의 흐름이 다르다.
위와 같이 예외가 발생하지 않았을 경우 catch 문 블록은 실행되지 않는다.
위와 같이 일부러 1/0 연산을 이용해서 에러를 발생시킨 경우 에러 발생 시 catch 블록을 실행시키는 걸 확인할 수 있다.
catch블록은 예외가 발생하면 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다.
예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리할 수 있는 catch블럭이 있는지 찾게된다.
finally
finally 블럭은 try 블럭 코드의 완료 방식과 상관 없이 try 블럭이 일부만 실행되더라도 finally 블럭이 실행됩니다.
중간에 catch 블럭으로 빠지더라도 finally 블럭이 실행됩니다.
throw
throw는 특정 시점에 예외를 던져서 호출한 곳에 예외를 알리는 것입니다.
throws
이 메소드에서 예외 처리를 하지 않고 호출하는 메소드로 예외를 전파시키고 싶다면 사용할 수 있는 것이 바로 throws입니다. doCheckedException() 메소드에서 Exception() 메소드는 Checked Exception이기 때문에 try catch 문으로 예외 처리를 해주어야 하지만 throws를 통해서 상위 메소드에게 예외 처리를 전파시킨 경우입니다. 그러기 때문에 processException() 메소드에서 예외처리를 해줘야한다고 IDE에서 경고하고 있습니다.
위와 같이 상위 메소드로 예외를 전파시킨 걸 확인할 수 있습니다.
try-with-resources
리소스를 사용하고 닫아야 할 경우 finally 문에서 close()를 이용해서 자원을 해제합니다. 자원을 해제하지 않으면 메모리 누수가 발생할 수 있기 때문입니다. 하지만 위와 같이 사용할 경우 문제점이 몇가지 있었습니다.
자원 처리를 위해 InputStream 객체를 상단에 null로 선언이 필요하다.
개발자는 잊지말고 finally 에서 자원해제를 해줘야 한다.
is.close() 메소드는 Checked Exception을 던지고 있기 때문에 예외 처리가 필요하다.
위와 같은 문제를 해결하기 위해서 java 7 이상에서는 try-with-resource 문법을 통해서 해결하였습니다.
try 키워드 뒤에 괄호 안에 자원 해제가 필요한 객체를 넣어 줌으써 자동으로 자원을 해제할 수 있습니다.
보다 훨씬 코드 가독성도 좋아졌습니다.
커스텀한 예외 만드는 방법
기존의 정의된 예외 클래스 외에 필요에 따라 개발자가 새로운 예외 클래스를 정희하여 사용할 수 있습니다.
커스텀한 Checked Exception 예외를 만들고 싶다면 Exception을 상속 받아서 만들고, Unchecked Exception 예외를 만들고 싶다면 RuntimeException을 상속 받아서 생성합니다.
기존의 예외 클래스는 주로 Exception을 상속받아서 Checked Exception으로 작성하는 경우가 많았다고 한다. 요즘은 예외처리를 선택적으로 할수 있도록 RuntimeException을 상속받아서 작성하는 쪽으로 바뀌어가고 있다고 한다. Checked Exception은 반드시 예외처리를 해주어야 하기 때문에 불필요한 경우에도 try-catch문을 넣어 코드가 복잡해지기 때문이다.
JVM이란 JAVA Virtual Machine, 자바 가상 머신의 약자를 따서 줄여 부르는 용어이다.
JVM 역할은 자바 애플리케이션을 클래스 로더를 통해 읽어 들여 자바 API와 함께 실행하는 것이다.
그리고 JAVA와 OS사이에서 중개자 역할을 수행하여 JAVA가 OS에 구애받지 않고 재사용을 가능하게 해준다.
JVM 구성
Class Loader
- Runtime 시점에 class를 로딩하게 해주며 클래스의 인스턴스를 생성하면 클래스 로더를 통해 메모리에 로드한다.
- jar파일 내 저장된 클래스들을 JVM위에 탑재하고 사용하지 않는 클래스들은 메모리에서 삭제한다.
Runtime Data Areas
- JVM이 프로그램을 수행하기 위해 OS로 부터 별도로 할당받은 메로리 공간
- PC Register : CPU가 Instruction을 수행하는 동안 필요한 정보를 저장
- JVM Stack : Thread가 시작될 때 생성되며 Method와 Method 정보 저장
- Native Method Stack : Java 이외의 언어로 작성된 native 코드를 위한 Stack
- Method Area : 모든 쓰레드가 공유하는 메모리 영역(클래스, 인터페이스, 메소드, 필드 static 변수등의 바이트 코드 등을 보관)
- Heap : 런타임시 동적으로 할당하여 사용하는 영역 class를 통해 instance를 생성하면 Heap에 저장됨
Execution Engine
- Load된 Class의 ByteCode를 실행하는 Runtime Module
- Class Loader를 통해 JVM 내의 Runtime Data Areas에 배치된 바이트 코드는 Execution Engine에 의해 실행된다.
Garbage Collector
- 자바는 메모리 관리를 사용자가 아닌 JVM이 알아서 해준다.
- GC(Garbage Collector)는 더이상 참조되지 않는 메모리를 정리해준다.
컴파일 하는 방법, 실행하는 방법
컴파일 한다는 것은 .java 파일을 .class 파일로 만드는 것을 의미한다.
JDK (Java Development Kit) 자바 개발 도구를 설치하면 bin 폴더 안에 javac라는 java compiler가 포함되어 있다.
이 명령을 사용해서 .class 파일을 만든다.
cmd 창에서 해당 java 파일이 있는 곳으로 이동 후 javac 파일.java 명령을 실행하면 파일.class 파일이 생성된다.
다음으로 java 파일 명령을 실행시키면 class 파일이 실행된다.
.class 파일을 자바 바이트 코드라고 부른다. 자바 바이트 코드는 클래스 로더에 의해서 JVM 내로 로드 되고, 실행 엔진에 의해 기계어로 해석되어 메모리 상(Runtime Data Area)에 배치된다.
바이트코드란 무엇인가
자바 문법으로 작성한 .java 파일은 사람이 이해할 수 있는 언어로 작성했기 때문에 컴퓨터는 이해할 수 없다.
그렇기 때문에 번역을 통해 컴퓨터가 이해할 수 있는 형태로 만들어 줘야한다.
컴퓨터가 이해할 수 있는 형태로 번역하는 것은 JVM이 담당한다.
그럼 우리는 JVM이 이해할 수 있는 형태로 번역을 해서 전해줘야한다.
이때 이 JVM이 이해할 수 있는 형태가 바이트코드이다.
위에서 보았듯이 javac 파일.java 명령어를 이용해서 .class 파일을 생성하는데 이때 이 .class 파일이 바이트 코드이다.
JIT 컴파일러란 무엇이며 어떻게 동작하는가
JIT 컴파일러는 Just In Time 컴파일러로 바이트코드를 기계어로 번역하여 실행하는 것을 뜻한다.
실행엔진에는 Interpreter와 JIT(Just-In-Time) Compiler가 있다. Interpreter는 바이트 코드를 할줄씩 읽기 때문에 느린 단점이 있다. 이러한 단점을 보완하기 위해서 JIT Compiler가 나왔다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일 하고 더이상 인터프리팅 하지 않고 해당 코드를 직접 실행하는 것이다. JIT Compiler에 의해 해석된 코드는 캐시에 보관하기 때문에 한 번 컴파일 된 후에는 빠르게 수행하는 장점이 있다.
하지만 인터프리팅 방식보다는 훨씬 오래 걸리므로 한번만 실행하면 되는 코드는 인터프리팅하는것이 유리하다.
JDK 와 JRE의 차이
자바 세가지 에디션
Java SE(Standard Edition) Java vm과 표준 api 등을 정리한 가장 표준적인 버전이다. PC나 서버 등에서 동작하는 애플리케이션을 개발 및 실행하는 경우에 사용한다.
Java EE(Enterprise Edition) 웹 서비스나 서버 간 통신, 메일 송신 등 서버 애플리케이션에 필요한 기능이 많이 포함되어 있다.
Java ME(Micro Edition) 가전제품과 휴대전화 등의 임베디드 시스템용의 애플리케이션을 개발하기 위한 에디션이다. Java SE에 비해 사용할 수 있는 기능이 한정되어 있지만 필요한 리소스(CPU나 메모리 등)가 적게 들어 임베디드 시스템 등에서도 잘 동작한다. 또한 자바는 실행 환경 (JRE) 및 개발 환경 (JDK)의 두가지로 나누어져 있다.
JDK(Java Development Kit) : 자바 개발자 도구
- 자바 애플리케이션의 개발 환경이다. 실행 환경뿐만 아니라 소스 파일의 컴파일러 및 디버거 등 자바 애플리케이션을 개발하기 위한 도구가 포함되어 있다.
JRE(Java Runtime Environment) : 자바 실행 환경
- 자바 애플리케이션의 실행 환경이다. 이미 컴파일된 자바 애플리케이션의 모듈(JAR 파일과 클래스 파일 등)을 이용하여 실행할 수 있다.
JAR 파일 - 자바 프로젝트 압축 파일
정리하자면 자바 언어로 프로그램을 개발하기 위해서는 JDK가 필요하고 자바 언어로 작성된 프로그램을 실행하기 위해서는 JRE가 필요하다. JDK에는 JRE가 포함되어 있다.