Java 내부 구조 이해
Java 메모리 구조
stack 영역
- 메서드 호출 정보, 지역변수, 기본형 변수
- 스레드마다 별도의 스택영역을 가짐.
Heap 영역
- 객체와 배열이 저장되는 공간
- 여러 스레드가 공유하는 영역
- 가비지 컬렉터가 활동하는 영역
자바와 JVM
JVM : 자바를 실행하기 위한 가상머신
Java 어플리케이션은 JVM위에서 동작하기 때문에 OS에 종속적이지 않음.
클래스 로더
- .class 파일을 로드하고, jar 파일 내 저장된 클래스를 JVM의 Metaspace(Heap 외부)에 탑재하는 역할
실행엔진
- 인터프리터와 JIT으로 클래스를 실행
- 가비지 컬렉터로 사용하지 않는 메모리를 찾아 삭제
RunTime Data Area
- 프로그램 수행을 위해 OS에서 할당 받은 메모리 공간
자바 변수와 자료형
기본형과 참조형 래퍼 클래스
- boxing, unboxing
public class NumberBox {
public static void main(String[] args) {
int a = 100;
int b = 100;
Integer A = 100;
Integer B = 100;
// 기본형 비교 (값 비교)
System.out.println("a == b: " + (a == b));
// 래퍼 클래스 비교 (주소 비교)
System.out.println("A == B: " + (A == B));
// 래퍼 클래스 equals()로 비교 (값 비교)
System.out.println("A.equals(B): " + A.equals(B));
}
}
⇒ Integer cache : -128 ~ 127 범위의 Integer는 JVM이 미리 만들어둔 객체를 재사용
따라서 모두 True가 반환됨.
Synchronized
멀티 쓰레드 환경에서 동기화를 보장하기 위한 기능
공유 자원에 여러 쓰레드가 동시에 접근할때 데이터 불일치를 차단
객체 지향 프로그래밍
좋은 설계
높은 응집도 + 낮은 결합도
책임주도설계
객체지향설계에서 무엇을 할 것인가(책임)를 먼저 정하고, 그 책임을 수행할 객체를 찾는 방식의 설계 방법론
객체지향프로그래밍
시스템을 역할과 책임 중심으로 객체로 모델링하여, 높은 응집도와 낮은 결합도를 통해 유지보수성과 확장성을 향상시키는 프로그래밍
static 클래스, 변수, 메소드
static 키워드는 객체가 아니라 클래스 자체에 있는 변수나 메소드를 정의할때 사용
객체지향 프로그래밍의 특징
- 캡슐화 객체의 속성을 외부에서 직접 접근하지 못하도록 숨기고, 공개된 메서드를 통해서만 간접적으로 접근하도록 하는것
- 상속 부모 클래스의 속성과 메서드를 자식 클래스가 물려받아 사용하는 객체지향 개념 extends 키워드
- 추상화 복잡한 시스템에서 핵심적인 개념만 추출, 불필요한 세부사항은 숨기는것. abstract class, interface 를 사용하여 구현
- 다형성(메서드 오버로딩, 메서드 오버라이딩) 하나의 타입으로 여러 형태의 동작을 표현할 수 있는 능력
*업캐스팅 how, what을 분리하여 구현하도록 도와줌, interface 중심으로 더 자주 활용
추상화 : 인터페이스 이해하기
인터페이스의 구조 메서드의 (이름, 매개변수, 반환형)만 선언하고, 실제 구현은 인터페이스를 구현한 클래스가 담당. 기본적으로 인터페이스의 메서드는 public 단, 필드는 모든 구현체가 공유하는 불변상수만 허용되기 때문에, public static final이 자동 할당됨.
*interface , implements 키워드 사용
@Override ⇒ 컴파일러 체크 기능, 가독성향상, 리팩토링 안정성
상속
접근제한자의 종류

