본문으로 건너뛰기

6.1 클래스와 객체 (Classes and Objects)

자바는 진정한 객체지향(Object-Oriented Programming, OOP) 언어입니다. 현실 세계의 사물이나 개념을 컴퓨터 프로그램에 '객체'라는 형태로 모델링하는 것이 객체지향의 핵심입니다.

1. 절차적 프로그래밍 vs 객체지향 프로그래밍​

OOP를 이해하기 위해, 먼저 이전 패러다임인 절차적 프로그래밍(Procedural Programming) 과의 차이를 살펴보겠습니다.

구분절차적 프로그래밍객체지향 프로그래밍
중심함수(기능) 중심객체(데이터 + 기능) 중심
구조위에서 아래로 순서대로 실행객체들이 서로 메시지를 주고받음
재사용함수 단위 재사용클래스/객체 단위 재사용
유지보수규모가 커지면 복잡도 급증모듈화로 변경 영향 최소화
대표 언어C, Pascal, FORTRANJava, C++, Python, C#

절차적 프로그래밍은 작은 프로그램에는 적합하지만, 수만 줄이 넘는 대형 프로젝트에서는 코드 간 의존성이 폭발적으로 증가하여 유지보수가 어렵습니다. OOP는 이 문제를 해결하기 위해 탄생했습니다.

2. OOP 4대 원칙 개요​

객체지향 설계는 다음 4가지 핵심 원칙 위에 세워집니다.

  1. 캡슐화 (Encapsulation): 데이터와 메서드를 하나로 묶고 외부 접근을 제한하여 데이터를 보호합니다.
  2. 상속 (Inheritance): 기존 클래스의 속성과 기능을 물려받아 코드를 재사용하고 확장합니다.
  3. 다형성 (Polymorphism): 동일한 코드가 객체의 실제 타입에 따라 다르게 동작합니다.
  4. 추상화 (Abstraction): 복잡한 내부 구현을 숨기고 핵심 인터페이스만 노출합니다.

이 원칙들을 통해 코드의 재사용성, 유연성, 유지보수성 을 극대화할 수 있습니다.

3. 클래스(Class)와 객체(Object)의 정의​

클래스 (Class) — 붕어빵 틀​

  • 정의: 객체를 만들기 위한 설계도 또는 템플릿
  • 특징: 메모리에 올라가지 않음 (인스턴스화 전까지는 실체 없음)
  • 비유: 붕어빵 틀, TV 설계 도면, 자동차 청사진

객체 (Object / Instance) — 실제 붕어빵​

  • 정의: 클래스로부터 생성된 실체로, 메모리(힙)에 실제로 할당된 것
  • 특징: 각각 독립적인 상태(데이터)를 가짐
  • 비유: 틀로 찍어낸 팥붕어빵, 슈크림붕어빵 (같은 틀이지만 각각 다른 실체)
인스턴스화(Instantiate)

클래스로부터 객체를 만드는 과정을 인스턴스화 라고 하며, 만들어진 객체를 그 클래스의 인스턴스(instance) 라고 합니다. "인스턴스"와 "객체"는 거의 같은 의미로 사용됩니다.

4. 클래스 선언 문법​

클래스는 속성(필드) 과 기능(메서드) 으로 구성됩니다.

// 접근제어자 class 클래스명 {
// 타입 필드명; // 멤버 변수(속성)
// 반환타입 메서드명() { ... } // 메서드(기능)
// }

public class Car {
// === 멤버 변수(필드) ===
String brand; // 브랜드
String color; // 색상
int speed; // 현재 속도
int fuel; // 연료량

// === 메서드(기능) ===
void accelerate(int amount) {
speed += amount;
fuel -= amount / 10;
System.out.println("가속! 현재 속도: " + speed + "km/h");
}

void brake() {
if (speed > 0) {
speed -= 20;
if (speed < 0) speed = 0;
}
System.out.println("제동! 현재 속도: " + speed + "km/h");
}

void displayInfo() {
System.out.println("=== 차량 정보 ===");
System.out.println("브랜드: " + brand);
System.out.println("색상: " + color);
System.out.println("속도: " + speed + "km/h");
System.out.println("연료: " + fuel + "L");
}
}

