자바 클래스와 객체의 차이 – 붕어빵 틀과 붕어빵으로 이해하기


자바 클래스와 객체 차이, Java를 배우기 시작하면 가장 먼저 마주치는 개념인데 막상 설명하려면 말문이 막히는 분들이 많습니다. “클래스는 설계도고 객체는 실체”라는 말, 들어는 봤는데 코드로 보면 왜 이렇게 헷갈릴까요? 사실 이 두 개념만 제대로 이해하면 객체지향 프로그래밍(OOP)의 절반은 이해한 것입니다. 이 글에서는 붕어빵 틀과 붕어빵이라는 친숙한 비유부터 시작하여, 메모리에서 실제로 어떤 일이 벌어지는지, 면접에서 어떻게 답해야 하는지까지 코드와 함께 단계별로 설명합니다. 끝까지 읽고 나면 클래스와 객체를 자신의 언어로 명확하게 설명할 수 있습니다.


목차

  1. 자바 클래스와 객체 차이 – 기초 개념 정리
  2. 클래스와 객체의 핵심 구성 요소와 동작 원리
  3. 객체 생성이 가져다주는 장점과 활용 가치
  4. 초보자가 자주 하는 실수와 주의점
  5. 실전 단계별 활용법 – 클래스 설계부터 객체 활용까지
  6. 전문가 관점 – 메모리 구조와 객체 생명주기

1. 자바 클래스와 객체 차이 – 기초 개념 정리

자바 클래스와 객체 차이를 이해하는 가장 빠른 방법은 현실 세계의 비유에서 시작하는 것입니다.

붕어빵 틀과 붕어빵 비유

붕어빵 가게를 생각해 보겠습니다.

  • 붕어빵 틀(Class): 붕어빵의 모양, 크기, 재료 배합 방식이 정해진 설계 원형입니다. 틀 자체는 먹을 수 없습니다. 틀만으로는 아무 일도 일어나지 않습니다.
  • 붕어빵(Object): 틀을 이용해 실제로 구워낸 결과물입니다. 팥 붕어빵, 슈크림 붕어빵, 피자 붕어빵처럼 같은 틀에서 만들어졌지만 속재료(상태)는 각각 다를 수 있습니다. 실제로 존재하고, 먹을 수 있습니다.
붕어빵 틀 (Class)          붕어빵 1 (Object)  → 속재료: 팥
    │                  →   붕어빵 2 (Object)  → 속재료: 슈크림
    │                  →   붕어빵 3 (Object)  → 속재료: 피자
 설계 원형, 하나만 존재       실체, 여러 개 생성 가능

Java 코드로 표현하면 이렇습니다.

java

// 붕어빵 틀 = 클래스 (설계 원형, 메모리에 하나)
public class BungeoppangMachine {
    String filling; // 속재료 (인스턴스 변수)

    void eat() {
        System.out.println(filling + " 붕어빵을 먹습니다. 맛있어요!");
    }
}

// 붕어빵 = 객체 (실체, 메모리에 여러 개 생성 가능)
public class Main {
    public static void main(String[] args) {
        BungeoppangMachine b1 = new BungeoppangMachine(); // 붕어빵 1 생성
        b1.filling = "팥";

        BungeoppangMachine b2 = new BungeoppangMachine(); // 붕어빵 2 생성
        b2.filling = "슈크림";

        BungeoppangMachine b3 = new BungeoppangMachine(); // 붕어빵 3 생성
        b3.filling = "피자";

        b1.eat(); // 팥 붕어빵을 먹습니다. 맛있어요!
        b2.eat(); // 슈크림 붕어빵을 먹습니다. 맛있어요!
        b3.eat(); // 피자 붕어빵을 먹습니다. 맛있어요!
    }
}

같은 클래스(BungeoppangMachine)에서 만들어진 세 객체(b1b2b3)는 각자 독립된 filling 값을 갖습니다. 하나를 바꿔도 다른 객체에 영향이 없습니다.

핵심 정의 한 줄 요약

