JAVA의 특징
- 객체 지향 언어
- 객체를 만들기 위해 클래스를 작성
- 객체 지향 언어의 특징인 캡슐화, 상속성, 다형성, 추상화를 완벽하게 지원
- 높은 이식성
- 자바 실행환경이 설치되어 있는 모든 운영체제에서 실행 가능
- 하이브리드 언어
- 컴파일 언어인 동시에 인터프리터 언어
- 소스를 컴파일하여 이진 파일(class)로 만든 다음 자바 런타임이 클래스 파일을 인터프리트하면서 실행
- 시스템에 무관한 이진 파일을 만들어 자바는 컴파일 언어에 가까운 속도와 시스템 독립성을 얻을 수 있음
- 메모리 자동 관리
- 개발자는 메모리에 직접 접근할 수 없고 자바가 직접 메모리를 관리
- 객체 생성시 자동으로 메모리 영역 찾아 할당
- 가비지 콜렉터를 실행시켜 자동으로 사용하지 않는 객체 제거
- 개발자는 메모리 관리의 수고를 덜고 핵심 기능 코딩에 집중 가능
- 풍부한 오픈 소스 라이브러리
- 자바는 오픈 소스 언어, 라이브러리 또한 오픈 소스의 양이 방대함
- 검증된 오픈 소스 라이브러리를 사용하면 애플리케이션을 다시 컴파일 할 필요 X
- 자바는 실행을 위해 자바 가상 머신을 거쳐야하므로, 다른 언어에 비해 실행 속도가 느림
- 자바는 예외 처리가 잘 되어있지만, 개발자가 일일이 처리를 지정해줘야 하는 불편함이 있음
- 자바는 다른 언어에 비해 작성해야 하는 코드 길이가 긴 편
- 멀티 쓰레딩 지원
- 운영체제마다 멀티 쓰레드를 이용하는 API가 다르지만 자바의 경우 자바 API를 사용하기 때문에 쉽게 구현 가능
- 동적임
- 객체간의 상호 작용을 정의하기 때문에 필요하지 않은 객체는 생성되지 않고 필요한 객체만 생성하여 사용
- 오류가 발생하면 오류가 발생한 클래스만 수정하면 되므로 전체를 수정할 필요가 없음
- 즉, 유지보수를 쉽고 빠르게 진행할 수 있음
- 안전하고 강력함
- 모든 메모리 접근을 자바 시스템이 관리해서 시스템 붕괴의 우려가 없음
- 포인터 개념이 없고 유형 정의가 강고하여 실행 전에 클래스 파일을 이용한 프로그램 검사가 가능
JVM
- JVM이란 Java Virtual Machine, 자바 가상 머신을 뜻함.
- JVM의 역할
- Java와 OS 사이에서 중개자 역할을 수행
- Java가 OS에 구애받지 않고 독립적으로 작동이 가능하도록 함
- 가장 중요한 메모리 관리, Garbage collection(가비지 컬렉션)을 수행
- JVM의 특징
- 컴파일된 바이트 코드를 기계가 이해할 수 있는 기계어로 변환
- 스택 기반의 가상 머신
- 메모리 관리와 GC를 수행
- JVM의 구조
- 클래스 로더 : JVM 내로 클래스 파일을 동적으로 로드하고 링크를 통해 배치하는 작업을 수행하는 모듈. 바이트 코드들을 엮어서 JVM의 메모리 영역인 Runtime Data Areas에 배치
- 실행 엔진 : 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행. 실행 엔진은 인터프리터와 JIT 컴파일러 두 가지 방식을 혼합하여 바이트 코드를 실행함
- 인터프리터 : 명령어를 하나씩 읽어서 해석하고 바로 실행
- JIT 컴파일러 : 바이트 코드 전체를 컴파일하여 Native Code로 변경하고 캐싱해두었다가 네이티브 코드로 직접 실행하는 방식
- 가비지 컬렉터 : Heap 메모리 영역에서 더는 사용하지 않는 메모리를 자동으로 회수해줌
- 런타임 데이터 영역 : JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역
- 메소드 영역 : 바이트 코드를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간. 프로그램이 종료될 때까지 저장
- 힙 영역 : new 연산자로 생성되는 클래스와 인스턴스 변수, 배열 타입 등 Reference Type이 저장되는 곳
- PC Register : 현재 수행중인 JVM 명령어 주소를 저장하는 간
- 스택 영역 : 기본 자료형을 생성할 때 저장하는 공간으로, 임시적으로 사용되는 변수나 정보들이 저장되는 영역
- 네이티브 메소드 스택 : 기계어롤 작성된 프로그램을 실행시키는 영역
- JNI - 네이티브 메소드 인터페이스
- 네이티브 메소드 라이브러리
Java의 컴파일 과정
- 개발자가 자바 소스 코드 작성(.java)
- 자바 컴파일러가 자바 소스 파일 컴파일
- 이때 나오는 파일은 자바 바이트 코드 파일(.class)
- 자바 가상 머신이 이해할 수 있는 코드
- 컴파일된 바이트 코드를 JVM의 클래스로더에게 전달
- 클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역, 즉 JVM의 메모리에 올림
- 클래스 로더 세부 동작
로드 -> 검증 -> 준비 -> 분석 -> 초기화
- 실행엔진이 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행
- 인터프리터 : 바이트 코드 명령어를 하나씩 읽어서 해석 및 실행
- 하나하나의 실행은 빠르지만 전체적인 실행 속도는 느림
- JIT 컴파일러 : 인터프리터의 단점을 보완하기 위해 도입된 방식으로 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경하고 이후에는 해당 메서드를 더이상 인터프리팅하지 않고 바이너리 코드로 직접 실행하는 방식
Java에서 제공하는 원시 타입
- boolean
- 크기 : 1바이트
- 종류 : 논리형
- 표현 범위 : true/false
- char
- 크기 : 2바이트
- 종류 : 문자형
- 표현 범위 : '\u0000' ~ 'uFFFF' (16비트 유니코드 문자 데이터)
- 특징 : Java에서 유일하게 제공되는 unsigned 형태
- byte
- 크기 : 1바이트
- 종류 : 정수형
- 표현 범위 : -128 ~ 127
- short
- 크기 : 2바이트
- 종류 : 정수형
- 표현 범위 : -32768 ~ 32767
- int
- 크기 : 4바이트
- 종류 : 정수형
- 표현 범위 : -2147483648 ~ 2147483647
- 특징 : 정수형의 기본 형태
- long
- 크기 : 8바이트
- 종류 : 정수형
- 표현 범위 : -9223372036854775808 ~ 9223372036854775807
- 특징 : 정수 데이터 맨 뒤 쪽에 접미사 'l'이나 'L'을 붙여줘야 함
- float
- 크기 : 4바이트
- 종류 : 실수형
- 표현 범위 : 1.4E-45 ~ 3.4028235E38
- 특징 : 실수 데이터 맨 뒤 쪽에 접미사 'f'이나 'F'을 붙여줘야 함
- double
- 크기 : 8바이트
- 종류 : 실수형
- 표현 범위 : 4.9E-324 ~ 1.7976931348623157E308
- 특징 : 실수형의 기본 형태
Java에서 제공하는 참조 타입
- 참조 타입이란 원시 타입을 제외한 타입들(문자열, 배열, 열거, 클래스, 인터페이스)을 말함
- 실제 객체는 힙 영역에 저장되고 참조 타입 변수는 스택 영역에 실제 객체들의 저장함
- 객체를 사용할때마다 참조 변수에 저장된 객체의 주소를 불 러와 사용하는 방식
원시 타입과 참조 타입의 차이
- Null 포함 가능 여부
- 원시타입은 null을 담을 수 없지만 참조 타입은 가능함
int i = null; //불가능
Integer integer = null; //가능
- 제너릭 타입에서 사용 가능 여부
- 원시타입은 제너릭 타입에서 사용할 수 없지만 참조 타입은 가능함
List<i> list; //불가능
List<Integer> list; //가능
Boxing, Unboxing
- Boxing(박싱) : 원시 타입 -> 참조 타입
- Unboxing(언박싱) : 참조 타입 -> 원시 타입
- Java의 Auto Boxing/Unboxing으로 명시적으로 원시타입을 참조타입으로 감싸주지 않아도 자동으로 Boxing/Unboxing 해줌
- Auto Boxing/Unboxing은 메모리 누수의 원인이 될 수 있음
원시 타입의 장점
- 빠른 접근 속도
- 원시타입은 스택 메모리에 값이 존재하지만 참조 타입은 하나의 인스턴스이기 때문에 스택 메모리에는 참조값만 있고 실제 값은 힙 메모리에 존재
- 값을 필요로 할때마다 언박싱 과정을 거쳐야 하니 참조 타입은 원시 타입에 비해서 접근 속도가 느려질 수 밖에 없음
- 메모리 양
- 참조 타입이 사용하는 메모리양이 압도적으로 높음
- 따라서 메모리 사용적으로도 원시 타입이 참조 타입보다 효율적으로 사용할 수 있음
오버라이딩(Overriding)과 오버로딩(Overloading)
- 오버라이딩
- 상위 클래스의 메서드를 하위 클래스가 재정의하는 것
- 모든게 동일해야 함(메서드 이름, 파라미터 개수나 타입 등)
- 주로 상위 클래스의 동작을 상속받은 하위 클래스에서 변경하기 위해 사용
- 상속 받은 메서드의 내용만 변경하는 것
- 오버로딩
- 메서드의 이름은 같고 매개변수의 개수나 타입이 다른 함수를 정의하는 것을 의미
- 리턴값만을 다르게 갖는 오버로딩은 작성할 수 없음
- 기존에 없던 새로운 메서드를 정의하는 것
객체지향 프로그래밍(OOP)
- OOP란 문제를 여러개의 객체 단위로 나눠 작업하는 방식
- 객체들이 서로 유기적으로 상호작용 하는 프로그래밍 이론
- OOP 장점
- 코드 재사용성 증가
- 생산성 향상
- 자연적인 모델링
- 유지보수의 우수성
- OOP 단점
- 개발 속도가 느림
- 실행 속도가 느림
- 코딩 난이도 상승
- OOP 특징
- 클래스를 이용해 연관 있는 처리 부분(함수)과 데이터 부분(변수)을 하나의 객체(인스턴스)로 묶어 생성해 사용함
- 캡슐화, 추상화, 상속성, 다형성 네가지의 특성을 지님
캡슐화, 추상화, 상속성, 다형성
1. 추상화
- 공통의 속성이나 기능을 묶어 이름을 붙이는 것
- 객체 지향적인 관점에서 클래스를 정의하는 것을 추상화라고 함
2. 캡슐화
- 데이터 구조와 데이터를 다루는 방법들을 결합시켜 묶는 것
- 즉, 변수와 함수를 하나로 묶는 것
- 무작정 묶는 것이 아니라 객체가 맡은 역할을 수행하기 위한 하나의 목적을 한데 묶는 것 (은닉화)
- 데이터를 외부에서 직접 접근하면 안되고 오로지 함수를 통해서만 접근해야 하는데 이를 가능하게 해주는 것이 캡슐화
- 캡슐화에 성공하면 은닉화도 자연스럽게 효력이 나타남
3. 상속성
- 상위 개념의 특징을 하위 개념이 물려받는 것
- 객체 지향의 가장 중요한 부분
4. 다형성
- 부모클래스에서 물려받은 가상 함수를 자식 클래스 내에서 오버라이딩해 사용하는 것
try-with-resources
- try-catch-finally의 문제점을 보완하기 위해 나온 개념
- try(자원 선언문)와 같이 자원 객체를 전달하면 try 블록이 끝난 후 자동으로 자원 해제
- 따로 finally 구문이나 모든 catch 구문에 종료 처리를 하지 않아도 되는 장점
- 선언된 자원은 AutoCloseable 인터페이스가 구현된 객체여햐 함
불변 객체
- 객체 생성 이후 내부의 상태가 변하지 않는 객체
- read-only 메소드만을 제공
- 객체의 내부 상태를 제공하는 메소드를 제공하지 않거나 방어적 복사를 통해 제공
- Java의 대표적인 불변 객체 - String
- Java에서 필드가 원시 타입인 경우 final 키워드를 사용해 불변 객체를 만들 수 있음
- 참조 타입의 경우 추가적인 작업 필요
- 참조 변수가 일반 객체인 경우 객체를 사용하는 필드의 참조 변수도 불변 객체로 변경
- 배열일 경우 배열을 받아 copy해서 저장, getter를 clone으로 반환하도록 변경
- 리스트인 경우 배열과 마찬가지로 생성시 새로운 list를 만들어 값을 복사하도록 해야함
- 배열과 리스트는 내부를 복사하여 전달하는데, 이를 방어적 복사라고 함
불변 클래스 생성 규칙
- 클래스를 final로 선언
- 모든 클래스 변수를 private와 final 선언
- 객체를 생성하기 위한 생성자 또는 정적 팩토리 메소드 추가
- 참조에 의해 변경가능성이 있는 경우 방어적 복사를 이용하여 전달
얕은 복사, 깊은 복사, 방어적 복사
- 얕은 복사
- 객체를 복사할 때 해당 객체만 복사하여 새 객체 생성
- 복사된 객체의 인스턴스 변수는 원본 객체의 인스턴스 변수와 같은 메모리 주소 참조
- 해당 메모리 주소 값이 변경되면 원본 객체 및 복사 객체의 인스턴스 변수값은 같이 변경
- 깊은 복사
- 객체를 복사할 때 해당 객체와 인스턴스 변수까지 복사하는 방식
- 전부를 복사하여 새 주소에 담기 때문에 참조를 공유하지 않음
- 깊은 복사를 위해서는 객체는 Cloneable Interface를 implement 해야하고 clone 메서드를 오버라이드 해야함
- 방어적 복사
- 생성자의 인자로 받은 객체의 복사본을 만들어 내부 필드를 초기화하거나, getter 메서드에서 내부의 객체를 반환할 때 객체의 복사본을 만들어 반환
- 방어적 복사를 사용할 경우 외부에서 객체를 변경해서 내부의 객체는 변경되지 않음
- 깊은 복사는 clone 메서드 사용에 있어서 문제점이 있고, 이와 같은 객체의 취약점을 방어하기 위해 사용
- clone 메서드가 사용자가 정의한 것이 아닐 수 있음 (clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수도 있어 사용에 주의가 필요)
불변 객체 및 final 사용해야 하는 이유
- Thread-safe 하여 병렬 프로그래밍에 유용, 동기화를 고려하지 않아도 됨
- 실패 원자적인(Failure Atomic) 메소드를 만들 수 있음
- Cache나 Map 또는 Set 등을 요소로 활용하기에 적합
- 부수 효과를 피해 오류가능성을 최소화할 수 있음
- 다른 사람이 작성한 함수를 예측가능하며 안전하게 사용할 수 있음
- 가비지 컬렉션의 성능을 높일 수 있음
추상 클래스와 인터페이스
-
공통점
- 인스턴스를 생성할 수 없음
- 선언만 되어 있고 구현 내용이 없음
- 자식 클래스가 메소드의 구체적인 동작을 구현하도록 책임 위임
-
추상 클래스
- 추상 메소드를 자식 클래스가 구체화하여 그 기능을 확장하는 데에 목적이 있음
- 상속받는 클래스에서 구현을 강제화함
- 상속을 위한 부모 클래스로 활용
- 클래스가 존재
- 단일 상속 (extends)
- 최소 한 개의 추상 메소드를 포함하는 경우 반드시 추상 클래스로 선언해야 함
- 추상 메소드가 없어도 추상 클래스로 선언할 수 있음
-
인터페이스
- 서로 관련이 없는 클래스에서 공통적으로 사용하는 방식이 필요하지만 기능을 구현할 필요가 있는 경우에 사용
- 클래스가 존재하지 않음
- 다중 상속 (implement)
- interface 키워드 사용하여 선언
- 클래스가 자신의 목적에 맞게 메소드를 구현할 수 있도록 함
- 구현 객체의 같은 동작을 보장하기 위한 목적
- 상수 필드와 추상 메소드만으로 구성
- 모든 메소드는 추상 메소드 (public abstract 생략 가능)
- 상수는 public static final 속성 생략 가능
-
차이점 정리
- 인터페이스는 그 인터페이스를 구현하는 모든 클래스에 대해 특정한 메소드가 반드시 존재하도록 강제함, 추상 클래스는 상속받는 클래스들의 공통적인 로직을 추상화시키고 기능 확장을 위해 사용
- 추상 클래스는 다중 상속이 불가능하지만 인터페이스는 다중 상속이 가능
인터페이스 다중 상속 허용 이유
- 자식 클래스가 둘 이상의 클래스로부터 다중 상속을 받는다고 가정
- 두 클래스에 모두 같은 이름의 메서드가 존재하고 동일한 시그니처를 가진다면(선언부가 완벽히 같은 경우) 자식클래스는 둘 중 어떤것을 상속받을지 파악할 수 없게 됨
- 이미 구현 완료된 동일 시그니처의 메서드를 상속받는 일은 불가능함
- 자식 클래스 이상의 인터페이스로부터 다중 상속을 받는다고 가정
- 선언된 형태의 메서드만 가지고 메서드가 정의되지는 않았기 때문에 동일 시그니처의 메서드를 상속받아도 아무런 문제가 발생하지 안흥ㅁ
- 해당 메서드는 아직 구현되지 않아 어차피 자식 클래스에서 새롭게 정의해야 하기 때문임
- 정리
- 인터페이스는 구현된 메서드가 없기 때문에, 다중 상속이 가능
- 하지만 클래스나 추상 클래스는 이미 구현된 메서드가 존재하기 때문에 다중 상속이 불가능함
싱글톤 패턴
- 객체의 인스턴스를 한개만 생성되게 하는 패턴
- 사용하는 상황
- 프로그램 내에서 하나의 객체만 존재해야 할 때
- 프로그램 내 여러 부분에서 해당 객체를 공유하여 사용해야할 때
- 장점
- 메모리 측면의 이점 : 한개의 인스턴스만을 고정 메모리 영역에 생성
- 속도 측면의 이점 : 이미 생성된 인스턴스 활용으로 빠른 속도
- 쉬운 데이터 공유 : 전역으로 사용하는 인스턴스이기에 여러 다른 클래스에서 데이터를 공유하며 사용할 수 있음. (단, 동시성 문제에 유의해야함)
- Java, Spring에서 싱글톤이 적용된 사례
- Runtime()
- 빈의 싱글톤 스코프(스프링 빈 기본값이 싱글톤임)
자바를 이용해 싱글톤을 구현하는 6가지 방법
- Eager Initialization
싱글톤 클래스의 인스턴스를 클래스 로딩 단계에서 생성하는 방법
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
Exception에 대한 Handling 제공 안함. 클래스가 적은 리소스를 다룰때 유리함. 큰 리소스들을 다루는 경우 낭비 심함.
- Static Block Initialization
1과 유사하지만 static block을 통해서 Exception Handling에 대한 옵션을 제공함
public class Singleton {
private static Singleton instance;
private Singleton(){}
static{
try{
instance = new Singleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static Singleton getInstance(){
return instance;
}
}
인스턴스를 생성할 때 발생할 수 있는 예외에 대한 처리를 할 수 있지만, 클래스 로딩 단계에서 인스턴스를 생성하기 때문에 여전히 큰 리소스를 다루는 경우에는 적합하지 않음
- Lazy Initialization
나중에 초기화하는 방법. global access한 getInstance() 메서드를 호출할 때에 인스턴스가 없다면 생성
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
1, 2번의 문제인 사용하지 않을 경우 인스턴스가 낭비되는 문제에 대해 어느정도 해결책이 됨
but, multi-thread 환경에서 동기화 문제가 존재(single-thread 환경 보장시에 구현하면 좋음)
- Thread Safe Singleton
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
getInstance() 메서드 내 진입하는 쓰레드가 하나로 보장받기에 멀티 쓰레드 환경에서도 정상 동작
but, synchronized 키워드 자체에 대한 비용이 크기 때문에 싱글톤 인스턴스 호출이 잦은 어플리케이션에서는 성능 떨어짐
getInstance() 메서드 수준에 lock을 걸지 않고 instance가 null일 경우에만 sunchronized가 동작하도록 하는 double checked locking이 고안됨!
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
- Bill Pugh Singleton Implementation
public class Singleton {
private Singleton(){}
private static class SingletonHelper{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
inner static helper class를 사용하는 방식
현재 가장 널리 쓰이는 싱글톤 구현 방법
singletonHelper 클래스는 singleton 클래스가 load 될때에도 load되지 않다가 getInstance()가 호출되었을때 비로소 JVM 메모리에 로드, 인스턴스 생성
synchronized를 사용하지 않으므로 성능 저하도 해결
- Enum Singleton
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
}
}
Reflection을 통한 싱글톤 파괴 불가능
but, 사용하지 않았을 경우 메모리 문제를 해결하지 못한 것과 유연성이 떨어진다는 한계 존재
가비지 컬렉션(Garbage Collection)
- JVM에는 가비지 컬렉터가 존재함
- 가비지 컬렉터는 더 이상 참조되지 않는 가비지라고 불리는 불필요한 메모리를 알아서 정리해주는 역할을 함
- 가비지 컬렉터가 주기적으로 메모리 누수를 방지하기 위해 메모리를 청소하는 과정을 가비지 컬렉션이라고 함
- 장점
- 이미 해제된 메모리에 접근하는 버그 방지
- 이미 해제된 메모리를 또 다시 해제하는 버그 방지
- 메모리 누수 방지
- 단점
- 메모리 해제되는 시점을 알 수 없음
- 해제할 메모리를 결정하는데 비용 O
- 쓰레기 수집이 이루어지는 시점을 예측할 수 없어서 어떤 타이밍에 점유당할지 모름
- 가비지 컬렉션 방법
- 표시하고 쓸기, 삼색 표시 기법, 객체 이동 기법, 세대 단위 쓰레기 수집 등의 방법이 있음 (자바는 세대 단위 쓰레기 수집 방법 이용
- 세대 단위 쓰레기 수집 방법
- 오랫동안 사용된 객체일수록 앞으로 사용되지 않을 확률이 적고, 최근에 생긴 객체일수록 사용되지 않을 확률이 높음
- 할당된 시간에 따라 세대별로 구분하여 서로 다른 메모리 영역에 객체를 할당
- 한 세대의 메모리 영역이 꽉 차면, 이 메모리 영역에서 살아남은 객체를 더 오래된 메모리 영역으로 옮김
- 오래된 영역(Old Memeory)의 객체는 해제될 확률이 줄어서 새로 할당 영역(Young Memory)만 주기적으로 수집하게 되는 장점이 있음
- 가비지 컬렉션 과정
- JVM이 어플리케이션 실행을 잠시 멈추고 GC를 실행하는 쓰레드를 제외한 모든 쓰레드들의 작업을 중단(Stop The World)
- 사용하지 않는 메모리를 제거(Mark and Sweep)
- 작업 재개
객체지향의 설계 원칙(SOLID)
- SRP : 단일 책임 원칙
- 클래스는 단 하나의 책임만 가져야 함
- 하나의 클래스는 하나의 기능을 담당하여 하나의 책임을 수행하는 데에 집중
- 목적은 프로그램의 유지보수성을 높이는 것
- OCP : 개방 폐쇄 원칙
- 확장에는 열려있어야 하며, 수정에는 닫혀있어야 함
- 클래스는 확장을 통해 손쉽게 구현하면서 확장에 따른 클래스 수정은 최소화하도록 함
- 확장에 열림 : 새로운 변경 사항 발생시 유연한 코드 추가로 큰 힘을 들이지 않고 애플리케이션 기능 확장 가능
- 변경에 닫힘 : 새로운 변경 사항 발생시 객체를 직접적으로 수정하지 않음
- 추상화 사용을 통한 관계 구축을 권장
- LSP : 리스코프 치환 원칙
- 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 함
- 다형성 우너리를 이용하기 위한 원칙
- 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 함
- ISP : 인터페이스 분리 원칙
- 인터페이스를 각각 사용에 맞게끔 작게 분리해야 함
- 인터페이스의 단일 책임 강조
- 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 제공이 목표
- 한번 인터페이스를 분리하여 구성하고 나중에 무언가 수정사항이 생겨 또 인터페이스를 분리하는 행위를 가하지 말아야 함
- DIP : 의존 역전 원칙
- 어떤 클래스를 참조해서 사용해야하는 상황이 생겼을 때, 그 클래스를 직접 참조하기보다 그 대상의 상위요소로 참조하라는 원칙
- 구현 클래스에 의존하지 않고 인터페이스에 의존하라는 뜻
- 변화가 쉬운 것보다는 변화가 어려운 것에 의존하라는 뜻
- 클래스간의 결합도를 낮추는게 지향점
Java의 메모리 영역
- JVM의 메모리 공간은 크게 Method(Static) 영역, Stack 영역, Heap 영역으로 구분
- Method(Static) 영역
- 런타임 상수 풀, 멤버 변수(필드), 클래스 변수(static 변수), 상수(final), 생성자(constructor)와 메소드(method) 등을 저장하는 공간
- 어느곳에서나 접근 가능
- 프로그램의 시작부터 종료까지 메모리에 남아있음
- JVM 동작해서 클래스가 로딩될 때 생성
- Stack 영역
- 메소드가 호출될 때 스택 영역에 스택 프레임이 생기고 그 안에 메소드를 호출
- 메소드 내에서 정의하는 primitive(int, double, byte, long 등) 타입의 데이터에 해당되는 지역변수, 매개 변수 데이터 값이 저장
- 메소드가 호출될 때 메모리에 할당되고 종료되면 메모리에서 사라짐
- 후입선출의 특성을 가지며, 스코프의 범위를 벗어나면 스택 메모리에서 사라짐
- 컴파일(소스코드 -> 기계어 변환으로 실행 가능한 프로그램이 될 때) 시 할당
- Heap 영역
- JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역
- 참조형 데이터 타입을 갖는 객체(instance), 배열 등이 저장되는 공간
- 단, Heap 영역에 있는 object를 가리키는 레퍼런스 변수는 stack에 적재
- Stack 영역과 다르게 보관되는 메모리가 호출이 끝나도 삭제되지 않고 유지
- 어떤 참조 변수도 Heap 영역에 있는 인스턴스 참조하지 않게 되면 GC에 의해 메모리에서 청소
- Stack은 Thread 개수마다 각각 생성, Heap은 몇개의 Thread가 존재하든 단 하나의 Heap 영역만 존재
- 런타임(컴파일 이후 프로그램이 실행되는 때) 시 할당
- Stack과 Heap의 차이점
- 힙 메모리는 애플리케이션의 모든 부분에서 사용되나 스택 메모리는 하나의 Thread가 실행될 때 사용
- 힙과 메서드 공간에 저장된 객체는 어디서든 접근 가능, 스택 메모리는 다른 Thread 접근 불가능
- 객체 생성되면 항상 힙 공간에 저장, 스택 메모리는 힙 공간에 있는 객체를 참조만 함
- 스택 메모리의 생명주기는 매우 짧음, 힙 메모리는 애플리케이션 시작부터 끝까지 살아남음
- 스택 메모리가 가득 차면 StackOverFlowError 발생, 힙 메모리가 가득 차면 OutOfMemoryError 발생
- 스택 메모리 사이즈는 힙 메모리에 비교했을 때 매우 작음, 스택 메모리는 LIFO 사용으로 힙 메모리보다 빠름
클래스와 객체, 인스턴스
- 클래스
- 객체를 만들어내기 위한 설계도 혹은 틀
- 연관되어 있는 변수와 메서드의 집합
- 객체
- 소프트웨어 세계에 구현할 대상
- 클래스에 선언된 모양 그대로 생성된 실체
- 클래스의 인스턴스
- 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 가짐
- 인스턴스
- 설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체
- 객체를 소프트웨어에 실체화하면 인스턴스
- 실체화된 인스턴스는 메모리에 할당
- 인스턴스는 객체에 포함된다고 볼 수 있음
- oop 관점에서 객체가 메모리에 할당되어 실제 사용될 때 인스턴스로 불림
- 객체는 클래스의 인스턴스
- 실행 프로세스는 프로그램의 인스턴스
- 어떤 원본(추상적인 개념)으로부터 생성된 복제본을 의미
- 객체 VS 인스턴스
- 클래스 타입으로 선언되었을 때 객체라고 부르고, 그 객체가 메모리에 할당되어 실제 사용될 때 인스턴스라고 부름
- 객체는 현실 세계에 가깝고 인스턴스는 소프트웨어 세계에 가까움
- 객체는 실체, 인스턴스는 관계에 초점을 맞춤
- 객체를 클래스의 인스턴스라고도 부름
생성자
- new 연산자를 통해서 인스턴스를 생성할 때 반드시 호출되고 제일 먼저 실행되는 일종의 메소드
- 인스턴스 변수(필드값 등)를 초기화시키는 역할을 함
- 클래스와 같은 이름의 메소드
- 객체가 생성될 때 호출됨
- 명시적으로 생성자를 만들지 않아도 default로 만들어짐
- 파라미터를 다르게 하여 오버로딩 가능
Wrapper 클래스
- 원시 자료형에 대한 객체 표현을 Wrapper class 라고 함
- 원시 자료형에 대한 참조타입
- int -> Integer, byte -> Byte, char -> Character, double -> Double 등
- primitive type -> wrapper class : Boxing
- wrapper class -> primitive type : Unboxing
Synchronized
- 멀티스레드 환경에서 하나의 공유자원에 동시에 접근하여 사용하면 문제가 생기기도 함 -> 스레드 동기화를 통해 해결
- 스레드 동기화 : 멀티스레드 환경에서 여러 스레드가 하나의 공유자원에 동시에 접근하지 못하도록 막는 것
- 임계 영역 : 공유 데이터가 사용되어 동기화가 필요한 부분
- 임계 영역에 synchronized 키워드를 사용하여 여러 스레드가 동시에 접근하는 것을 금지할 수 있음 -> 동기화
- synchronized로 지정된 임계 영역은 한 스레드가 이 영역에 접근하여 사용하면 lock 걸림
- 해당 임계 영역의 코드를 다 실행한 후 벗어나게 되면 unlock 상태가 됨
- lock은 해당 객체당 하나씩 존재, synchronized로 설정된 임계 영역은 lock 권한을 얻은 하나의 객체만이 독점적으로 사용
- 사용법
- 메소드에 synchronized 설정
- 메소드 이름 앞에 키워드를 사용하여 해당 메소드 전체를 임계 영역으로 설정
- 코드 블럭에 synchronized 설정
- 동기화를 많이 사용하여 효율이 떨어지게 되는 상황을 방지하기 위해 꼭 필요한 부분에만 블럭 지정하여 임계 영역으로 설정
new String()과 리터럴("")의 차이점
- Java에서 문자열을 생성하는 과정
- 문자열 리터럴 사용
- new String() 사용
- 리터럴과 new String()의 차이점
- 리터럴
- 문자열들이 String Pool에 생성됨
- JVM이 이미 String Pool에 존재하는 String은 그 String을 참조하도록 (같은 문자열이면 같은 주소를 가리키도록 함)
- new String()
- Java의 Heap Memory에 각각의 문자열 객체가 생성됨
- 같은 문자열이어도 기존 문자열 참조 없이 새로 생성
String Pool
- String Pool은 Java Heap의 저장 영역
- JVM은 과도한 String 개체의 개수를 줄이고자 문자열 리터럴이 생성될때마다 String pool 체크
- 이미 존재하는 문자열인 경우 인스턴스에 대한 참조가 반환
- 존재하지 않으면 새로운 인스턴스가 String Pool에 생성
- 리터럴을 사용하는 것이 릭기가 더 쉽고 컴파일러가 코드를 효율적으로 최적화함
== Operator VS Equals()
- == Operator는 동일한 메모리 공간인지 비교하는 연산자
- == Operator는 동일한 공간을 참조하고 있는지를 비교
- Equals()는 객체의 내용을 비교하는 메소드
- 동일한 문자열인 경우 리터럴을 사용했다면 String Pool 내부의 같은 인스턴스를 참조하므로 == Operator의 결과값으로 True가 나옴
- 동일한 문자열인 경우 new String()을 사용했다면 Heap에 서로 다른 객체를 참조하므로 == Operator의 결과값으로 False가 나옴
String, StringBuffer, StringBuilder의 차이점
- String
- 한번 값이 할당괴면 그 공간은 변하지 않음. 불변성
- 새로운 값을 할당할 경우 다른 메모리 영역을 가리키도록 변경함
- 처음 가리키던 메모리 영역은 GC에 의해 정리됨
- 불변하기 때문에 문자열을 연산하는 시점에서 새로운 String 인스턴스가 메모리에 할당됨
- 변하지 않는 문자열을 자주 읽어들일 때 사용하면 좋은 성능을 기대할 수 있음
- 연산이 자주 일어나는 로직에서 String 클래스를 사용하면 힙 메모리에 많은 가비지가 생성되어 힙메모리 부족으로 애플리케이션 성능에 큰 부정적 영향
- 불변성으로 인해 멀티스레드 환경에서 안전
- StringBuffer
- 한번 값이 할당되더라도 한번 더 다른 값이 할당되면 할당된 공간이 변함. 가변성
- AbstractStringBuilder라는 추상 클래스 상속받아 구현
- 위 추상 클래스에 정의되어 있는 append 메서드는 문자열을 추가했을 때 추가할 문자열 크기만큼 현재 문자열을 저장하는 배열을 공간을 늘리고, 늘린 공간에 추가할 문자열을 넣어주는 방식
- 위의 동작으로 인해 값이 변경되어도 같은 주소 공간을 참조, 값이 변경되는 가변성을 띔
- 동기화 키워드를 지원해 멀티스레드 환경에서 안전
- StringBuilder
- 한번 값이 할당되더라도 한번 더 다른 값이 할당되면 할당된 공간이 변함. 가변성
- AbstractStringBuilder라는 추상 클래스 상속받아 구현
- 위 추상 클래스에 정의되어 있는 append 메서드는 문자열을 추가했을 때 추가할 문자열 크기만큼 현재 문자열을 저장하는 배열을 공간을 늘리고, 늘린 공간에 추가할 문자열을 넣어주는 방식
- 위의 동작으로 인해 값이 변경되어도 같은 주소 공간을 참조, 값이 변경되는 가변성을 띔
- 동기화를 지원하지 않기 때문에 멀티스레드 환경에서 사용하는 것은 적합하지 않음
- 동기화를 고려하지 않는 단일 스레드 환경에서는 StringBuffer보다 뛰어남
- 사용하는 상황
- String : 문자열 연산이 적고 멀티스레드 환경일 겨웅
- StirngBuffer : 문자열 연산이 많고 멀티스레드 환경일 경우
- StringBuilder : 문자열 연산이 많고 단일스레드이거나 동기화를 고려하지 않아도 되는 경우
String이 불변인 이유
- 성능
- 문자열 리터럴을 캐싱하고 재사용하면 String pool의 다른 문자열 변수가 동일한 개체를 참조하기 때문에 힙 공간을 많이 절약할 수 있음
- 동기화
- 불변 객체는 값이 바뀔 일이 없기 때문에 멀티쓰레드 환경에서 Thread-safe하다는 장점이 있음. 동시에 실행되는 여러 쓰레드에서 공유 가능
- 해시코드 캐싱
- String의 hashCode() 메서드 구현을 보면 최초 한번만 실제 계산 로직을 실행하고, 이후부터는 해당 값을 그냥 리턴만 하도록 overriding 되어있음 (계산해두었던 해시코드를 재사용)
- String이 불변이기에 caching이 가능하다는 이점을 활용 가능 (값이 변하지 않기 때문에)
- 보안
- String이 변경되지 않기에 중요한 정보를 저장하는데에 쓰이고 보안에 유리함
접근 제한자
- 접근 제한자란 객체의 멤버들에게 접근 제한을 거는 것
- 변수 또는 메소드의 접근 범위를 설정해주기 위해 사용하는 Java의 예약어
- 의도치 않은 실수를 줄이기 위함과 정보 은닉의 목적으로 사용할 수 있음
- 종류
- public : 모든 접근 허용
- protected : 같은 패키지에 있는 객체와 상속 관계의 객체들만 허용
- default : 같은 패키지에 있는 객체들만 허용
- private : 현재 객체 내에서만 허용
- 접근제한자의 사용
- 클래스 : public, default
- 생성자 : public, protected, default, private
- 멤버변수 : public, protected, default, private
- 지역변수 : 접근제한자 사용 불허
클래스 멤버 변수 초기화 순서
- static 변수 선언부 : 클래스가 로드될 때 변수가 제일 먼저 초기화됨(Method 영역에 올라감)
- 필드 변수 선언부 : 객체가 생성될 때 생성자 block보다 앞서 초기화됨(Heap 영역에 올라감)
- 생성자 block : 객체가 생성될 때 JVM에 내부적으로 locking(Heap 영역에 올라감, thread safe 영역임)
필드 변수 중 final 변수의 가시화는 생성자 block이 끝난 다음
필드 변수 선언부에서 이미 초기화되었다면 그 값들은 덮어씀
- 초기화 시점
- 클래스 변수의 초기화 시점 : 클래스가 처음 로딩될 때 한번 초기화
- 인스턴스 변수의 초기화 시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화
- 초기화 순서
- 클래스 변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
- 인스턴스 변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자
static
- 정적(static)은 고정된이라는 의미
- static 변수와 static 메소드를 만들 수 있음 (정적 필드와 정적 메소드라고 함 -> 정적 멤버, 클래스 멤버)
- 인스턴스(객체)에 소속된 멤버가 아니라 클래스에 고정된 멤버
- 클래스 로더가 클래스를 로딩해서 메소드 메모리 영역을 적재할 때 클래스별로 관리
- 클래스 로딩이 끝나는 즉시 바로 사용 가능
- Heap 영역이 아닌 Static 영역에 할당
- Static 영역에 할당된 메모리는 모든 객체가 공유하여 하나의 멤버를 어디서든지 참조할 수 있는 장점 가짐
- GC의 관리 영역 밖에 존재하기에 Static 영역에 있는 멤버들은 프로그램의 종료시까지 메모리가 할당된 채 존재
- static을 너무 남발하면 만들고자 하는 시스템 성능에 악영향
static 사용 이유
- 전역적으로 쉽게 재사용하는 멤버나 잘 변하지 않는 변수나, 메소드를 사용할 때 주로 사용됨
- 만들어놓고 클래스 호출, 객체 생성을 따로 할 필요 없이 바로바로 사용할 수 있기 때문에 사용성이 좋음
- 너무 많이 사용하면 메모리 자원 할당량이 높아 프로그램이 무거워짐
참고
https://dev-coco.tistory.com/153
https://backendcode.tistory.com/161
https://code-lab1.tistory.com/63
https://88240.tistory.com/228
https://gyoogle.dev/blog/computer-language/Java/%EC%BB%B4%ED%8C%8C%EC%9D%BC%20%EA%B3%BC%EC%A0%95.html
https://velog.io/@gillog/%EC%9B%90%EC%8B%9C%ED%83%80%EC%9E%85-%EC%B0%B8%EC%A1%B0%ED%83%80%EC%9E%85Primitive-Type-Reference-Type
https://88240.tistory.com/450
https://velog.io/@gillog/OOP%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D
https://mangkyu.tistory.com/131
https://velog.io/@miot2j/%EC%96%95%EC%9D%80%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80%EB%B3%B5%EC%82%AC-%EB%B0%A9%EC%96%B4%EC%A0%81-%EB%B3%B5%EC%82%AC%EB%9E%80
https://velog.io/@seongwon97/%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80
https://code-lab1.tistory.com/91
https://velog.io/@jkijki12/Java-%EA%B0%80%EB%B9%84%EC%A7%80%EC%BB%AC%EB%A0%89%ED%84%B0
https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EB%B3%B4%EB%8A%94-%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD%EC%8A%A4%ED%83%9D-%ED%9E%99
https://gmlwjd9405.github.io/2018/09/17/class-object-instance.html
https://kadosholy.tistory.com/123
https://developer-talk.tistory.com/44
https://dev-jwblog.tistory.com/108
https://gyrfalcon.tistory.com/entry/JAVA-%EC%A0%91%EA%B7%BC-%EC%A0%9C%ED%95%9C%EC%9E%90
https://hajoung56.tistory.com/33
https://eyevsky.tistory.com/entry/Java-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%A9%A4%EB%B2%84-%EB%B3%80%EC%88%98%EB%93%A4%EC%9D%98-%EC%B4%88%EA%B8%B0%ED%99%94-%EC%88%9C%EC%84%9C
https://coding-factory.tistory.com/524
https://junior-datalist.tistory.com/213
https://readystory.tistory.com/116
https://devlog-wjdrbs96.tistory.com/247
https://taewooblog.tistory.com/entry/java-%EC%9E%90%EB%B0%94-static-%EB%9E%80-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0