다중상속
자바는 다중상속을 지원하지 않지만, Interface를 활용하여 다중상속을 지원한다.
추상화 : 추상클래스
객체를 직접 생성할 수 없는 클래스, 주로 공통 기능을 정의하고 하위 클래스에서 구현하도록 강제. 최소 하나의 추상메소드를 포함하고 있으면 추상클래스.
보통 하위 클래스에는 상태를 공유하고 외부에는 숨기기 위해서 추상클래스의 필드는 protected로 접근제한자를 두어 설계함.
*추상클래스와 인터페이스
추상클래스 : 공통된 상태 + 기본 동작을 공유하기 위한 상속 구조
인터페이스 : “역할” 을 정의하기 위한 “규약”
다형성
하나의 타입으로 여러 형태의 객체를 다룰 수 있는 능력.
동일한 메서드 호출이 실행시점에 실제 객체 타입에 다라 다르게 동작.
- 오버라이딩 부모 클래스의 메소드를 자식 클래스가 동일한 형태로 또다시 구현하는 행위
- 오버로딩 입력 파라미터가 다른 경우에는 동일한 이름으로 메소드를 만들 수 있음.
Collection Type
- List
순서가 있는 데이터를 저장하는 것이 목적, 구현체로는
1) ArrayList 조회가 빈번한 경우 자주 사용
2) Vector 동기화와 멀티스레드 환경 지원
3) LinkedList 삽입/삭제가 빈번한 경우 자주 사용
주요 메소드 size(), add(), get() → forEach문 으로 사용 , iterator(), remove() - Set
중복이 허용되지 않는 데이터를 관리
1) HashSet 빠른 검색/삽입/삭제가 필요할 때 사용, 가장 빠른 일반 Set
2) LinkedHashSet 삽입 순서 유지 필요시 사용
3) TreeSet 정렬이 필요하다면 사용. - Queue
데이터의 in-out을 FIFO 방식으로 관리 offer() → 요소추가, poll() → 첫요소제거후반환
*Deque : 양쪽에서 넣고 양쪽에서 빼는 구조
1) ArrayDeque (Deque) : 속도가 가장 빠름
2) LinkedList(Queue, Deque) : 검색가능, 중간 삽입/삭제 가능
3) PriorityQueue (Queue): 오름차순또는 내림차순 순서변경
4) ConcurrentLinkedQueue (Queue) : 고성능 비차단 큐
5) ConcurrentLinkedDeque (Deque): 멀티스레드 Duque - Stack
데이터의 in-out을 LIFO방식으로 관리 push(), pop()
1) ArrayDeque : 속도가 가장 빠름
2) Stack : Tread-safe - Map
키와 값 쌍을 저장하는 데이터 구조 구현체로는 HashMap, LinkedHashMap, TreeMap 등이 있음.
put(k,v), get(k), remove(k),,,
Collection<E> Java Collection에서 제공하는 최상위 인터페이스 add, remove, contains와 같은 공통기능이 정의되어 있음.
Generic Type
제네릭
타입별로 중복 코드를 작성하는 것을 막아서 코드 재사용성을 높이는 것을 목표로 함.
관례적으로 T를 많이 사용.
제네릭타입의 제한
상한 제한 : extends
하한 제한 : super
제네릭 와일드 카드 : <? extends T>
이미 요소가 채워진 리스트를 받아서 읽기 전용으로 사용하기 위한 타입
? extends T : T의 하위 타입 중 하나
제네릭 와일드 카드 : <? super T>
이미 T 타입 이상의 상위 타입을 받도록 하여 리스트에 값을 넣기 위한 타입
? super T : T의 상위 타입 중 하나
static method에 제네릭 타입
static 메서드는 인스턴스와 무관하기 때문에, 클래스 제네릭 타입을 쓸 수 없고, 메서드 레벨에서 별도의 제네릭 타입 선언.
<T> 의 위치가 메서드 앞에 와야 함.
Lambda Expression
함수형 프로그램
함수를 일급 객체로 취급하여, 불변성과 순수 함수를 기반으로 프로그램을 작성하는 방식.
일급 객체 → 함수 자체를 데이터처럼 다루는 방식
불변성 → 함수 내부에서 외부 변수를 변경하지 않는 구조
순수함수 → 같은입력으로 같은 출력을 반환
선언형스타일 → 어떻게가 아닌, 무엇을 할지에 집중
Java는 원래 객체 지향 중심 언어이기 때문에 함수를 값처럼 전달하는 방식이 어려워, 익명 클래스를 사용하여 함수처럼 구현하기 시작.
⇒ 그런데, 코드가 장황하고 가독성이 감소하는 문제가 발생 ⇒ 람다표현식
람다표현식
익명 함수를 간결하게 표현하기 위한 문법, 함수형 인터페이스를 인스턴스로 생성하는 짧은 방식