구분정의비유메모리
클래스(Class)객체를 만들기 위한 설계 틀붕어빵 틀, 건축 설계도메서드 영역(단 한 번 로드)
객체(Object)클래스를 기반으로 생성된 실체붕어빵, 완성된 건물힙(Heap) 영역(생성마다 할당)
인스턴스(Instance)특정 클래스로부터 생성된 객체“이 붕어빵은 저 틀의 인스턴스”힙(Heap) 영역

객체 vs 인스턴스: 두 단어는 거의 같은 의미로 쓰이지만 뉘앙스가 다릅니다. 객체는 클래스의 실체를 일반적으로 지칭하고, 인스턴스는 “어떤 클래스의 인스턴스”처럼 특정 클래스와의 관계를 강조할 때 사용합니다. “b1은 BungeoppangMachine의 인스턴스이자 객체입니다.”

다양한 비유로 굳히기

하나의 비유가 잘 와닿지 않는 분들을 위해 다양한 관점으로 설명드립니다.

  • 건축: 클래스는 건물 설계도, 객체는 그 설계도로 지어진 실제 건물. 같은 설계도로 서울 지점·부산 지점·제주 지점을 따로따로 지을 수 있습니다.
  • 쿠키 커터: 클래스는 별 모양 쿠키 커터, 객체는 그 커터로 찍어낸 각각의 쿠키. 쿠키마다 아이싱 색이 다를 수 있습니다.
  • 자동차 공장: 클래스는 소나타 모델 설계 스펙, 객체는 공장에서 생산된 각각의 소나타 자동차. 같은 모델이지만 차량 번호, 색상, 주행 거리는 제각각입니다.

2. 클래스와 객체의 핵심 구성 요소와 동작 원리

클래스의 구성 요소

Java 클래스는 크게 세 가지 구성 요소로 이루어집니다.

java

public class Car {

    // ① 필드(Field) – 객체의 상태(데이터)를 저장
    private String brand;     // 브랜드
    private String color;     // 색상
    private int speed;        // 현재 속도
    private static int totalCars = 0; // 클래스 변수 – 모든 인스턴스가 공유

    // ② 생성자(Constructor) – 객체 생성 시 초기 상태를 설정
    public Car(String brand, String color) {
        this.brand = brand;
        this.color = color;
        this.speed = 0;       // 초기 속도는 0
        totalCars++;          // 자동차가 한 대 생길 때마다 카운트 증가
    }

    // ③ 메서드(Method) – 객체의 행동(기능)을 정의
    public void accelerate(int amount) {
        if (amount > 0) {
            this.speed += amount;
            System.out.println(brand + " 가속! 현재 속도: " + speed + "km/h");
        }
    }

    public void brake(int amount) {
        this.speed = Math.max(0, this.speed - amount);
        System.out.println(brand + " 감속! 현재 속도: " + speed + "km/h");
    }

    public String getInfo() {
        return brand + " | " + color + " | " + speed + "km/h";
    }

    // 클래스 변수 접근용 정적 메서드
    public static int getTotalCars() {
        return totalCars;
    }
}

new 키워드 – 객체 탄생의 순간

new 키워드는 클래스로부터 객체를 생성하는 명령입니다. 내부적으로 세 가지 일이 순서대로 일어납니다.

Car myCar = new Car("현대 아이오닉6", "흰색");

① Heap 메모리 공간 확보
   → JVM이 Car 객체를 담을 메모리 공간을 Heap에 할당

② 생성자 실행
   → Car("현대 아이오닉6", "흰색") 생성자 호출
   → brand = "현대 아이오닉6", color = "흰색", speed = 0으로 초기화
   → totalCars++ (정적 변수 증가)

③ 참조 반환
   → Heap에 생성된 객체의 메모리 주소를 myCar 변수(Stack)에 저장
   → myCar는 객체 자체가 아니라 객체를 가리키는 참조(주소)

java

// 객체 생성과 사용
Car car1 = new Car("현대 아이오닉6", "흰색");
Car car2 = new Car("기아 EV6", "검정");
Car car3 = new Car("테슬라 모델3", "빨강");

System.out.println(Car.getTotalCars()); // 3 (클래스 변수는 공유됨)

