

목 차
1-1. 클래스 란?
1-2. 객체 란?
1-3. 인스턴스 란?
2-1. 필드
2-2 생성자
2-3 메소드
번외)
3-1. 생성자 오버로딩
3-2. 메소드 오버로딩
5. final 필드와 상수
6. 접근 제한자
7. Getter, Setter
8. 오버라이딩(Overriding)
10-1 캡슐화 (Encapsulation)
10-2 상속 (Inheritance)
10-3 다형성(Polymorphism)
10-4 추상화(Abstraction)
11-1 SRP 단일 책임 원칙 (Single Responsibility Principle)
11-2 OCP 개방 폐쇄 원칙 (Open-Closed Principle)
11-3 LSP 리스코프 치환 원칙 (Liskov Substitution Principle)
11-4 ISP 인터페이스 분리 원칙 (Interface Segregation Principle)
11-5 DIP 의존성 역전의 원칙 (Dependency Inversion Principle)
12-1 얕은 복사 (Shallow Copy)
12-2 깊은 복사 (Deep Copy)
13-1 메서드(Method) 영역
13-2 힙(Heap) 영역
13-3 스택(Stack)영역
13-4 PC 레지스터(Program Counter Register)
13-5 네이티브 메서드 스택 (Native Method Stack)

자바에서 클래스는 데이터와 행동을 캡슐화한 사용자 정의 타입(User-defined Type) 입니다.
클래스는 객체의 속성(Field)과 동작(Method)을 정의하며, 객체를 생성하기 위한 청사진(설계도) 역할을 합니다.

도메인 로직을 캡슐화하는 기본 단위
계층 아키텍처의 구성요소 (Controller, Service, Entity, DTO 등)로 클래스를 나누어 설계
SOLID 원칙, 객체지향 설계 원칙을 적용하는 기본 단위

| 용도 | 예시 |
|---|---|
| 도메인 모델 | User, Order, Product 등의 Entity |
| 기능 수행 로직 | UserService, OrderProcessor |
| 입출력 구조 정의 | UserRequestDto, UserResponseDto |
| 컨트롤 계층 | UserController |
| 예외 구조화 | BusinessException, NotFoundException |

책임 단위를 분리하여, 유지보수를 용이하게 함
팀 내 협업 및 모듈화 기준 단위가 됨
적절한 추상화를 통해 코드 중복 방지, 재사용성 확보

User user = new User("andamiro", "dev@team.com");

애플리케이션의 실행 중 실질적으로 행동하는 주체
DB에서 읽어온 Entity 객체, 외부 API 결과로 생성된 DTO 객체 등
객체는 상태를 가지며, 해당 상태는 변경될 수 있음
→ 트랜잭션에서 이 변경은 중요한 의미를 가짐.

캡슐화를 지켜 적절한 메서드로 상태 변경 유도
객체의 생명주기를 고려 (Spring Bean 관리, 트랜잭션 범위)
불필요한 상태 공유를 막기 위해 불변 객체 패턴(Immutable Object)을 설계할 수 있음

User user = userRepository.findById(1L);
user.changeEmail("new@mail.com"); // 도메인 객체에게 책임 위임

인스턴스는 클래스에서 new 연산자를 통해 메모리에 실제로 생성된 객체를 말합니다.
즉, 클래스 → 객체 생성 → 메모리에 올라감 → 인스턴스라는 과정의 결과입니다.

| 측면 | 객체(Object) | 인스턴스(Instance) |
|---|---|---|
| 정의 | 클래스 기반 실체 | 객체가 메모리에 존재하는 상태 |
| 문맥 | 넓은 의미로 사용됨 | 클래스와의 관계 강조 시 사용 |
| 실무 사용 예 | "이 객체가 어떤 역할을 하나?" | "User 클래스의 인스턴스를 생성함" |

클래스는 메서드 영역(Method Area)에 올라가고
new 로 생성된 인스턴스는 힙(Heap) 영역에 위치하며,
참조 변수는 스택(Stack)에 위치
User u1 = new User(); // u1: Stack, new User(): Heap

“User는 클래스”
“user는 User의 인스턴스이자 객체”
코드에서 말할 땐 대부분 "객체"라 칭하지만, 설계나 JVM 메모리 이야기할 땐 "인스턴스"라 말함

| 용어 | 의미 | 실무에서의 예 |
|---|---|---|
| 클래스 | 설계도 | UserService, OrderController |
| 객체 | 클래스를 기반으로 생성된 코드 상의 실체 | new User(), responseDto |
| 인스턴스 | 객체가 메모리에 존재하는 상태 | User 인스턴스가 힙 메모리에 존재 |