함수형인터페이스(@FunctionalInterface)
단 하나의 추상 메서드만을 가지는 인터페이스
람다 표현식이 바로 이 함수형 인터페이스의 익명 구현 객체로 생성
*람다는 개념적으로는 함수처럼 사용되지만, Java에서는 함수형 인터페이스의 인스턴스로 표현되는객체임.
@FunctionalInterface
interface Calculator{
int operate(int a, int b);
}
public class LambdaExample{
public class static void main(String[] args){
Calculator add = (a,b) -> a+b;
Calculator multiply = (a,b) -> a*b;
System.out.println("덧셈: " + add.operate(10, 5));
System.out.println("곱셈: " + multiply.operate(10, 5));
}
}
대표적인 함수형 인터페이스
ex) Runnable, Predicate<T>, Function<T,R>, Comparator<T>
메서드 참조
클래스명(or 객체명) :: 메서드 명
*람다식과 익명클래스는 외부 변수를 사용할 때, 자신이 생성된 환경의 변수를 캡처하여 사용한다. 그 변수는 반드시 final이거나 effectively final 이어야함.
⇒ 지역변수는 스택에 있어, 메서드가 끝나면 사라지는데, 람다는 그 변수의 값을 복사해서 들고 다님.
상속 vs 함수형 인터페이스
*함수형 인터페이스를 인자로 교체하면, 런타임에 행동을 유연하게 교체할 수 있어 확장성에 유리한 구조가 됨.
Stream API
컬렉션의 요소들을 선언적이로 함수형 스타일로 처리할 수 있도록 지원하는 API
⇒ 일종의 데이터 처리 파이프라인, 어떻게 할지 구현 하는게 아닌, 무엇을 할지만 표현
- stream 생성 list.stream();
- 중간연산 stream을 반환함, 지연실행됨. filter(Predicate <T>), map(Function<T,R>), sorted(), limit(), distinct()
- 최종연산 stream 종료, 실제연산수행 forEach(), collect(), reduct(), count(), anyMatch(), …
Reflection
Runtime 시점에 클래스에 대한 정보를 검색해 클래스 또는 객체를 조작할 수 있도록 지원
- 컴파일 시점이 아니라, 런타임에 타입을 조작
- private 포함 모든 속성/멤버 접근
⇒ 프레임워크 내부에서만 사용
⇒ JVM이 메타데이터 저장소(메타스페이스)에 올려놓은 클래스의 구조를 읽어오고 조작할 수 있는 기능.
⇒ 어노테이션, 객체생성, DI
하지만, 성능저하, 캡슐화파괴, 런타임오류 증가로 일반적으로는 사용X
Annotation
클래스, 메소드, 필드 등에 부가 정보를 붙여 그 코드가 어떻게 처리되어야 하는지를 외부시스템이 결정하도록 하는 선언적 메커니즘. ⇒ 주석x, 코드가 읽고 동작을 바꾸기 위한 정보.
- @interface 새로운 Annotation을 만들 때 @interface 키워드 사용 메서드 모양으로 속성을 정의한다.
- @Retention, @Target @Retention : 언제까지 살아남는지 정의 ex) SOURCE, CLASS, RUNTIME @Target : 어디에 붙일 것인지 정의
Target(ElementType.METHOD) // 메서드에만 사용
@Retention(RetentionPolicy.RUNTIME)
public @interface RunMe { }
객체 생성 패턴
- Singleton
단 하나의 인스턴스만 생성, 공유
전역에서 하나의 객체만 필요할 때 - Factory Method
객체 생성을 하위 클래스에서 담당
상위 클래스는 인터페이스만 제공, 하위 클래스가 생성 결정 - Abstract Factory
관련 객체 집합을 생성
객체 계열을 통일된 방식으로 생성 - Builder
복잡한 객체의 생성 과정 분리
객체의 생성 절차를 단계별로 분리 및 체이닝 지원
SOLID 원칙
유지보수성, 확장성, 재사용성을 높이고 결합도를 낮추기 위한 OOP의 5대 설계 원칙

































