car1.accelerate(60);  // 현대 아이오닉6 가속! 현재 속도: 60km/h
car2.accelerate(80);  // 기아 EV6 가속! 현재 속도: 80km/h

// 각 객체는 독립적인 상태를 가짐
System.out.println(car1.getInfo()); // 현대 아이오닉6 | 흰색 | 60km/h
System.out.println(car2.getInfo()); // 기아 EV6 | 검정 | 80km/h
System.out.println(car3.getInfo()); // 테슬라 모델3 | 빨강 | 0km/h

인스턴스 변수 vs 클래스 변수(static)

자바 클래스와 객체 차이를 깊이 이해하려면 인스턴스 변수와 클래스 변수의 차이도 알아야 합니다.

java

public class Counter {
    // 인스턴스 변수: 각 객체마다 별도로 존재
    private int myCount = 0;

    // 클래스 변수(static): 모든 객체가 공유하는 하나의 변수
    private static int totalCount = 0;

    public void increment() {
        myCount++;      // 이 객체만의 카운터 증가
        totalCount++;   // 모든 객체가 공유하는 카운터 증가
    }

    public int getMyCount()     { return myCount; }
    public static int getTotal() { return totalCount; }
}

// 사용 예시
Counter c1 = new Counter();
Counter c2 = new Counter();

c1.increment();
c1.increment();
c2.increment();

System.out.println(c1.getMyCount());    // 2 (c1만의 카운트)
System.out.println(c2.getMyCount());    // 1 (c2만의 카운트)
System.out.println(Counter.getTotal()); // 3 (전체 합산, 공유)
구분인스턴스 변수클래스 변수(static)
선언 위치static 없이 필드 선언static 키워드 붙여 선언
메모리객체마다 별도 힙 공간메서드 영역(단 하나)
생성 시점객체 생성 시클래스 로딩 시
접근 방법객체참조.변수명클래스명.변수명
활용 예시이름, 나이, 속도 등 개별 상태총 생성 수, 공유 설정값

3. 객체 생성이 가져다주는 장점과 활용 가치

코드 재사용성 – 하나의 클래스로 무한한 객체

클래스를 한 번 잘 설계해 두면, 그 클래스로 얼마든지 객체를 만들어 사용할 수 있습니다. 비슷한 기능을 함수나 변수로 반복 작성하지 않아도 됩니다.

java

// 클래스 없이 학생 데이터를 관리하는 방식 (나쁜 예)
String student1Name = "김철수";
int student1Age = 20;
double student1Gpa = 3.8;

String student2Name = "이영희";
int student2Age = 21;
double student2Gpa = 4.2;

String student3Name = "박지수";
int student3Age = 19;
double student3Gpa = 3.5;
// 학생이 100명이면 변수 300개... 관리 불가

// 클래스와 객체를 사용하는 방식 (좋은 예)
public class Student {
    private String name;
    private int age;
    private double gpa;

    public Student(String name, int age, double gpa) {
        this.name = name;
        this.age  = age;
        this.gpa  = gpa;
    }

    public void printInfo() {
        System.out.printf("이름: %s | 나이: %d | 학점: %.1f%n", name, age, gpa);
    }

    public boolean isHonorRoll() {
        return gpa >= 4.0;
    }
}

// 객체 리스트로 100명도 깔끔하게 관리
List<Student> students = new ArrayList<>();
students.add(new Student("김철수", 20, 3.8));
students.add(new Student("이영희", 21, 4.2));
students.add(new Student("박지수", 19, 3.5));

// 우등생 목록 출력
students.stream()
    .filter(Student::isHonorRoll)
    .forEach(Student::printInfo);

캡슐화 – 데이터와 행동을 하나로

객체는 데이터(필드)와 그 데이터를 다루는 행동(메서드)을 하나로 묶어 관리합니다. 외부에서는 public 메서드를 통해서만 접근할 수 있어 데이터의 무결성이 보장됩니다.

java

public class BankAccount {
    private double balance; // 외부 직접 접근 불가

