백엔드/Java

클래스와 객체

mino28 2025. 5. 26. 17:20

1. 객체지향 프로그래밍

객체지향 프로그래밍(Object-Oriented Programming, OOP)은 현실 세계를 객체(object)라는 단위로 모델링하여 프로그램을 구성하는 방식으로, 코드의 재사용성과 확장성을 높여주는 프로그래밍 패러다임입니다. 객체는 데이터(속성)와 그 데이터를 처리하는 함수(메서드)를 함께 가지며, 클래스(class)를 통해 설계되고 인스턴스를 통해 사용됩니다. 객체지향의 주요 특징은 캡슐화, 상속, 다형성, 추상화로, 이 네 가지 특성을 통해 코드의 구조를 체계화하고 유지보수와 협업을 용이하게 합니다.

 

 

2. 클래스

클래스(Class)는 객체를 만들기 위한 설계도로, 객체의 속성과 동작을 정의하는 사용자 정의 자료형입니다. 클래스는 변수(필드, 속성)와 메서드(함수)를 포함하여 하나의 단위로 묶으며, 이를 바탕으로 실제 사용할 수 있는 객체(인스턴스)를 생성할 수 있습니다. 예를 들어 Car라는 클래스를 만들면, 이 클래스는 자동차의 속성(예: 색상, 속도)과 동작(예: 달리기, 멈추기)을 정의하고, 이를 기반으로 여러 대의 자동차 객체를 생성하여 사용할 수 있습니다. 클래스는 객체지향 프로그래밍의 핵심 구성요소로, 코드의 재사용성과 구조화를 가능하게 합니다.

"자동차"라는 개념을 프로그래밍으로 표현하고 싶다면?

  • 속성(필드): 색상, 속도, 브랜드 등
  • 동작(메서드): 달리다(run), 멈추다(stop) 등

이런 속성과 동작을 정의한 것이 클래스이고, 이 클래스를 바탕으로 만들어진 각각의 자동차는 객체(Object) 혹은 인스턴스(Instance)라고 부릅니다.

 

1. 클래스의 구성 요소

필드(Field) 객체가 가지는 변수, 속성 (예: 이름, 나이 등)
메서드(Method) 객체가 할 수 있는 동작, 함수
생성자(Constructor) 객체가 생성될 때 호출되는 특수한 메서드
// 클래스 정의
public class Dog {
    // 필드(속성)
    String name;
    int age;

    // 메서드(동작)
    public void bark() {
        System.out.println(name + "가 멍멍 짖습니다!");
    }

    public void introduce() {
        System.out.println("안녕하세요! 제 이름은 " + name + "이고, 나이는 " + age + "살입니다.");
    }
}​
public class Main {
    public static void main(String[] args) {
        // Dog 클래스의 객체 생성
        Dog myDog = new Dog();

        // 필드 값 설정
        myDog.name = "루시";
        myDog.age = 15;

        // 메서드 호출
        myDog.introduce(); // 출력: 안녕하세요! 제 이름은 루시이고, 나이는 15살입니다.
        myDog.bark();      // 출력: 루시가 멍멍 짖습니다!
    }
}

 

객체

객체(Object)는 클래스라는 설계도를 바탕으로 생성된 실제 사용 가능한 실체(instance)입니다. 객체는 클래스에 정의된 속성(필드)과 동작(메서드)을 그대로 가지고 있으며, 프로그램 내에서 다양한 데이터를 저장하고 기능을 수행하는 주체로 활용됩니다. 예를 들어 Car라는 클래스가 있다면, myCar = new Car();처럼 생성된 myCar는 Car 클래스의 객체이며, 고유한 색상, 속도 등의 값을 가지며 달리기(run) 같은 동작을 수행할 수 있습니다. 즉, 객체는 클래스에서 정의한 구조를 바탕으로 실제 메모리에 생성되어 작동하는 실질적인 요소입니다.

 

객체(Object) vs 인스턴스(Instance)

정의 클래스에 의해 생성된 실체를 의미 객체가 특정 클래스의 구성 요소로 생성되었을 때를 강조하는 표현
포괄성 더 일반적인 개념 보다 구체적인 의미, 어떤 클래스의 인스턴스인지 명확히 함
예시 표현 "메모리에 존재하는 독립된 실체" "이 객체는 A라는 클래스의 인스턴스다"
관점 프로그래밍 실체 자체를 말할 때 사용 클래스와의 관계를 강조할 때 사용
비유 '강아지'라는 개체 '강아지는 동물(Animal) 클래스의 인스턴스다'

 

