[TIL 3일 차] 객체지향 프로그래밍의 이해

오늘의 학습

1. 객체지향 프로그래밍(OOP)의 개념과 필요성

1-01. 객체지향 프로그래밍(OOP; Object-Oriented Programming)

프로그래밍에서 필요한 정보를 한 곳에 모아, 상태와 행동을 가지는 작은 객체를 만들고, 이 객체들이 서로 협력하며 하나의 프로그램을 완성하는 방식

  • 객체지향 프로그래밍에서 중요한 개념들
구분 설명
객체(Object) 상태(state)와 행동(behavior)를 가진 독립적인 존재
클래스(Class) 객체를 만들기 위한 설계도
메시지 객체 간의 상호작용 방식 (메서드 호출)
협력 여러 객체가 상호작용하며 프로그램을 구성
  • 객체지향의 핵심은 현실 세계를 프로그래밍으로 옮기는 것
  • 객체지향은 “기능을 어떻게 구현할까?” 같은 기능 중심이 아닌 “누가 이 기능을 담당해야 하지?” 같은 책임 중심 사고 → 역할과 책임이 명확한 코드 구조를 만들 수 있다.
    • 절차지향 vs 객체지향
      • 절차 지향 : “사용자가 입력 → 검증 → 저장 → 출력”
      • 객체 지향 : “사용자는 어떤 객체이고, 입력을 누가 받아서, 어디에 저장하고 출력할까?”

        항목 절차지향 (Procedural) 객체지향 (OOP)
        중심(초점) 순서와 함수 중심 객체 중심 (역할과 책임)
        재사용성 낮음 높음
        유지보수 어렵고 반복 많음 유연하고 모듈화 쉬움
        예시 언어 C, 초기 Python Java, Kotlin, C#


1-02. 객체지향 방식이 유리한 상황

상황 객체지향이 유리한 이유
기능이 계속 추가되는 프로젝트 설계 변경 없이 객체 추가만으로 확장 가능
여러 개발자가 동시에 개발 각자의 책임이 명확하여 충돌 없이 개발 가능
테스트 및 유지보수가 필요한 시스템 단위 객체 테스트가 가능하고, 코드 수정 범위가 제한됨
재사용 가능한 라이브러리나 프레임워크 객체 단위로 분리되어 다양한 상황에서 재활용 가능

즉, 객체지향은 협업, 확장성, 유지보수성이 중요한 프로젝트에서 진가를 발휘한다.


2. Java의 클래스(Class)와 객체(Object)

2-01. 클래스(Class)

개념적 의미: 객체를 추상화(정의)한 설계도(blueprint) 또는 틀(frame), 인스턴스를 생성하는 목적의 코드 단위

문법적 의미: 필드와 메서드를 정의하는 사용자 정의 자료형

  • 클래스는 설계도에서 벗어날 수 없음 → 즉, 객체가 될 수 없음
    • 자동차 설계도(클래스)를 통해 만든 자동차(객체)들은 전부 다름
  • 클래스는 객체 그 자체가 아닌 객체를 생성하는데 사용되는 하나의 tool
  • 클래스를 통해 생성되는 개체를 클래스의 인스턴스(instance)라고 부른다.


1) 클래스 구성 요소

Class 클래스명 { // 클래스명은 대문자로 시작
  // 필드 (속성)
  // 메서드 (행위)
  // 생성자
  // 이너 클래스 (선택)
}
구성 요소 설명
필드(Field) 객체가 가지는 속성 (예: 이름, 나이 등)
메서드(Method) 객체가 수행할 수 있는 동작
생성자(Constructor) 객체 생성 시 호출되는 특수한 메서드
이너 클래스 클래스 내부에 정의된 또 다른 클래스 (필요 시 사용)


2-02. 객체(Object)와 인스턴스(Instance)

  • 객체(Object)
    • 개념적 의미: 현실 세계의 사물이나 개념을 추상화한 것.
    • 문법적 의미: 클래스를 기반으로 생성된 메모리 상의 인스턴스
    • Person p = new Person(); // 객체(인스턴스) 생성
  • 인스턴스(Instance): 특정 클래스의 정의에 따라 생성된 객체
    • “모든 인스턴스는 객체이지만, 모든 객체가 특정 클래스의 인스턴스는 아닐 수 있다.”

객체 생성

  • 객체는 new 키워드로 생성할 수 있다.
클래스명 참조_변수명; // 인스턴스를 참조하기 위한 참조 변수 선언
참조_변수명 = new 생성자(); // 인스턴스 생성 후, 객체의 주소를 참조 변수에 저장
// 클래스명 참조_변수명 = new 생성자();
  • 먼저, 특정 클래스 타입의 참조 변수를 선언하고, new 키워드와 생성자를 통해 인스턴스를 생성해 참조 변수에 할당
  • 여기서 참조 변수는 실제 데이터 값을 저장하는 것이 아니라 실제 데이터가 저장되어 있는 힙 메모리 주소 값을 저장하는 변수를 의미
  • new 키워드는 생성된 객체를 힙 메모리에 넣으라는 의미
  • new 키워드와 생성자를 통해 클래스의 객체를 생성한다는 것은 해당 객체를 힙 메모리에 넣고 그 주소값을 참조변수에 저장하는 것과 동일