// 설계도
public class User {
private String name;
public void changeName(String newName) { this.name = newName; }
}
// 실체 생성
User user = new User(); // 객체이자 인스턴스
user.changeName("andamiro");
🔑 실무 설계 팁.
DTO는 대부분 불변 객체로 생성
Entity는 트랜잭션 내에서 변경이 가능한 객체
Service는 상태 없는 객체 (Stateless)로 사용 (싱글톤 Bean)
자바에서 '필드(Field)'는 클래스 내부에서 선언되는 '객체의 상태(state)'를 저장하는 변수입니다.
즉, '객체의 속성(attribute)'으로서, 객체마다 개별적인 값을 가지게 됩니다.
{ 필드 = 클래스의 데이터 멤버 = 객체가 가지고 있는 속성 = 인스턴스 변수 } 라고 이해 가능 !!.
public class User {
private String name; // ← 이게 필드
private int age;
}

User user = new User("andamiro", 30);
'user'는 Stack 영역에 있는 참조 변수
'new User(...)'는 Heap 영역에 생성된 인스턴스.
그 안의 'name', 'age'라는 필드들은 해당 'Heap' 객체에 속한 상태 정보로 저장됨.

public class User {
private String name;
public String getName() {
return name;
}
public void changeName(String newName) {
this.name = newName;
}
}
외부에서 객체의 내부 상태를 '직접적으로 변경하지 못하게 하여서 일관성을 유지 ! '
객체 스스로만 자신의 상태를 바꾸도록 유도 -->> "도메인 중심 설계 가능"
상태 변경의 트리거를 '메서드에 집중' 시킬 수 있음 -->> 유효성 검사, 트랜잭션 추적 가능. !

public class UserDto {
private final String name;
private final int age;
public UserDto(String name, int age) {
this.name = name;
this.age = age;
}
}
JPA Entity 등은 상태 변경이 필요한 객체
트랜잭션 내에서 상태 변경 후, persistence context에 반영됨
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
public void changeName(String newName) {
this.name = newName;
}
}

| 필드 종류 | 설명 | 예시 |
|---|---|---|
| 인스턴스 필드 | 객체마다 개별적으로 존재 | private String name; |
| 정적(static) 필드 | 클래스 전체에서 공유 | private static int count; |
| final 필드 | 초기화 후 변경 불가 | private final String id; |

public class User {
public String name; // 직접 접근 가능 → 캡슐화 위반
}
public class UserDto {
private String name;
public void setName(String name) {
this.name = name;
}
}
실제 개발과정에서는 DTO의 'setter'가 무분별하게 열려 있으면, "어디서 상태가 변경되는지 추적하기 어렵고', 보안적으로도 문제가 될 수 있습니다.

| 실무 측면 | 설명 |
|---|---|
| 유지보수 | 필드를 잘 구조화하면 변경 영향도가 낮아짐 (loose coupling) |
| 테스트 | 필드가 불변이면 테스트 코드가 단순하고 예측 가능 |
| 협업 | 필드 네이밍이 명확해야 의도 파악 쉬움 |
| 보안 | 민감 데이터(예: 비밀번호)는 필드 단에서 접근 제한 필요 |

public class User {
private final String email;
private String nickname;
private boolean active;
public User(String email, String nickname) {
this.email = email;
this.nickname = nickname;
this.active = true;
}
public void deactivate() {
this.active = false;
}
public void changeNickname(String newNickname) {
if (newNickname.length() < 3) {
throw new IllegalArgumentException("닉네임은 3자 이상이어야 합니다.");
}
this.nickname = newNickname;
}
public String getEmail() { return email; }
public String getNickname() { return nickname; }
}
'email' : 불변 -->> 식별자.
'nickname' : 가변 -->> 유효성 검사를 갖춘 setter
'active' : 상태 관리 -->> 행동을 메서드로 유도.

