기계어(Machine Language) : CPU 가 직접 이해할 수 있는 0 과 1 로 구성된 이진 코드
어셈블리어(Assembly Language) : 기계어에 대한 프로그래밍 저급 언어로 기계어와 1:1 매핑됨
사람이 쓰는 개발언어를 기계어로 번역해주는 것이 컴파일러(Compiler) 와 인터프리터(Interpreter)
컴파일러(Compiler) : 고급 프로그래밍 언어로 작성된 프로그램 소스 코드 전체를 스캔해서 이를 모두 기계어로 한번에 번역해서 실행파일을 생성
인터프리터(Interpreter) : 고급 프로그래밍 언어로 작성된 프로그램 소스 코드를 한 줄씩 읽고 즉시 실행
런타임(Runtime) : 특정 프로그램이 실제로 실행되는 동안의 동작
특정 프로그램이 필요로 하는 시스템 자원(RAM, 시스템 변수, 환경변수 등) 을 할당받고 실제로 시스템 자원을 사용해 어떠한 처리를 하는 동작
런타임 환경(Runtime Environment) : 애플리케이션이 OS 의 시스템 자원(RAM, 시스템 변수, 환경변수 등) 에 액세스 할 수 있도록 해주는 실행 환경
자료형은 같게 저장할 수 있으나 각 자료형 별 저장된 값이 상이할 수 있음
Call By Value(값에 의한 호출)
Call By Reference(참조에 의한 호출)
깊은 복사(Deep Copy)
- 실제 값 복사
- 객체의 모든 필드를 복사해서 새로운 객체를 생성함. 객체의 상태를 완전히 복제할 때 사용
- 메모리를 많이 사용하고, 복사 과정에서 성능 저하 발생할 수 있음
얕은 복사(Shallow Copy)
- 주소 값 복사
- 객체의 참조 주소만 복사하여 새로운 객체를 생성함. 객체의 참조 주소만 복사하기 때문에 원본 객체와 복사된 객체가 동일한 데이터를 참조
- 메모리를 적게 사용하고, 성능이 뛰어남. 하지만 참조된 데이터를 변경하면 원본 데이터에도 영향을 끼침
특징 | 얕은 복사 (Shallow Copy) | 깊은 복사 (Deep Copy) |
---|---|---|
복사 방식 | 1차 데이터만 복사, 참조 데이터는 주소 복사 | 모든 데이터와 참조 데이터까지 재귀적으로 복사 |
원본과의 독립성 | 복사본과 원본이 참조 데이터 공유 | 복사본과 원본이 완전히 독립적 |
수행 속도 | 상대적으로 빠름 | 상대적으로 느림 |
메모리 사용량 | 원본과 공유하므로 적음 | 복제된 데이터를 저장하므로 많음 |
데이터 변경 영향 | 참조 데이터 변경 시 원본에 영향 | 복사본 변경 시 원본에 영향 없음 |
자료형(Data Type) : 데이터를 구성하고 관리하는 기본 개념으로, 데이터의 크기, 형식, 해석 방식을 정의.
자료형의 필요&중요 이유
특징 | 정적 타입 | 동적 타입 |
---|---|---|
타입 결정 시점 | 컴파일 시점 | 런타임 시점 |
자료형 선언 | 명시적으로 선언하거나 컴파일러가 추론 | 선언 필요 없음 |
타입 검사 | 컴파일러가 타입을 검사 | 런타임에 인터프리터가 타입을 검사 |
유연성 | 낮음 | 높음 |
오류 탐지 시점 | 컴파일 단계에서 오류를 탐지 | 런타임에서 오류를 탐지 |
주요 언어 | C, C++, Java, TypeScript, Go | Python, JavaScript, Ruby, PHP |
성능 | 일반적으로 빠름 | 상대적으로 느릴 수 있음 |
기본 데이터 타입(primitive types) : 기본적인 데이터 구조로 값 자체를 메모리에 저장하기에 크기가 고정되어 있음
참조 타입(reference types) : 실제 데이터는 메모리의 다른 위치에 저장되고, 변수는 해당 데이터의 참조(주소)를 저장함. 주로 객체, 배열, 함수 등의 복잡한 데이터 구조를 다룸
특징 | Primitive Types | Reference Types |
---|---|---|
저장 방식 | 값 자체를 저장 | 값의 메모리 주소를 저장 |
메모리 할당 | 스택(Stack) | 힙(Heap)에 데이터 저장, 스택에는 참조 저장 |
크기 | 고정 크기 | 크기가 동적 |
값 복사 | 값을 복사 (독립적) | 참조를 복사 (공유됨) |
변경 가능성 | 대부분 불변 | 대부분 변경 가능 |
성능 | 빠름 | 참조를 따라가야 하므로 상대적으로 느릴 수 있음 |
주요 예시 | 숫자, 문자, 불리언 등 | 객체, 배열, 리스트, 함수 등 |
타입변환 : 하나의 데이터 타입을 다른 데이터 타입으로 변환하는 과정으로 서로 다른 데이터 타입 간의 연산이나 호환성을 위해 사용함
명시적 형변환(Explicit Conversion)
묵시적 형변환(Implicit Conversion)
주의사항
특징 | 명시적 변환 (Explicit Conversion) | 묵시적 변환 (Implicit Conversion) |
---|---|---|
변환 요청 주체 | 개발자가 명시적으로 요청 | 컴파일러나 인터프리터가 자동 수행 |
코드 가독성 | 변환 과정이 명확히 드러남 | 변환 과정이 코드에 드러나지 않음 |
데이터 손실 가능성 | 발생할 수 있음 | 안전한 변환만 수행 |
사용 용이성 | 변환 함수나 캐스팅 연산자를 명시적으로 사용해야 함 | 추가 코드 작성 없이 자동 변환 |
변환 방향 | 데이터 크기와 무관하게 변환 가능 | 작은 크기/범위 → 큰 크기/범위로만 변환 |
long 은 8byte float 는 4byte 인데 왜 long->float 가 가능한지?
-> 일반적으로 메모리 설계상 정수 타입보다 실수 타입이 더 크게 되어있기 때문
char 타입은 문자 자료형이지만, 아스키 코드 숫자를 저장하기에 사실상 정수형 타입으로 볼 수 있음
범위 초과
오버플로(Overflow) / 언더플로(Underflow)
큰 자료형에서 작은 자료형으로 변환 시(ex long->int) 표현 범위를 벗어나는 부분은 잘리거나 왜곡된 값으로 변환될 수 있음
크기 축소 변환
크기 축소 변환 : 큰 범위(크기) 의 데이터 타입에서 작은 범위(크기) 의 데이터 타입으로 변환시 일부 데이터가 잘리거나 왜곡될 수 있음
ex) 큰 정수형 -> 작은 정수형 변환 시 데이터가 잘리거나 왜곡 되어 예상치 못한 데이터 발생(long->short) 에서 long 데이터가 short 로 담을 수 없는 경우
정밀도 손실
정밀도 손실 : 데이터 변환 시 원래 값의 정밀도가 유지되지 못할 수 있음
ex) 실수->정수 변환 시 소수점 이하가 잘려나가고 부동소수점 연산(float->double, double->float) 에서 반올림 오차 발생 가능
메모리 구조
코드 영역 (Text Segment)
- 실행할 프로그램의 코드가 저장.
- 보통 읽기 전용.
데이터 영역 (Data Segment)
- 전역 변수와 정적 변수(static variables)가 저장.
- 프로그램이 종료될 때까지 유지.
힙 영역 (Heap)
- 동적 메모리 할당에 사용.
- 런타임에 필요에 따라 크기가 증가하거나 감소.
스택 영역 (Stack)
- 함수 호출 시 사용되는 지역 변수와 매개변수가 저장.
- LIFO(Last In, First Out) 구조로 빠르게 접근.
기본 자료형(Primitive Types) 저장 방식
참조 자료형(Reference Types) 저장 방식
Object
객체 생성시 데이터는 힙 영역에 저장되고, 변수는 해당 객체의 메모리 주소를 저장함
class Example {
int x = 10;
}
Example obj = new Example();
obj 는 스택에 생성되고, 힙에 생성된 객체 Example 의 메모리 주소를 가지게 됨
Array
int[] array = {1,2,3};
array 는 스택에 저장되고, 배열 데이터 [1,2,3] 은 힙에 저장String
자료형 | 저장 위치 | 저장 내용 |
---|---|---|
Primitive | 스택 | 값 자체 (예: int, float, char) |
Reference | 스택 & 힙 | 스택에 참조(주소), 힙에 실제 데이터 저장 |
전역 변수 | 데이터 영역 | 프로그램 실행 동안 지속 |
동적 할당 | 힙 | 힙에 데이터 저장, 스택에 참조 저장 |
코드 | 코드 영역 | 실행할 명령어 저장 |
Immutable(불변성)
String Pool 사용
String str1 = "Hello";
String str2 = "Hello"; // str1과 동일한 문자열 참조
포인터(Pointer)
참조(Reference)
특징 | 포인터 (Pointer) | 참조 (Reference) |
---|---|---|
메모리 주소 | 메모리 주소를 저장 | 별칭(alias)로, 내부적으로 주소를 사용 |
NULL 가능성 | NULL 값을 가질 수 있음 | NULL을 가질 수 없음 (항상 유효해야 함) |
선언 및 초기화 | 선언 후 초기화하지 않아도 됨 | 선언과 동시에 초기화가 필요 |
재할당 가능 여부 | 다른 주소를 가리키도록 변경 가능 | 초기화 이후 다른 객체를 참조할 수 없음 |
연산 가능 여부 | 주소를 기반으로 산술 연산 가능 | 직접 연산 불가 |
사용 목적 | 메모리 주소의 직접 조작, 동적 메모리 관리 | 변수나 객체에 대한 간편하고 안전한 접근 |
배열(Array)
컬렉션(Collection)
특징 | 배열 (Array) | 컬렉션 (Collection) |
---|---|---|
크기 | 고정 크기 | 동적 크기 |
데이터 타입 | 동일한 데이터 타입의 요소만 저장 가능 | 다양한 데이터 타입의 객체 저장 가능 |
메모리 구조 | 연속된 메모리 공간에 저장 | 비연속적인 메모리 공간에 저장 가능 |
기능성 | 제한적 (추가/삭제/정렬 등의 기능 없음) | 추가, 삭제, 검색, 정렬 등 고급 기능 제공 |
성능 | 빠름 (특히 인덱스 기반 접근) | 성능은 구현체에 따라 다름 (HashMap 등은 빠름) |
사용 목적 | 고정된 크기의 단순 데이터 저장 | 동적으로 크기가 변하는 데이터 저장 및 관리 |
사용 예 | 정수 배열, 문자 배열 등 | 리스트, 셋, 맵 등 |
사용 상황 | 배열 (Array) | 컬렉션 (Collection) |
---|---|---|
데이터 크기가 고정 | 배열이 적합 | 불필요하게 메모리를 낭비할 수 있음 |
데이터 크기가 유동적 | 부적합 | 크기가 동적으로 조정되는 컬렉션이 적합 |
단순 데이터 저장 및 접근 | 배열이 적합 | 컬렉션의 오버헤드가 불필요 |
데이터 추가, 삭제, 정렬, 검색 등의 작업 필요 | 구현하기 어려움 | 컬렉션이 적합 (이미 기능이 구현되어 있음) |
성능이 매우 중요한 경우 (고정 크기) | 배열이 적합 (연속된 메모리, 인덱스 접근 빠름) | 컬렉션은 약간의 성능 오버헤드가 발생 가능 |
정적 배열(Static Array)
정적 배열(Dynamic Array)
특징 | 정적 배열 (Static Array) | 동적 배열 (Dynamic Array) |
---|---|---|
크기 결정 시점 | 컴파일 시점 | 런타임 시점 |
메모리 할당 위치 | 스택(Stack) 또는 데이터 영역 | 힙(Heap) |
크기 조정 | 불가능 | 가능 |
성능 | 빠름 (메모리가 연속적으로 할당됨) | 약간 느림 (동적 메모리 할당 및 복사 필요) |
메모리 관리 | 자동 (스택 메모리는 함수 종료 시 해제됨) | 명시적으로 해제해야 함 (delete 또는 free) |
유연성 | 낮음 | 높음 |
ArrayList
LinkedList
ArrayList 와 LinkedList 의 차이
특징 | ArrayList | LinkedList |
---|---|---|
내부 구현 방식 | 동적 배열 | 이중 연결 리스트 |
데이터 저장 방식 | 연속된 메모리 블록 | 분리된 노드(각 노드는 데이터와 참조를 포함) |
인덱스 접근 속도 | 빠름 (O(1), 배열 인덱스 접근) | 느림 (O(n), 순차적으로 노드 탐색 필요) |
삽입/삭제 (중간 위치) | 느림 (O(n), 요소 이동 필요) | 빠름 (O(1), 노드 참조만 변경) |
삽입/삭제 (끝 위치) | 빠름 (O(1), 크기 초과 시 O(n)) | 빠름 (O(1)) |
메모리 사용량 | 적음 (배열 기반, 추가 메모리 오버헤드 적음) | 많음 (노드별 추가 참조 필드 및 메모리 할당) |
두 List의 성능 차이
연산 | ArrayList | LinkedList |
---|---|---|
인덱스 접근 | 빠름 (O(1)) | 느림 (O(n)) |
삽입 (끝) | 빠름 (O(1), 동적 확장 시 O(n)) | 빠름 (O(1)) |
삽입 (중간) | 느림 (O(n)) | 느림 (O(n), 탐색 포함) |
삭제 (끝) | 빠름 (O(1)) | 빠름 (O(1)) |
삭제 (중간) | 느림 (O(n)) | 느림 (O(n), 탐색 포함) |
메모리 효율성 | 높음 | 낮음 |
제네릭스(Generics)
ArrayList<String> list = new ArrayList<>();
<> 가 제네릭스
타입 | 설명 |
---|---|
<T> | 타입(Type) |
<E> | 요소(Element), ex) List<E> |
<K> | 키(Key), ex) Map<K, V> |
<V> | 리턴 값 또는 매핑된 값(Value), ex) Map<K, V> |
<N> | 숫자(Number) |
<S, U, V> | 2번째, 3번째, 4번째에 선언된 타입 |
//제네릭스 없을 때
ArrayList list = new ArrayList(); // Raw 타입
list.add("Hello");
String str = (String) list.get(0); // 명시적 캐스팅 필요
//제네릭스 사용할 때
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // 캐스팅 필요 없음
래퍼 클래스(Wrapper Class)
기본 타입 | 래퍼 클래스 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
**가비지 컬렉션(Garbage Collection)
박싱(Boxing)
int a = 10;
Integer boxed = Integer.valueOf(a); // 박싱
언박싱(Unboxing)
Integer boxed = 20;
int unboxed = boxed.intValue(); // 언박싱
오토박싱, 오토언박싱
int a = 10;
Integer boxed = a; // 오토박싱
int unboxed = boxed; // 오토언박싱
박싱과 언박싱이 성능에 미치는 영향
박싱/언박싱 사용시 주의할점
1. 성능에 민감한 작업에는 남용 금지 : 계산량이 많은 작업의 경우 박싱/언박싱으로 인해 성능저하 발생 가능
2. 제네릭과 래퍼 클래스의 조합 : 제네릭 사용시 박싱/언박싱이 자동 발생하므로 성능에 영향을 미칠 수 있음을 인지해야함
불변 객체(Immutable Objects)
사용 이유
1. 안전성 : 멀티스레드 환경에서 동기화 없이도 안전하게 공유 가능
2. 변경 불가 : 객체 상태가 변경되지 않아 예상하지 못한 부작용을 방지
3. 캐싱 가능 : 동일한 값을 가진 객체를 재사용할 수 있어 메모리와 성능 최적화
4. 디버깅 용이 : 객체의 상태가 변하지 않으므로 추적과 디버깅이 쉬움
장점 | 단점 |
---|---|
안전성 | 변경되지 않으므로 스레드 간 안전하게 공유 가능. |
예측 가능성 | 상태 변경이 불가능하므로 코드의 동작이 예측 가능. |
변경 추적 | 상태가 변경되지 않아 디버깅과 문제 원인 추적이 쉬움. |
성능 최적화 | 캐싱 및 공유를 통해 동일한 객체를 재사용 가능. |