3. 필드(Field)와 메서드(Method)

3-01. 필드(Field)

  • 클래스에 포함된 변수로, 객체가 가지게 될 상태(데이터)를 정의하기 위해 사용됨
  • 객체지향 프로그래밍에서는 클래스의 속성을 나타냄


1) 변수의 세 가지 종류

  1. 클래스 변수 (cv, class variable)
  2. 인스턴스 변수 (iv, instance variable)
  3. 지역 변수 (lv, local variable)
  • 클래스 변수와 인스턴스 변수가 필드이고, 메서드 내에서 선언된 변수는 지역 변수


2) 인스턴스 변수

인스턴스 변수는 객체가 가질 고유한 특성을 저장하는 변수

  • new 생성자()로 객체가 생성될 때마다 각 객체마다 별도로 생성
  • 힙 메모리에 저장되어 객체가 유지되는 한 함께 유지


3) 클래스 변수 (static)

  • 클래스 변수는 static 키워드로 선언하며, 클래스가 메모리에 로드될 때 단 한 번만 생성됩니다.
  • 모든 인스턴스가 공유하며, 인스턴스를 만들지 않아도 접근이 가능합니다.

클래스명.변수명으로 접근 가능


4) static 키워드

클래스 로딩 시점에 정적 메모리 영역에 할당되는 멤버를 정의합니다. 객체를 생성하지 않아도 클래스명.멤버명 형식으로 접근할 수 있으며, 여러 인스턴스 간에 공유됩니다.

class StaticExample {
    int num1 = 10; // 인스턴스 변수
    static int num2 = =10; // 클래스 변수
}

public class StaticTest {
    public static void main(String[] args) {
    StaticExample.num2; // 클래스 변수
    }
}
(1) 메모리 저장 위치와 생성 시점
구분 저장 위치 생성 시점 접근 방법
인스턴스 멤버 힙 영역 객체 생성 시 객체.멤버
정적 멤버 클래스 영역 클래스 로드 시 클래스명.멤버
(2) static 메서드

static 키워드를 메서드에 붙이면 객체 생성 없이도 호출이 가능한 정적 메서드가 됨 ex) Math.sqrt(), Integer.parseInt()

⚠️ 주의!!

static 메서드에서는 인스턴스 멤버(필드나 메서드)를 사용할 수 없음. 인스턴스가 없는 상태에서도 호출되기 때문에 논리적으로 존재하지 않는 인스턴스에 접근할 수 없기 때문.


5) 지역 변수

  • 메서드, 생성자, 블록 내부에서 선언되는 변수로, 해당 블록 내에서만 유효합니다.
  • 스택 메모리에 저장되고, 메서드 실행이 끝나면 자동 소멸합니다.
class Counter {
    int count; // 인스턴스 변수
    static int totalCount = 0; // 클래스 변수
    void increase() {
        int tempCount = 0; // 지역 변수
    }
}


6) 필드 vs 지역 변수

구분 필드 (클래스 변수/인스턴스 변수) 지역 변수
메모리 저장 영역 클래스/힙 메모리 스택 메모리
생성 시점 클래스 로드/객체 생성 시 메서드 실행 시
소멸 시점 프로그램 종료/GC 메서드 종료 시
초기화 자동 초기화됨 직접 초기화 필요

⚠️ 주의!!

필드는 초기값을 명시하지 않아도 자동으로 0, false, null 등으로 초기화 되지만, 지역 변수는 반드시 초기값을 설정해야 하며, 그렇지 않으면 컴파일 에러가 발생합니다.


3-02. 메서드(Method)


1) 메서드(Method)

작업을 수행하는 일련의 명령문 블록

제어자 반환_타입 메서드명(매개변수) {
		// 메서드 바디: 실제로 수행할 작업
}


2) 메서드 오버로딩(Method Overloading)

  • 동일한 이름의 메서드를 매개변수 타입, 개수, 순서를 달리하여 여러 개 정의하는 것
class Shape {
    public void area() {
        System.out.println("넓이");
    }

    public void area(int r) {
        System.out.println("원 넓이 = " + 3.14 * r * r);
    }

    public void area(int w, int h) {
        System.out.println("직사각형 넓이 = " + w * h);
    }

    public void area(double b, double h) {
        System.out.println("삼각형 넓이 = " + 0.5 * b * h);
    }
}

⚠️ 아래와 같은 형태는 오버로딩이 불가능:

  • 오직 반환 타입만 다른 경우
  • 매개변수 이름만 다른 경우
  • 접근 제어자만 다른 경우
  • 가변인자의 모호성(ambiguity) : (int... nums)(int[] nums)

4. 생성자(Constructor)

4-01. 생성자(Constructor)

클래스의 인스턴스가 생성될 때 자동으로 호출되는 초기화 메서드

  • 이름이 클래스명과 같아야 하며, 반환 타입이 없음 (void도 쓰지 않음).
  • 인스턴스 생성 자체는 new 키워드가 담당하고, 생성자는 초기화 역할을 수행