2. 생성자

생성자(Constructor) 생성자는 클래스로부터 객체를 생성할 때 호출되는 특수한 메서드입니다. 주된 목적은 객체가 생성될 때 필요한 초기값을 설정(초기화)해주는 것입니다.

이름 클래스 이름과 동일해야 함 (반드시!)
반환형 없음 (void도 쓰지 않음)
호출 시점 new 키워드로 객체를 생성할 때 자동으로 호출됨
여러 개 가능 매개변수를 달리하여 생성자 오버로딩 가능

 

매개변수가 없는 생성자

public class Person {
    String name;
    int age;

    // 기본 생성자
    public Person() {
        System.out.println("기본 생성자 호출됨!");
    }
}

 

매개변수가 있는 생성자

  • this: 현재 객체 자신을 가리킴
  • this(...): 다른 생성자 호출(생성자 안에서만 첫 줄에 사용 가능)
public class Person {
    String name;
    int age;

    // 생성자 (이름과 나이를 초기화)
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("김사과", 20);  // 생성자 호출됨
        System.out.println(p1.name + ", " + p1.age);
    }
}

 

3. 생성자 오버로딩

같은 이름의 생성자를 매개변수 개수나 타입이 다르게 여러 개 정의할 수 있습니다.

public class User {
    String name;
    int age;

    // 1. 기본 생성자
    public User() {
        this("이름없음", 0);  // this()를 통해 다른 생성자 호출
        System.out.println("기본 생성자 호출됨");
    }

    // 2. 이름만 받는 생성자
    public User(String name) {
        this(name, 0);  // age를 0으로 설정하여 다른 생성자 호출
        System.out.println("이름만 받는 생성자 호출됨");
    }

    // 3. 이름과 나이를 받는 생성자
    public User(String name, int age) {
        System.out.println("이름과 나이를 받는 생성자 호출됨");

        // 조건 분기 로직: 나이 유효성 검사
        if (age < 0) {
            System.out.println("나이는 음수가 될 수 없습니다. 0으로 설정합니다.");
            this.age = 0;
        } else {
            this.age = age;
        }

        // 이름이 null이거나 빈 문자열이면 기본값 설정
        if (name == null || name.trim().isEmpty()) {
            this.name = "이름없음";
        } else {
            this.name = name;
        }
    }

    // 소개 메서드
    public void introduce() {
        System.out.println("안녕하세요. 제 이름은 " + name + "이고 나이는 " + age + "살입니다.");
    }

    // main 메서드로 테스트
    public static void main(String[] args) {
        User u1 = new User();                     // 기본 생성자
        User u2 = new User("김사과");                // 이름만 받는 생성자
        User u3 = new User("반하나", -5);           // 잘못된 나이로 조건 분기 확인
        User u4 = new User("", 20);              // 잘못된 이름으로 조건 분기 확인

        u1.introduce();
        u2.introduce();
        u3.introduce();
        u4.introduce();
    }
}

 

3. null

null은 참조형 변수(객체)의 기본값이며, "아직 어떤 객체도 참조하지 않고 있음"을 의미합니다. null은 기본형(int, double 등)에는 사용할 수 없고, 객체 타입(참조형)에만 사용할 수 있습니다.

String s = null;  // s는 아직 아무 문자열도 참조하지 않음

 

1. NullPointerException(NPE)이란?

null 값을 가진 변수에서 메서드를 호출하거나 필드에 접근하려고 하면 자바는 NullPointerException을 발생시킵니다.

String s = null;
System.out.println(s.length());  // ❌ NPE 발생

 

2. null 처리 시 주의할 점 5가지

메서드 호출 전에 null 체크 필수

if (s != null) {
    System.out.println(s.length());
}

 

equals 비교는 null 객체로 하지 말고, 상수로 시작

String name = null;

// 잘못된 방식 (null에서 메서드 호출)
if (name.equals("김사과")) { }  // ❌ NPE

// 올바른 방식
if ("김사과".equals(name)) { }  // ✅ 안전

 

래퍼 클래스는 언박싱 시 null이면 에러

Integer num = null;
int x = num;  // ❌ NPE 발생 (null → int 언박싱 불가)

// 해결 방법
if (num != null) {
    int x = num;
}
// 또는
int x = (num != null) ? num : 0;

 

배열 또는 리스트에서 null 요소 존재 여부 확인

String[] names = new String[3];  // [null, null, null]

for (String n : names) {
    if (n != null) {
        System.out.println(n.length());
    }
}

 