    public BankAccount(double initialBalance) {
        if (initialBalance < 0)
            throw new IllegalArgumentException("초기 잔액은 0 이상이어야 합니다.");
        this.balance = initialBalance;
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("입금액은 양수여야 합니다.");
        this.balance += amount;
        System.out.printf("입금 완료. 잔액: %.0f원%n", balance);
    }

    public void withdraw(double amount) {
        if (amount > balance) throw new IllegalStateException("잔액 부족");
        this.balance -= amount;
        System.out.printf("출금 완료. 잔액: %.0f원%n", balance);
    }

    public double getBalance() { return balance; }
}

// 객체를 통해서만 잔액 변경 가능 – 안전하게 보호됨
BankAccount myAccount = new BankAccount(100000);
myAccount.deposit(50000);   // 입금 완료. 잔액: 150000원
myAccount.withdraw(30000);  // 출금 완료. 잔액: 120000원
// myAccount.balance = -999999; // 컴파일 오류! private이므로 직접 접근 불가

독립성 – 객체끼리 서로 영향을 주지 않는다

같은 클래스로 만들어진 객체라도 각자의 상태는 완전히 독립적입니다. 하나의 객체 상태를 변경해도 다른 객체에 전혀 영향이 없습니다.

java

BankAccount account1 = new BankAccount(100000);
BankAccount account2 = new BankAccount(200000);

account1.withdraw(50000); // account1 잔액: 50000원
// account2는 전혀 변하지 않음

System.out.println(account1.getBalance()); // 50000.0
System.out.println(account2.getBalance()); // 200000.0 (영향 없음)

4. 초보자가 자주 하는 실수와 주의점

실수 1 – NullPointerException: 객체를 생성하지 않고 사용

가장 많이 만나는 오류입니다. 참조 변수를 선언만 하고 new로 객체를 생성하지 않은 채 메서드를 호출하면 NullPointerException이 발생합니다.

java

// 잘못된 코드
Car myCar;                    // 선언만 함 (myCar = null)
myCar.accelerate(60);         // NullPointerException 발생!
// myCar는 아무것도 가리키지 않는데 메서드를 호출하려 했기 때문

// 올바른 코드
Car myCar = new Car("현대", "흰색"); // 선언 + 객체 생성
myCar.accelerate(60);         // 정상 동작

null은 “아무 객체도 가리키지 않는 상태”를 의미합니다. 집 주소(참조 변수)는 있는데 그 주소에 실제 집(객체)이 없는 것과 같습니다. 메서드를 호출하러 갔더니 빈 땅만 있는 상황입니다.

실수 2 – 참조 복사를 값 복사로 오해

Java에서 객체 변수는 객체 자체가 아니라 객체를 가리키는 주소(참조) 를 담습니다. 변수를 다른 변수에 대입하면 주소가 복사되어 두 변수가 같은 객체를 가리키게 됩니다.

java

Car car1 = new Car("현대", "흰색");
Car car2 = car1; // car1의 참조(주소)를 car2에 복사

// car1과 car2는 같은 객체를 가리킴!
car2.color = "검정"; // car2를 통해 색상 변경

System.out.println(car1.getInfo()); // 현대 | 검정 | 0km/h
// car1도 같은 객체를 가리키기 때문에 color가 바뀌어 있음!

// 메모리 구조:
// Stack:  car1 → [주소: 0x100]
//         car2 → [주소: 0x100]  ← 같은 주소!
// Heap:   [0x100] { brand: "현대", color: "검정", speed: 0 }

완전히 독립된 복사본을 만들려면 새 객체를 생성해야 합니다.

java

// 독립적인 복사본 – 복사 생성자 패턴
public class Car {
    // ... 기존 필드 생략

    // 복사 생성자
    public Car(Car other) {
        this.brand = other.brand;
        this.color = other.color;
        this.speed = other.speed;
    }
}

Car car1 = new Car("현대", "흰색");
Car car2 = new Car(car1); // 독립된 새 객체 생성

car2.color = "검정";

System.out.println(car1.getInfo()); // 현대 | 흰색 | 0 (변하지 않음)
System.out.println(car2.getInfo()); // 현대 | 검정 | 0 (독립적으로 변경됨)