클래스명(매개변수) {
    // 인스턴스 초기화 코드
}


4-02. 생성자 오버로딩

하나의 클래스에 여러 개의 생성자를 정의할 수 있는 것을 생성자 오버로딩이라 하며, 매개변수의 개수타입이 다르면 가능

class Constructor {
    Constructor() {
        System.out.println("1번 생성자");
    }

    Constructor(String str) {
        System.out.println("2번 생성자");
    }

    Constructor(int a, int b) {
        System.out.println("3번 생성자");
    }
}


4-03. this() 키워드

this()같은 클래스 내의 다른 생성자를 호출할 때 사용하는 키워드입니다.


1) 사용 규칙

  • 오직 생성자 내부에서만 사용할 수 있습니다.
  • 반드시 생성자의 첫 번째 줄에 위치해야 합니다.
    • JDK25 LTS부터는 다른 줄에 위치해도 동작됨


4-04. this 키워드

메서드나 생성자 안에서 인스턴스 자기 자신을 참조하는 키워드입니다.

주로 필드(인스턴스 변수)매개변수 이름이 겹칠 때 구분 목적으로 사용됩니다.


1) 필요한 이유

  • name = name처럼 작성하면 둘 다 지역 변수로 간주되어 인스턴스 변수 초기화가 되지 않음
  • this.name으로 인스턴스 변수에 명확히 접근함을 명시해야 함

5. 자바 가상 머신(Java Virtual Machine)

5-01. JVM(Java Virtual Machine)

자바 애플리케이션을 실행하기 위한 가상 컴퓨터(Virtual Machine)

  • 자바 소스 코드를 컴퓨터가 이해할 수 있도록 실행시키는 해석기(interpreter) 역할을 수행
  • .class 파일(바이트코드)을 읽고 해석하여 실제 실행
  • 운영체제에 독립적으로 동작할 수 있게 만드는 핵심 요소


5-02. JVM 메모리 구조

JVM은 실행 시 메모리를 크게 다음과 같이 나눠 관리


1) Method Area (메서드 영역)

  • 클래스 메타 정보, static 변수, final 상수 등을 저장
  • 클래스가 처음 로드될 때 생성되며, JVM 종료 시까지 유지
  • 모든 스레드에서 공유되는 영역


2) Heap Area (힙 영역)

  • new 키워드로 생성한 객체(인스턴스)가 저장
  • 참조형 변수는 이 주소값을 참조
  • JVM에는 오직 하나의 힙만 존재
  • GC(Garbage Collector)에 의해 메모리 자동 관리


3) Stack Area (스택 영역)

  • 메서드 호출 시 생성되는 스택 프레임 저장
  • 각 프레임에는 매개변수, 지역변수, 참조 변수 등이 포함
  • 메서드 종료 시 자동 제거 (LIFO 구조)


5-03. Stack과 Heap의 동작 방식


1) Stack: 메서드 실행을 위한 저장소

  • LIFO 구조 (Last In First Out)
  • 메서드 호출 시마다 스택 프레임이 위로 쌓임
  • 프레임 안에는 지역 변수, 매개변수, 연산 중 발생하는 값 등을 저장


2) Heap: 실제 객체 저장소

  • 프로그램 실행 중 생성된 객체는 모두 Heap에 저장
  • 각 참조 변수는 Stack에 존재하고, Heap의 주소를 참조
Person p = new Person();
  • Person()은 Heap에 객체 생성
  • 참조 변수 p는 Stack에 저장되며, 객체의 주소를 가리킴


5-04. Garbage Collection

프로그램 실행 도중 더 이상 참조되지 않는 객체를 탐지하고 제거해 메모리를 확보하는 자동화된 메커니즘

  • C/C++ 등은 개발자가 수동으로 메모리 해제를 해야 하지만, 자바는 JVM이 자동 관리
  • 덕분에 메모리 누수, dangling pointer 등 위험을 줄일 수 있음


5-05. 메모리 누수(Memory Leak)

“더 이상 사용되지 않지만 여전히 참조되고 있어 GC가 수거하지 못하는 객체”로, 누적되면 시스템 자원을 고갈시켜 애플리케이션 성능 저하 또는 비정상 종료로 이어질 수 있다.

  • 참조만 끊기면 GC가 수거할 수 있으나, 의도치 않게 참조가 유지되면 메모리 점유가 계속됨
  • 이는 OutOfMemoryError의 원인이 되기도 함


5-06. 메모리 누수가 발생하기 쉬운 상황

  1. 컬렉션에 객체 추가 후 제거하지 않을 때
    • 사용이 끝난 객체는 remove() 등으로 명시적 제거가 필요
  2. static 변수에 객체를 저장했을 때
    • 약한 참조, 캐시 만료 정책, 주기적 clean 또는 remove 처리
  3. 리스너, 콜백 등 이벤트 객체 미해제
    • 이벤트 등록 후 반드시 romoveListener() 호출 또는 약한 참조를 사용해 자동 GC 유도

Leave a comment