Optional (선택적) 사용으로 null 안전하게 처리

Optional<String> opt = Optional.ofNullable(name);

opt.ifPresent(n -> System.out.println(n.length())); // null이면 실행 안 함

String result = opt.orElse("기본값"); // null이면 기본값 사용

 

 

4. 가비지 컬렉터

가비지 컬렉터(Garbage Collector)는 자바에서 더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거해주는 기능으로, 개발자가 직접 메모리를 해제하지 않아도 되도록 도와주는 자동 메모리 관리 시스템입니다. 객체가 생성되면 힙(heap) 메모리에 저장되는데, 더 이상 어떤 변수나 객체에서도 참조되지 않는 객체는 "쓸모없다"고 판단되어 가비지 컬렉터의 대상이 됩니다. 이 과정은 JVM이 자동으로 수행하며, 메모리 누수를 방지하고 안정적인 프로그램 실행을 돕습니다. 필요할 경우 System.gc()로 수동 요청도 가능하지만, 실제 실행 여부는 JVM의 판단에 따릅니다.

 

객체에 대한 참조(reference)가 사라지면, JVM은 "이건 이제 필요 없는 객체"라고 판단하고 제거 대상에 넣습니다.

Dog dog = new Dog();  // Dog 객체 생성
dog = null;           // 참조 제거 → 이 객체는 더 이상 접근할 수 없음

 

1. 가비지 컬렉터의 동작시기

가비지 컬렉터는 JVM이 자동으로 판단하여 실행합니다.

  • 메모리가 부족할 때
  • 시스템이 한가할 때
  • 명시적으로 요청할 수도 있음(단, 반드시 실행된다는 보장은 없음)
System.gc();  // JVM에게 가비지 컬렉션을 요청함 (강제는 아님)

 

2. 가비지 컬렉터의 내부 동작 방법

  • Reachability 분석 :  루트(root, 예: 지역변수, static 변수 등)에서부터 접근할 수 없는 객체를 탐지
  • Mark and Sweep (표시 & 제거) : 사용 중인 객체는 표시(mark), 사용되지 않은 객체는 제거(sweep)
  • Compact(압축) : 남은 객체들을 메모리 앞쪽으로 정리해서 메모리 단편화 방지

 

 

단편화

단편화는 메모리에 빈 공간이 조각조각 나뉘어 흩어져 있어서, 충분한 총 공간은 있지만 연속된 공간이 부족하여 새로운 데이터를 저장할 수 없는 상태를 말합니다. 자바의 가비지 컬렉터는 단순히 객체만 지우는 게 아니라, 필요 시 남은 객체들을 앞으로 땡겨서(압축하여) 단편화를 줄이는 작업(compaction)도 수행합니다.

 

 

5. 패키지

자바에서 패키지(Package)는 관련된 클래스, 인터페이스, 열거형 등을 논리적으로 묶어주는 디렉터리 형태의 구조로, 코드의 조직화와 관리를 쉽게 해줍니다. 패키지를 사용하면 클래스 이름이 충돌하는 것을 방지할 수 있고, 서로 관련 있는 기능들을 그룹으로 묶어 모듈화와 재사용성을 높일 수 있습니다. 패키지는 package 키워드로 선언하며, 예를 들어 package com.example.util;처럼 계층적으로 구성할 수 있고, 다른 패키지의 클래스를 사용하려면 import 문을 통해 명시적으로 불러와야 합니다.

 

1. 패키지 선언

자바 소스파일의 가장 첫 줄에 package 키워드를 사용합니다.

src/
 └── com/
     └── example/
         └── utils/
             └── MathHelper.java
package com.example.utils;

public class MathHelper {
    public static int add(int a, int b) {
        return a + b;
    }
}

 

2. 다른 패키지 클래스 사용

import com.example.utils.MathHelper;

public class Main {
    public static void main(String[] args) {
        int result = MathHelper.add(5, 10);
        System.out.println("결과: " + result);
    }
}

 

전체 패키지를 임포트할 수도 있습니다

import com.example.utils.*;

 

3. 기본 패키지 (default package)

  • 패키지를 명시하지 않으면 자동으로 default package에 속합니다.
  • 하지만 실제 프로젝트에서는 패키지 명시를 강력히 권장합니다.

'백엔드 > Java' 카테고리의 다른 글

접근 제한자  (0) 2025.07.07
상속  (0) 2025.07.07
메서드  (0) 2025.05.26
제어문 - 반목문  (0) 2025.05.26
제어문 - 조건문  (0) 2025.05.26