실수 3 – static 메서드에서 인스턴스 변수 접근

static 메서드는 특정 객체에 종속되지 않고 클래스 레벨에서 동작합니다. 따라서 static 메서드 안에서는 인스턴스 변수(객체마다 다른 값)에 접근할 수 없습니다.

java

public class Car {
    private String brand; // 인스턴스 변수
    private static int totalCars = 0; // 클래스 변수

    // 잘못된 코드
    public static void printBrand() {
        System.out.println(brand); // 컴파일 오류!
        // static 메서드는 어떤 객체의 brand인지 알 수 없음
    }

    // 올바른 코드 ① – static 메서드는 static 변수만 접근
    public static int getTotalCars() {
        return totalCars; // 클래스 변수 접근 – 가능
    }

    // 올바른 코드 ② – 인스턴스 변수는 인스턴스 메서드로 접근
    public String getBrand() {
        return brand; // 인스턴스 메서드에서 인스턴스 변수 접근 – 가능
    }
}

실수 4 – 생성자를 일반 메서드로 혼동

생성자는 클래스 이름과 동일하고 반환 타입이 없습니다. 반환 타입을 붙이면 생성자가 아닌 일반 메서드가 됩니다.

java

public class Student {
    private String name;

    // 올바른 생성자 – 반환 타입 없음
    public Student(String name) {
        this.name = name;
    }

    // 실수: void를 붙이면 생성자가 아닌 일반 메서드
    public void Student(String name) { // 생성자 아님! 일반 메서드!
        this.name = name;
    }
}

// new로 객체를 생성하면 진짜 생성자(반환 타입 없는 것)가 호출됨
Student s = new Student("김철수"); // 올바른 생성자 호출

5. 실전 단계별 활용법 – 클래스 설계부터 객체 활용까지

Step 1. 클래스 설계 – 무엇을 표현할 것인가

클래스를 설계할 때는 “이 클래스가 표현하는 것이 무엇인가” 를 먼저 명확히 합니다. 도서관 시스템을 예로 들겠습니다.

java

// 책을 표현하는 클래스 설계
public class Book {
    // 책의 상태 (인스턴스 변수)
    private String isbn;
    private String title;
    private String author;
    private int publishYear;
    private boolean isAvailable; // 대출 가능 여부

    // 생성자 – 반드시 필요한 정보로 초기화
    public Book(String isbn, String title, String author, int publishYear) {
        this.isbn        = isbn;
        this.title       = title;
        this.author      = author;
        this.publishYear = publishYear;
        this.isAvailable = true; // 새 책은 대출 가능 상태
    }

    // 책의 행동 (메서드)
    public boolean checkOut() {
        if (!isAvailable) {
            System.out.println("[" + title + "] 이미 대출 중입니다.");
            return false;
        }
        isAvailable = false;
        System.out.println("[" + title + "] 대출이 완료되었습니다.");
        return true;
    }

    public void returnBook() {
        isAvailable = true;
        System.out.println("[" + title + "] 반납이 완료되었습니다.");
    }

    public String getSummary() {
        return String.format("『%s』 - %s (%d년) [%s]",
            title, author, publishYear,
            isAvailable ? "대출 가능" : "대출 중");
    }

    // Getter
    public String getIsbn()   { return isbn; }
    public String getTitle()  { return title; }
    public boolean isAvailable() { return isAvailable; }
}

Step 2. 객체 생성과 활용

java

public class Library {
    public static void main(String[] args) {

        // 책 객체 생성
        Book book1 = new Book("978-89-01-23456-7", "클린 코드", "로버트 C. 마틴", 2013);
        Book book2 = new Book("978-89-01-78901-2", "이펙티브 자바", "조슈아 블로크", 2018);
        Book book3 = new Book("978-89-01-11111-3", "토비의 스프링", "이일민", 2012);

        // 현황 출력
        System.out.println("=== 도서관 현황 ===");
        System.out.println(book1.getSummary());
        System.out.println(book2.getSummary());
        System.out.println(book3.getSummary());

        System.out.println("\n=== 대출 처리 ===");
        book1.checkOut();  // 클린 코드 대출
        book1.checkOut();  // 이미 대출 중 메시지 출력
        book2.checkOut();  // 이펙티브 자바 대출

        System.out.println("\n=== 반납 후 현황 ===");
        book1.returnBook();
        System.out.println(book1.getSummary()); // 다시 대출 가능 상태
    }
}