'final 필드'는 '한 번 초기화되면 값을 변경할 수 없는 변수'.
'불변 객체(immutable Object)'를 만들 때 핵심 요소.
Lombok은 final 필드 또는 @NonNull 필드를 포함하는 생성자를 자동 생성해줌
→ @RequiredArgsConstructor
| 장점 | 설명 |
|---|---|
| 쓰레드 안전성 | 공유 객체가 상태를 바꾸지 않으므로 동기화 필요 없음 |
| 테스트 용이성 | 한 번 설정된 상태가 변하지 않음 (예측 가능) |
| 설계 명확성 | 어떤 값이 "초기화 후 불변"인지 코드로 드러남 |
| 의도 표현 | "이 필드는 생성자에서 반드시 초기화돼야 한다"는 의사소통 가능 |
public class UserDto {
private final String name;
private final int age;
public UserDto(String name, int age) {
this.name = name;
this.age = age;
}
}
@RequiredArgsConstructor
public class UserDto {
private final String name;
private final int age;
}
💡 주의: final만으로는 Bean 등록 시 불편할 수 있으니, 스프링 빈에서 사용하는 생성자는
@Autowired 또는 @RequiredArgsConstructor(onConstructor = @__(@Autowired)) 와 같이 쓰기도 함
값 객체(Value Object), 응답 DTO, Command 객체처럼 바뀌지 않아야 하는 객체에서
final 필드 사용은 설계 원칙을 표현하는 수단입니다.
특히 @Builder + @Getter + @RequiredArgsConstructor 조합은 깔끔하고 불변 설계에 적합 !

클래스 로딩 시 메모리에 한 번만 올라가며, '모든 인스턴스가 공유'
'인스턴스와 무관하게 전역 상태를 저장' 합니다.
| 항목 | 주의 내용 |
|---|---|
| 상태 공유 | 모든 객체가 공유하므로, 잘못된 상태 변경이 모든 인스턴스에 영향을 줌 |
| 테스트 어려움 | 테스트 간 전역 상태가 유지되기 때문에 독립성 깨짐 |
| GC 대상 제외 | 클래스가 살아있는 한 static은 GC 대상이 아님 → 메모리 누수 가능성 |
| DI 방해 | Spring에서 객체를 주입받지 않고 직접 static으로 접근하면 결합도 증가 |
public class UserService {
public static String globalName = "test";
}
public class JwtProperties {
public static final String SECRET = "JWT_SECRET_KEY";
}
public final class StringUtils {
public static boolean isEmpty(String str) { ... }
}

JVM은 객체를 생성할 때 '필드를 Heap에 배치' 하지만,
최적화 기법에 따라 'Escape Analysis'를 수행하여, 'Stack'에 올릴 수도 있습니다.
객체가 메서드 밖으로 "탈출"하는가? 를 분석하는 최적화 방법.
| 결과 | 설명 |
|---|---|
| Escape O | 다른 메서드나 쓰레드에서 참조 → Heap에 할당 |
| Escape X | 메서드 내부에서만 사용 → Stack에 할당 (Escape Analysis OK) |
public String createFullName(String first, String last) {
FullName name = new FullName(first, last); // Escape X → Stack 할당 가능
return name.toString();
}
.
public void saveUser() {
User user = new User(...);
userRepository.save(user); // Escape O → Heap 할당
}
GC(Garbage Collection) 비용 감소.
CPU 캐시 효율 향상 ( 메모리 locality 증가 )
Lock 제거 가능성 ( Lock Elision )
단! , 정확한 적용 여부는 JDK 버전, JVM 설정, JIT 여부에 따라 상이. !
- 확인 방법.
- JVM 옵션 -XX:+PrintEscapeAnalysis -XX:+DoEscapeAnalysis
- GraalVM, JMH 등을 통해 간접 테스트 가능

| 어노테이션 | 역할 |
|---|---|
@JsonIgnore | 해당 필드는 JSON 결과에 포함되지 않음 |
@JsonProperty("이름") | 필드명을 JSON에서 다르게 매핑 |
@JsonInclude(Include.NON_NULL) | null 값은 제외하고 직렬화 |
public class UserResponse {
private String name;
@JsonIgnore
private String password;
@JsonProperty("user_age")
private int age;
}
{
"name": "andamiro",
"user_age": 28
}
- password는 누락.
- age는 user_age로 표시.
| 케이스 | 설명 |
|---|---|
| 보안 | 비밀번호, 토큰, 인증 관련 필드 감춤 |
| API 규약 | snake_case 등 JSON 규칙에 맞춰 필드 이름 매핑 |
| 옵셔널 | 응답에 null 필드 제외 (특히 RESTful API에서 사용) |
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseDto {
private String data;
private String errorMessage; // null이면 응답에서 제외
}

public class User {
private final String name;
// 생성자
public User(String name) {
this.name = name;
}
}
메서드처럼 보이지만, '리턴 타입이 없음'
클래스 이름과 동일해야 함. !
new 키워드와 함께 호출 됨.
✅ 핵심 포인트: 생성자는 객체를 “무결한 상태”로 만들기 위한 진입점. !

public class User {
private final String email;
public User(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("유효한 이메일 주소여야 합니다.");
}
this.email = email;
}
}
-> 이런 식으로 '생성자에서 validation을 수행' 함으로써,
애초에 잘못된 객체가 태어나지 않도록 차단하는 것이 실제 코드 설계의 핵심입니다.