5. 객체 생성: new 연산자와 힙 메모리​

객체는 new 연산자를 사용하여 생성합니다. new는 힙(Heap) 메모리 에 객체를 위한 공간을 할당하고, 그 주소를 반환합니다.

// 1단계: 참조 변수 선언 (스택 메모리에 변수 공간만 생성)
Car myCar;

// 2단계: 객체 생성 후 주소를 참조 변수에 저장
// (힙 메모리에 Car 객체 공간 할당)
myCar = new Car();

// 한 줄로 선언과 생성을 동시에
Car yourCar = new Car();
스택과 힙 메모리
  • 스택(Stack): 참조 변수(주소값)가 저장되는 공간. 메서드 호출 시 생성되고 종료 시 자동 소멸.
  • 힙(Heap): 실제 객체(데이터)가 저장되는 공간. new로 생성되고 GC(가비지 컬렉터)가 청소.

6. 참조 변수와 객체의 관계​

참조 변수는 객체 자체가 아니라, 객체가 있는 힙 메모리 주소 를 저장합니다.

public class ReferenceExample {
public static void main(String[] args) {
Car car1 = new Car(); // 객체 A 생성
Car car2 = new Car(); // 객체 B 생성
Car car3 = car1; // car3는 car1과 같은 객체 A를 참조!

car1.brand = "현대";
car3.brand = "기아"; // car3 == car1이므로 car1의 brand도 "기아"가 됨

System.out.println(car1.brand); // 기아 (같은 객체이므로!)
System.out.println(car3.brand); // 기아

// 독립적인 car2
car2.brand = "BMW";
System.out.println(car2.brand); // BMW
}
}
참조 복사 vs 값 복사

car3 = car1은 객체를 복사하는 것이 아니라 주소(참조)를 복사 합니다. 따라서 두 변수가 같은 객체를 가리키게 됩니다. 이 점을 반드시 기억하세요!

7. 객체 비교: == vs equals()​

public class CompareExample {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = car1;

car1.brand = "현대";
car2.brand = "현대"; // 내용은 같지만 다른 객체

// == 연산자: 참조(주소)를 비교
System.out.println(car1 == car2); // false (다른 객체)
System.out.println(car1 == car3); // true (같은 객체 참조)

// String은 equals()로 내용 비교
String s1 = new String("자바");
String s2 = new String("자바");
System.out.println(s1 == s2); // false (다른 객체)
System.out.println(s1.equals(s2)); // true (내용이 같음)
}
}
비교 방법대상결과
==참조(주소값)같은 객체이면 true
equals()내용(데이터)내용이 같으면 true (오버라이딩 필요)

8. 멤버 변수(필드) vs 지역 변수​

구분멤버 변수(필드)지역 변수
선언 위치클래스 내부 (메서드 외부)메서드, 생성자, 블록 내부
생성 시점객체 생성 시선언문 실행 시
소멸 시점객체가 GC에 의해 소멸 시블록({}) 종료 시
기본값타입별 기본값 자동 초기화없음 (반드시 직접 초기화)
저장 위치힙(Heap)스택(Stack)
public class VariableExample {
// 멤버 변수 (자동으로 기본값 초기화됨)
int memberInt; // 0
double memberDouble; // 0.0
boolean memberBool; // false
String memberStr; // null

void showDefaultValues() {
System.out.println("int 기본값: " + memberInt);
System.out.println("double 기본값: " + memberDouble);
System.out.println("boolean 기본값: " + memberBool);
System.out.println("String 기본값: " + memberStr);
}

void localVarExample() {
int localVar; // 선언만
// System.out.println(localVar); // 컴파일 에러! 초기화되지 않은 지역 변수 사용 불가
localVar = 10; // 초기화 후 사용 가능
System.out.println("지역 변수: " + localVar);
}
}

9. 실전 예제: Car 클래스 완전 구현​

실제로 사용 가능한 Car 클래스를 설계해봅니다.