Step 3. 객체 배열과 컬렉션 활용

java

import java.util.*;
import java.util.stream.Collectors;

public class LibrarySystem {

    private List<Book> books = new ArrayList<>();

    // 책 추가
    public void addBook(Book book) {
        books.add(book);
        System.out.println("도서 추가: " + book.getTitle());
    }

    // 대출 가능한 책 목록
    public List<Book> getAvailableBooks() {
        return books.stream()
            .filter(Book::isAvailable)
            .collect(Collectors.toList());
    }

    // 제목으로 검색
    public Optional<Book> findByTitle(String title) {
        return books.stream()
            .filter(b -> b.getTitle().contains(title))
            .findFirst();
    }

    // 전체 현황 출력
    public void printStatus() {
        System.out.println("\n=== 전체 도서 현황 ===");
        books.forEach(b -> System.out.println(b.getSummary()));
        System.out.printf("전체: %d권 | 대출 가능: %d권 | 대출 중: %d권%n",
            books.size(),
            getAvailableBooks().size(),
            books.size() - getAvailableBooks().size());
    }

    public static void main(String[] args) {
        LibrarySystem library = new LibrarySystem();

        library.addBook(new Book("978-01", "클린 코드", "마틴", 2013));
        library.addBook(new Book("978-02", "이펙티브 자바", "블로크", 2018));
        library.addBook(new Book("978-03", "리팩터링", "파울러", 2019));

        // 검색 후 대출
        library.findByTitle("클린 코드")
               .ifPresent(Book::checkOut);

        library.printStatus();
    }
}

6. 전문가 관점 – 메모리 구조와 객체 생명주기

JVM 메모리 구조와 클래스·객체의 위치

자바 클래스와 객체 차이를 가장 깊이 이해하는 방법은 JVM이 메모리를 어떻게 사용하는지 아는 것입니다.

JVM 메모리 구조
┌─────────────────────────────────────────────┐
│              Method Area (메서드 영역)        │
│  - 클래스 정보(바이트코드) 저장               │
│  - static 변수, 상수 풀                      │
│  - Car.class, Student.class 등              │
│  → 클래스 로더가 .class 파일 로드 시 생성    │
├─────────────────────────────────────────────┤
│              Heap (힙 영역)                  │
│  - new 키워드로 생성된 객체 저장             │
│  - 인스턴스 변수 값 저장                     │
│  [0x100] Car{brand="현대",color="흰색"}      │
│  [0x200] Car{brand="기아", color="검정"}     │
│  [0x300] Student{name="김철수",age=20}       │
│  → GC(가비지 컬렉터)가 참조 없는 객체 제거  │
├─────────────────────────────────────────────┤
│              Stack (스택 영역)               │
│  - 메서드 호출 시 프레임 생성                │
│  - 지역 변수, 매개변수, 참조 변수 저장       │
│  main 프레임: car1=0x100, car2=0x200        │
│  → 메서드 종료 시 프레임 자동 제거           │
└─────────────────────────────────────────────┘

java

public static void main(String[] args) {
    // Stack에 car1 변수 생성, Heap에 Car 객체 생성, car1에 주소(0x100) 저장
    Car car1 = new Car("현대", "흰색");

    // Stack에 car2 변수 생성, Heap에 새 Car 객체 생성, car2에 주소(0x200) 저장
    Car car2 = new Car("기아", "검정");

    // car1이 car2가 가리키는 객체(0x200)를 가리키도록 변경
    car1 = car2;
    // 이 시점에서 0x100 주소의 객체는 아무도 가리키지 않음
    // → GC 대상이 됨 (언젠가 메모리에서 제거됨)
}
// main 메서드 종료 → Stack 프레임 제거 → car1, car2 변수 사라짐
// Heap의 객체들도 이후 GC에 의해 제거됨

