본문으로 건너뛰기

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()내용 비교 (오버라이딩 필요)
멤버 변수클래스 내 선언, 자동 초기화, 힙에 저장
지역 변수메서드 내 선언, 수동 초기화 필수, 스택에 저장