
객체를 만들기 위한 설계도
클래스로부터 만들어진 실체
new로 생성되며 일반적으로 Heap에 생김Panda p = new Panda();
p : 스택에 있는 참조값new Panda() : 힙에 있는 객체주의
p가 객체다p는 객체를 가리키는 리모컨(참조) 이다
int, long, double, boolean ...int a = 10;
int b = a;// b는 10 (완전 독립)
b = 20;
a는 여전히 10
String, 배열, 모든 클래스 인스턴스 등Panda p1 = new Panda();
Panda p2 = p1;// 참조값 복사
p1과 p2는 두 객체가 아니라 한 객체를 같이 참조.
→ 얕은/깊은 복사로 이어짐
Java는 무조건 Call By Value
왜 사람들이 참조 전달이라고 착각하나?
void rename(Panda p) {
p.name = "철수";
}
Panda panda = new Panda("영희");
rename(panda);
System.out.println(panda.name);// 철수
p는 panda와 같은 객체를 가리키는 참조값을 복사받았음void change(Panda p) {
p = new Panda("새 판다");// p가 가리키는 대상을 바꿈
}
Panda panda = new Panda("영희");
change(panda);
System.out.println(panda.name);// 영희 (그대로)
핵심
p가 다른 객체를 가리켜도panda의 참조값은 변하지 않음Java는 참조 타입도 주소값을 복사하는 Call By Value라서, 내부에서 재할당은 외부에 영향이 없다.

Student student = new Student("Kim", 111);
Student shallowCopy = student; // 얕은 복사 (사실상 참조 복사)
또는 객체를 새로 만들더라도 내부 참조를 그대로 복사하면 얕은 복사
Student student2 = new Student(student.name, student.number);// 내부 Student 공유
위험

Cloneable 구현

정의된 메서드는 없지만, Object.clone() 메서드를 반드시 구현하라고 설명되어 있음
public class Student implements Cloneable{
String name;
int number;
public Student(String name, int number) {
this.name = name;
this.number = number;
}
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student)super.clone();
}
}
복사 생성자 / 복사 팩토리 메서드
public class Student{
String name;
int number;
public Student(){}
//복사 생성자
public Student(Student original){
this.name = original.name;
this.number = original.number;
}
//복사 팩터리 메서드
public static Student copy(Student original){
Student student = new Student();
student.name = original.name;
student.number = original.number;
return student;
}
}
객체 생성 이후 내부 상태가 절대 변하지 않는 객체
특징
Thread-Safe (동기화 불필요)
멀티스레드 문제의 본질은 공유 자원에 대한 쓰기(write)
실패 원자성(Failure Atomicity)
가변 객체의 문제
불변 객체의 장점
→ 실패해도 객체는 항상 안전
Cache / Map / Set에 최적
Hash 기반 컬렉션의 핵심 조건
→ equals가 true면 hashCode는 반드시 같아야 한다.
가변 객체를 키로 쓰면
불변 객체는
→ String이 Map 키로 자주 쓰이는 이유
부수 효과(Side Effect) 제거
메서드 호출이 객체 상태를 몰래 변경하는 것
가변 객체 + setter
불변 객체
→ 유지보수성 향상
협업 시 안전성
불변 객체
가변 객체
→ 불변성은 팀 생산성에 직접적인 영향
GC 성능 향상
GC 설계 가정 중 하나 → 대부분의 객체는 금방 죽는다
value 객체 생성 (불변)
↓
ImmutableHolder 생성
↓
Holder → value 참조
Holder가 살아 있으면 value는 처음 상태 그대로 유지
public class MutableHolder {
private Object value;
public Object getValue() { return value; }
public void setValue(Object o) { value = o; }
}
public class ImmutableHolder {
private final Object value;
public ImmutableHolder(Object o) { value = o; }
public Object getValue() { return value; }
}
public void createHolder() {
// 1. Object 타입의 value 객체 생성
final String value = "MangKyu";
// 2. Immutable 생성 및 값 참조
final ImmutableHolder holder = new ImmutableHolder(value);
}
GC 입장에서
MutableHolder의 문제
→ GC 비용 상승
String s = "a";
s = s + "b";
여기서 "ab"가 만들어질 때:
"a" 객체가 바뀌는 게 아니라장점
단점

String a = "hi";
String b = "hi";
System.out.println(a == b);// 대부분 true
리터럴 "hi"는 String Pool에 들어가고 재사용됨
반면
String c = new String("hi");
System.out.println(a == c);// false
System.out.println(a.equals(c));// true
== : 참조 비교equals : 값 비교intern()
String x = new String("hi").intern();

StringBuilder
StringBuffer
주의
StringBuilder 쓰면 됨StringBufferClass<?> clazz = Class.forName("com.example.Panda");
Object obj = clazz.getDeclaredConstructor().newInstance();
어디서 쓰이나?
단점
Retention
Target

클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법
제네릭 타입 전파

부분에서 실행부에서 타입을 받아와 내부에서 T타입으로 지정한 멤버들에게 전파하여 타입이 구체적으로 설정됨.
→ 구체화(Specialization)
타입 파라미터 기호 네이밍
암묵적 convention

장점
주의
컴파일러는 제네릭 타입을 이용해서 소스 파일을 체크하고
개발자가 지정한 코드에 따라 필요한 곳에 형변환을 넣어주고
→ 최종적으로 컴파일 코드에 Type Erasure로 제네릭 타입을 제거
왜 이렇게 설계?
equals()는 두 객체가 논리적으로 같은 값인가를 비교하기 위한 메서드
String a = new String("hi");
String b = new String("hi");
a == b// false (참조 비교)
a.equals(b)// true (값 비교)
왜 == 말고 equals를 써야 하나?
== : 주소(참조) 비교equals : 객체의 의미(값) 비교hashCode()는 객체를 정수 값으로 요약한 값
주 목적은 Hash 기반 컬렉션의 성능 최적화
예
HashMapHashSet핵심 계약
equals가 true인 두 객체는
반드시 같은 hashCode를 가져야 한다
반대는 아님
(hashCode가 같아도 equals는 false일 수 있음)
map.put(key, value);
내부 흐름
key.hashCode() 호출 → 버킷 선택equals()로 실제 키 비교만약
→ 같은 키인데 다른 버킷에 들어가서 조회 실패
불변 객체의 특징
가변 객체를 키로 쓰면?
key를 put 한 뒤
→ 내부 필드 변경
→ hashCode 변경
→ map에서 못 찾음
HashMap/HashSet 키는 불변 객체가 최선
Primitive 타입을 객체로 감싼 클래스
| primitive | wrapper |
|---|---|
| int | Integer |
| long | Long |
| boolean | Boolean |
Integer a = 10;// auto boxing
int b = a;// auto unboxing
컴파일러가 자동 변환 코드를 넣어준다.
Integer a = 100;
Integer b = 100;
a == b// true
Integer c = 1000;
Integer d = 1000;
c == d// false
이유
→ 캐시 범위는 JVM 옵션으로 변경 가능
for (int i = 0; i < 1_000_000; i++) {
Integer x = i;// boxing
}
→ 핫 루프에서는 primitive 사용 권장