객체 생명주기

① 클래스 로딩
   .class 파일 → JVM 클래스 로더 → Method Area에 클래스 정보 등록

② 객체 생성 (new)
   Heap 메모리 할당 → 생성자 실행 → 참조 반환

③ 객체 사용
   참조 변수를 통해 필드 접근, 메서드 호출

④ 객체 참조 해제
   변수에 null 할당, 변수가 스코프 밖으로 벗어남
   → 더 이상 참조하는 변수가 없는 객체 = GC 대상

⑤ 가비지 컬렉션 (GC)
   JVM이 참조 없는 객체를 자동으로 메모리에서 제거
   → 개발자가 명시적으로 메모리를 해제할 필요 없음 (C/C++와의 차이)

java

public void processData() {
    // 메서드 시작 – 스코프 시작
    Car tempCar = new Car("임시", "회색"); // Heap에 객체 생성

    tempCar.accelerate(30);
    System.out.println(tempCar.getInfo());

} // 메서드 종료 – tempCar 변수가 스코프를 벗어남
  // tempCar가 가리키던 객체는 이제 참조자 없음 → GC 대상

// 명시적 참조 해제
Car myCar = new Car("현대", "흰색");
myCar = null; // 명시적으로 참조 해제 → GC 대상

추천 학습 도구 및 리소스

도구 / 리소스용도
IntelliJ IDEA클래스·객체 디버깅, 메모리 분석
Java Visualizer (visualize.iotools.com)객체 참조 관계 시각화
JVM 메모리 분석 (VisualVM)힙·스택 실시간 모니터링
이펙티브 자바 (조슈아 블로크)클래스 설계 실무 모범 사례
자바의 정석 (남궁 성)한국어 Java 기초 최고 입문서
Baeldung (baeldung.com)Java 클래스·객체 심화 영문 레퍼런스

면접 대비 핵심 답변

“클래스와 객체의 차이를 설명해 보세요.” 클래스는 객체를 만들기 위한 설계 틀로 메서드 영역에 단 한 번 로드됩니다. 객체는 그 클래스를 바탕으로 new 키워드를 통해 힙 메모리에 생성된 실체이며, 같은 클래스로 여러 개의 독립적인 객체를 만들 수 있습니다.

“인스턴스와 객체의 차이는 무엇인가요?” 기술적으로는 같은 의미입니다. 다만 객체는 클래스의 실체를 일반적으로 지칭하고, 인스턴스는 “이 객체가 어떤 클래스로부터 만들어졌는지”를 강조할 때 사용합니다. “이 객체는 Car 클래스의 인스턴스입니다”처럼 클래스와의 관계를 명시할 때 인스턴스라는 표현을 씁니다.

“static 변수와 인스턴스 변수의 차이는 무엇인가요?” 인스턴스 변수는 객체마다 별도로 힙 메모리에 존재하며 각 객체의 고유한 상태를 저장합니다. static 변수는 메서드 영역에 단 하나만 존재하며 해당 클래스의 모든 인스턴스가 공유합니다. 클래스가 로딩될 때 생성되고 프로그램이 종료될 때까지 존재합니다.


결론

자바 클래스와 객체 차이는 “설계 틀”과 “실체”의 차이입니다. 클래스는 객체를 만들기 위한 청사진으로 메서드 영역에 하나만 존재하고, 객체는 그 클래스를 바탕으로 힙 메모리에 생성된 독립적인 실체로 여러 개를 만들 수 있습니다. 자바 클래스와 객체 차이를 붕어빵 틀과 붕어빵의 관계로 기억하고, new 키워드가 힙 메모리에 공간을 확보하고 생성자를 실행하여 참조를 돌려주는 과정을 머릿속에 그릴 수 있다면, 객체지향 프로그래밍의 핵심 기반은 완전히 다진 것입니다. 지금 바로 IntelliJ를 열어 나만의 클래스 하나를 설계하고 객체를 세 개 만들어보세요. 코드를 직접 실행해보는 순간 개념이 손끝에 새겨집니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다