public class Car {
// 멤버 변수 (필드)
String brand;
String model;
String color;
int speed; // 현재 속도 (km/h)
int fuel; // 연료량 (L)
boolean isOn; // 시동 여부

// 시동 켜기
void start() {
if (isOn) {
System.out.println(brand + " " + model + ": 이미 시동이 켜져 있습니다.");
return;
}
if (fuel <= 0) {
System.out.println("연료가 없어 시동을 걸 수 없습니다!");
return;
}
isOn = true;
System.out.println(brand + " " + model + ": 부릉부릉~ 시동이 켜졌습니다!");
}

// 시동 끄기
void stop() {
if (!isOn) {
System.out.println("시동이 이미 꺼져 있습니다.");
return;
}
isOn = false;
speed = 0;
System.out.println(brand + " " + model + ": 시동이 꺼졌습니다.");
}

// 가속
void accelerate(int amount) {
if (!isOn) {
System.out.println("시동을 먼저 켜세요!");
return;
}
speed += amount;
fuel -= amount / 10;
System.out.printf("가속 +%dkm/h → 현재 속도: %dkm/h, 연료: %dL%n",
amount, speed, fuel);
}

// 제동
void brake(int amount) {
speed = Math.max(0, speed - amount);
System.out.printf("제동 -%dkm/h → 현재 속도: %dkm/h%n", amount, speed);
}

// 연료 주입
void refuel(int amount) {
fuel += amount;
System.out.println(amount + "L 주유 완료. 총 연료: " + fuel + "L");
}

// 상태 출력
void displayStatus() {
System.out.println("============================");
System.out.println("차량: " + brand + " " + model);
System.out.println("색상: " + color);
System.out.println("시동: " + (isOn ? "ON" : "OFF"));
System.out.println("속도: " + speed + " km/h");
System.out.println("연료: " + fuel + " L");
System.out.println("============================");
}
}

// 실행 클래스
public class CarTest {
public static void main(String[] args) {
// 객체 생성
Car sonata = new Car();
sonata.brand = "현대";
sonata.model = "쏘나타";
sonata.color = "흰색";
sonata.fuel = 50;

Car bmw = new Car();
bmw.brand = "BMW";
bmw.model = "520d";
bmw.color = "검정";
bmw.fuel = 60;

// 쏘나타 조작
sonata.start();
sonata.accelerate(60);
sonata.accelerate(40);
sonata.brake(30);
sonata.displayStatus();

// BMW 조작
bmw.start();
bmw.accelerate(100);
bmw.displayStatus();
}
}

실행 결과:

현대 쏘나타: 부릉부릉~ 시동이 켜졌습니다!
가속 +60km/h → 현재 속도: 60km/h, 연료: 44L
가속 +40km/h → 현재 속도: 100km/h, 연료: 40L
제동 -30km/h → 현재 속도: 70km/h
============================
차량: 현대 쏘나타
색상: 흰색
시동: ON
속도: 70 km/h
연료: 40 L
============================

10. 클래스와 객체의 관계 정리​

// 하나의 클래스로 여러 독립적인 객체 생성 가능
Car car1 = new Car(); // 객체 1
Car car2 = new Car(); // 객체 2 (car1과 완전히 독립적)
Car car3 = new Car(); // 객체 3

car1.speed = 100;
car2.speed = 60;
car3.speed = 0;

// 각 객체는 자신만의 독립적인 상태(속도)를 가짐
System.out.println(car1.speed); // 100
System.out.println(car2.speed); // 60
System.out.println(car3.speed); // 0
OOP의 진정한 힘

한 클래스로 수천 개의 독립적인 객체를 만들 수 있습니다. 각 객체는 동일한 구조(필드, 메서드)를 갖지만, 각자 다른 상태(데이터)를 유지합니다. 이것이 바로 OOP의 핵심 매력입니다.

요약​

개념설명
클래스객체를 만들기 위한 설계도 (필드 + 메서드)
객체/인스턴스클래스로 생성된 실체 (힙 메모리에 존재)
new 연산자힙에 객체 공간 할당 + 생성자 호출
참조 변수힙에 있는 객체의 주소를 저장하는 변수
==참조(주소) 비교
equals()내용 비교 (오버라이딩 필요)
멤버 변수클래스 내 선언, 자동 초기화, 힙에 저장
지역 변수메서드 내 선언, 수동 초기화 필수, 스택에 저장