필드 초기화를 하지 않거나, JPA 등의 프레임워크에서 프록시 객체 생성을 위해 필요. !
User user = new User();
⚠️ 실제 개발 과정에서는 '기본 생성자를 노출하지 않거나, protected 처리' 하는 경우가 많음.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
...
}
@AllArgsConstructor
public class User {
private String name;
private int age;
}
'객체 생성' 시 필요한 최소한의 정보만 받도록 설계.
나머지 필드는 'setter' 혹은 '메서드'로 설정.
public class User {
private final String email;
private String nickname;
public User(String email) {
this.email = email;
}
public void changeNickname(String nickname) {
this.nickname = nickname;
}
}

자바는 생성자 오버로딩을 지원합니다.
public class User {
private final String name;
private int age;
public User(String name) {
this(name, 0);
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
생성자 내에서 'this(...)' 사용 -->> 중복 제거.
객체 생성 경로를 유연하게 구성. !

| 어노테이션 | 기능 |
|---|---|
@NoArgsConstructor | 기본 생성자 자동 생성 |
@AllArgsConstructor | 모든 필드 포함한 생성자 생성 |
@RequiredArgsConstructor | final 또는 @NonNull 필드만 포함한 생성자 생성 |
@RequiredArgsConstructor
public class User {
private final String email;
private String nickname;
}
-> 'new User("andamiro@example.com") ' 으로 객체 생성 가능.
💡 @RequiredArgsConstructor(onConstructor = @__(@Autowired)) : 스프링 DI에 사용

public : 어디서든 객체 생성 가능 ( API 레벨 노출 )
protected : 상속 또는 동일 패키지내에서만 생성 가능 -> "프레임워크용"
private : 외부에서 생성자 호출 금지 -> "정적 팩토리 메서드 패턴에 사용".
public class User {
private final String email;
private User(String email) {
this.email = email;
}
public static User fromEmail(String email) {
return new User(email);
}
}
✅ User user = User.fromEmail("andamiro@example.com");
생성자를 감추고 정적 메서드를 통해 의미 있는 생성 방식을 제공 ! ( 이름을 가질 수 있음 )
객체 생성을 '비즈니스 로직' 또는 '도메인 정책' 과 연결 가능 !

| 규칙 | 설명 |
|---|---|
| 기본 생성자 필요 | Hibernate가 리플렉션으로 객체 생성 시 사용 |
protected로 선언 | 외부에서 생성 막고, 프록시 생성 허용 |
| 비즈니스 로직 생성자 별도 제공 | 유효한 객체 생성을 보장하는 생성자 따로 정의 |
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
@Id @GeneratedValue
private Long id;
private String email;
public User(String email) {
this.email = email;
}
}

| 항목 | 생성자 | 정적 팩토리 메서드 |
|---|---|---|
| 이름 | 없음 | 있음 (of(), from(), with() 등) |
| 리턴 타입 | 클래스 자기 자신 | 유연함 (서브 타입 반환 가능) |
| 객체 생성 제어 | 불가능 | 가능 (캐싱, 싱글턴 등) |
| 사용 목적 | 단순한 생성 | 의미 있는 생성, 다양한 전략 |
실제 개발 환경에선, 스프링 내부 코드나 DDD 도메인 객체에서는 생성자보다는 정적 팩토리 메서드가 더 선호되기도 함. !

생성자는 객체의 '초기 상태 보증' 이므로, 테스트 대상 !
예외 상황까지 모두 테스트.
@Test
void createUser_withInvalidEmail_throwsException() {
assertThrows(IllegalArgumentException.class, () -> {
new User("invalid-email");
});
}

| 원칙 | 실무 적용 포인트 |
|---|---|
| 최소한의 불변 조건 확보 | 생성자에서 필수 값 검증 |
| 상태 불안정 방지 | setter 없는 설계 + 생성자에서 초기화 |
| 외부 공개 제한 | 정적 팩토리 메서드 패턴과 조합 |
| 테스트 가능성 | 잘못된 파라미터 시 예외 발생 보장 |
| JPA 호환 | @NoArgsConstructor(access = PROTECTED) 필수 |

public class User {
private String name;
public void changeName(String newName) {
this.name = newName;
}
public String getName() {
return this.name;
}
}

| 기준 | 설명 |
|---|---|
| SRP (단일 책임 원칙) | 하나의 메서드는 하나의 작업만 수행해야 한다 |
| 테스트 가능성 | 순수 함수(Pure Function)에 가까울수록 테스트 용이 |
| 재사용성 | 중복 제거를 위한 핵심 수단 |
| 가독성 | 이름, 길이, 책임 명확히 |
| 캡슐화 | 객체 상태 변경은 메서드를 통해서만 이루어져야 함 |

public int calculateDiscount(int price, double rate) {
return (int)(price * rate);
}
| 구성 요소 | 설명 |
|---|---|
| 접근 제어자 | public, private, protected, (default) |
| 반환 타입 | int, void, String, User 등 |
| 메서드 이름 | 동사 + 목적어 조합 추천 (saveUser, getName, changeStatus) |
| 매개변수 | 입력 값. 너무 많으면 DTO 객체로 묶는 것 고려 |
| 예외 선언 | throws 사용 가능 (예외는 컨트롤할 수 있어야 함) |

| 나쁜 예 | 좋은 예 |
|---|---|
handle() | sendWelcomeEmail() |
doTask() | processPayment() |
action() | changeOrderStatus() |
✔️ 이름만 보고 "무엇을 하는지" 유추 가능해야, 협업과 유지보수에서 유리합니다.
하나의 기능만 수행하도록 !
여러 기능이 있다면 내부 메서드로 분리.
public void registerUser(UserRequest request) {
validateEmail(request.getEmail());
User user = createUser(request);
userRepository.save(user);
sendWelcomeEmail(user);
}
-->> 각 로직은 책임이 분리된 메서드로 위임!.
// ❌ 너무 많은 파라미터
public void register(String email, String name, int age, String address, String phone) { ... }
// ✅ DTO로 감싸기
public void register(UserRegisterRequest request) { ... }
명세가 명확해지고, 파라미터 순서 실수 방지 가능
스프링에서는 '@valid'와 함께 사용 가능.

| 접근자 | 설명 |
|---|---|
private | 외부에 노출할 필요 없는 내부 구현 |
protected | 상속 관계에서만 사용 |
public | 외부 API로 노출됨. 신중하게 설계 필요 |
📌 캡슐화 : "객체의 상태를 보호하고 변화는 오직 메서드를 통해"
public class User {
private String password;
public void changePassword(String currentPw, String newPw) {
if (!this.password.equals(currentPw)) {
throw new IllegalArgumentException("비밀번호 불일치");
}
this.password = newPw;
}
}
-->> 필드 직접 변경은 막고, 메서드로 상태를 통제. !

public int getAge(); // 단순 타입
public OrderStatus changeStatus(OrderStatus newStatus); // 의미 있는 도메인 객체
public Optional<User> findById(Long id);
실무에서는 'null' 대신, 'Optional'을 반환함으로써, NPE(NullPointException)를 사전에 차단
클라이언트는 'isPresent()' 나 'orElseThrow()' 등을 통해 예외 처리.

public void processOrder(Order order) {
if (order.getStatus() == OrderStatus.CANCELLED) return;
order.setStatus(OrderStatus.SHIPPED);
orderRepository.save(order);
eventPublisher.publish(new OrderShippedEvent(order));
}
-->> 너무 많은 일을 처리 : 검증, 상태 변경, 저장, 이벤트 발행까지
public void processOrder(Order order) {
if (!canShip(order)) return;
ship(order);
publishEvent(order);
}

| 구분 | 설명 |
|---|---|
| 오버로딩 | 동일한 이름, 서로 다른 시그니처 (컴파일 타임 결정) |
| 오버라이딩 | 상속 관계에서 메서드 재정의 (런타임 다형성 활용) |
// 오버로딩
public void print(String msg) { ... }
public void print(int count) { ... }
// 오버라이딩
@Override
public void save(User user) { ... } // Repository에서 JPA 메서드 재정의

객체 간 협력에서 '가장 단위가 되는 요소'
단위 테스트의 주 대상이 됨.
@Test
void changeNickname_shouldUpdateCorrectly() {
User user = new User("andamiro@example.com");
user.changeNickname("다미로");
assertEquals("다미로", user.getNickname());
}

| 항목 | 실무 핵심 정리 |
|---|---|
| 메서드 역할 | 객체의 행동 정의 (상태 변경 또는 정보 조회) |
| 설계 기준 | SRP, 작고 명확한 책임, 캡슐화 유지 |
| 이름 규칙 | 동사 + 목적어 (changeStatus, getUserById) |
| DTO 활용 | 파라미터 많을 때 DTO로 캡슐화 |
| 반환값 | Optional, Enum, VO 등을 활용해 의미 부여 |
| 테스트 가능성 | 부작용 없는 메서드 = 테스트 용이 |




















