1. 객체지향 프로그래밍과 클래스

프로그래밍의 큰 흐름을 이해하려면 먼저 절차적 프로그래밍과의 차이를 알아야 한다.

  • 절차적 프로그래밍: 데이터와 그 데이터를 처리하는 함수가 따로 분리되어 있다. 코드가 위에서 아래로 순서대로 실행되는 것이 중요하며, 프로그램이 커질수록 데이터와 함수를 연결하고 관리하기가 매우 어려워진다.
  • 객체지향 프로그래밍 (OOP): 관련된 데이터(변수)와 그 데이터를 다루는 기능(메서드)을 하나의 덩어리로 묶어서 관리하는 방식이다. 이 덩어리를 객체(Object)라고 부른다.
  • 클래스(Class): 객체를 만들기 위한 텍스트 형태의 '설계도'이다.
  • 객체(인스턴스): 설계도(클래스)를 바탕으로 new 키워드를 사용해 컴퓨터 메모리에 실제로 생성된 실체이다.

2. 메서드와 매개변수 (기초 용어)

전사 무기 공격 문제에서 처음 막혔던 부분이다.

  • 메서드 (Method): 클래스 안에 정의된 '기능'이나 '동작'을 수행하는 코드 블록이다. (예: void 공격() { ... })
  • 매개변수 (Parameter): 메서드가 실행되기 위해 외부로부터 전달받아야 하는 값을 담아두는 빈 변수이다. 메서드를 정의할 때 괄호 안에 적는다. (예: void 공격(String 무기이름))
  • 인자 (Argument): 메서드를 실제로 호출(실행)할 때, 매개변수 자리에 던져주는 실제 데이터 값이다. (예: a전사.공격("칼"); 에서 "칼"이 인자이다.)

3. 생성자 (Constructor)의 모든 것

생성자는 객체의 '탄생'과 관련된 아주 중요한 개념이다.

생성자의 정의와 특징

  • 객체가 new 키워드를 통해 메모리에 생성될 때 딱 한 번, 가장 먼저 자동으로 실행되는 특수한 메서드이다.
  • 클래스 이름과 반드시 똑같아야 하고, 리턴 타입(void, int 등)을 적지 않는다.
  • 역할: 객체가 작동하기 위해 필요한 초기 데이터(나이, 이름 등)를 미리 세팅(초기화)하는 역할을 한다.
class Student {
    String name; // 학생의 이름 (데이터)

    // [생성자] : 학생이 태어날 때 이름을 무조건 지어주도록 강제함!
    Student(String inputName) {
        this.name = inputName; // 전달받은 이름을 name 변수에 저장(초기화)
        System.out.println(inputName + " 학생이 입학했습니다!");
    }
}

public class Main {
    public static void main(String[] args) {
        // 'new' 하는 순간 Student 생성자가 자동으로 팍! 실행됩니다.
        Student s1 = new Student("철수"); 
        // 출력: 철수 학생이 입학했습니다!
    }
}

생성자 오버로딩 (Constructor Overloading)

  • 객체를 생성할 때 상황에 따라 다른 초기값을 넣고 싶을 때 사용한다.
  • 매개변수의 개수나 타입을 다르게 해서 생성자를 여러 개 만들어 두는 것을 뜻한다.
  • 예를 들어 사람()으로 호출하면 기본값으로 세팅되고, 사람("홍길동", 20)으로 호출하면 전달한 값으로 세팅되게끔 여러 선택지를 주는 것이다.
class Coffee {
    String name;
    int price;

    // 1. 이름과 가격을 모두 받는 생성자
    Coffee(String name, int price) {
        this.name = name;
        this.price = price;
    }

    // 2. 이름만 받는 생성자 (가격은 3000원 고정) -> 오버로딩
    Coffee(String name) {
        this(name, 3000); // 1번 생성자를 호출해 처리
    }
}

// [사용]
Coffee c1 = new Coffee("라떼", 5000); // 1번 실행
Coffee c2 = new Coffee("생수");       // 2번 실행 (가격은 3000원)

this와 this()

  • this: 메모리에 생성된 객체 자기 자신을 가리키는 키워드이다. 메서드 안에서 객체가 가진 변수(this.이름)를 명확히 지칭할 때 쓴다.
    this: "이거 내 거야!" (변수 가리키기)
class Person {
    String name; // (A) 클래스 멤버 변수

    Person(String name) { // (B) 들어오는 매개변수
        this.name = name; 
        // 왼쪽 this.name은 (A)를 의미하고,
        // 오른쪽 name은 (B)를 의미합니다.
    }
}
  • this(): 내 클래스 안에 있는 다른 생성자를 호출할 때 쓴다. 생성자 오버로딩을 여러 개 해두었을 때, 코드가 중복되는 것을 막기 위해 특정 생성자에게 초기화 작업을 넘겨버리는(위임하는) 역할이다.
    this(): "저쪽 가서 해!" (다른 생성자 호출하기)
class Person {
    String name;
    int age;

    // (1) 이름만 받는 생성자
    Person(String name) {
        this(name, 20); // "야, 내 이름이랑 나이(20) 들고 밑에 (2)번 생성자한테 가서 만들어달라 해!"
    }

    // (2) 이름과 나이를 모두 받는 생성자
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

생성자 연쇄 호출과 super()

  • 상속 관계에서 자식 객체가 생성되려면, 반드시 부모 객체가 먼저 생성되어야 한다.
  • 자식 클래스의 생성자가 실행될 때, 자바는 개발자가 명시하지 않아도 자식 생성자 코드의 맨 윗줄에 super(); 라는 코드를 자동으로 몰래 끼워 넣는다.
  • super()부모 클래스의 생성자를 호출하는 명령어이다. 이를 통해 부모의 데이터가 먼저 초기화(세팅)된 후, 다시 자식 생성자로 돌아와 자식의 데이터가 초기화되는 흐름을 생성자 연쇄 호출이라고 부른다.

4. 다형성: 오버로딩(Overloading) vs 오버라이딩(Overriding)

이름이 비슷해서 가장 많이 헷갈리는 두 용어이므로 완전히 다른 개념임을 정확히 구분해야 한다.

메서드 오버로딩 (Overloading - 과적하다)

  • 하나의 클래스 안에서 발생한다.
  • 같은 이름의 메서드를 매개변수의 개수나 데이터 타입을 다르게 하여 여러 개 정의하는 것을 말한다.
  • 목적: 비슷한 기능을 하는 메서드들의 이름을 하나로 통일해서 개발자가 이름을 여러 개 외울 필요 없게(기억하기 쉽게) 만드는 것이다. (예: 자기소개(), 자기소개(String 이름))

메서드 오버라이딩 (Overriding - 덮어쓰다, 재정의하다)

  • 부모와 자식 클래스 간의 상속 관계에서 발생한다.
  • 부모 클래스에게 물려받은 메서드를 자식 클래스에서 자신의 상황에 맞게 내부 실행 코드만 뜯어고쳐서 다시 정의하는 것을 말한다.
  • 조건: 메서드의 이름, 매개변수 개수와 타입, 리턴 타입이 부모의 것과 100% 똑같아야 한다. 내용물 { }만 바꿀 수 있다.

1. 오버로딩 (Overloading): "옵션 추가"

식당 메뉴판을 생각하세요. '김밥'이라는 이름은 하나지만, 옵션이 여러 개인 상황입니다.

  • 김밥() : 그냥 기본 김밥 (매개변수 없음)
  • 김밥(참치) : 참치 넣은 김밥 (매개변수 1개)
  • 김밥(치즈, 멸치) : 치즈와 멸치 넣은 김밥 (매개변수 2개)

핵심: 손님이 "김밥 주세요"라고 할 때, 뒤에 무슨 말을 덧붙이느냐에 따라 주방장이 알아서 다른 요리를 내오는 것입니다. (같은 클래스 안에서 일어남)


2. 오버라이딩 (Overriding): "기존 메뉴판 수정"

대를 이어 하는 식당을 생각하세요. 부모님이 물려준 레시피가 마음에 안 들어서 내 식대로 바꾸는 겁니다.

  • 부모님: "우리 집 김밥은 오이를 넣는다." (기본 레시피)
  • 자식(나): "요즘 애들은 오이 싫어해. 난 을 넣을 거야!" (레시피 수정)

핵심: 이름(김밥)과 재료 구성은 똑같아야 하지만, 안에 들어가는 내용(실행 코드)만 내 마음대로 싹 바꾸는 것입니다. (상속 관계에서 일어남)


한눈에 비교 (이것만 기억하세요!)

구분오버로딩 (Overloading)오버라이딩 (Overriding)
한마디"이름은 같은데 여러 개 만들기""물려받은 거 내 맘대로 바꾸기"
발생 장소한 클래스 안에서 (나 혼자)부모와 자식 사이에서 (상속)
차이점매개변수가 달라야 함 (옵션 차이)내용물 { }만 달라야 함 (재정의)
비유폰 살 때 (128GB vs 256GB 선택)폰 업데이트 (기능은 같은데 성능 개선)

5. 클래스를 연결하는 두 가지 방법: 상속 vs 포함

페라리와 자동차, 그리고 사람과 팔 문제를 풀 때 사용되는 두 가지 구조이다.

상속 관계 (Inheritance - IS-A 관계)

  • 기존 클래스(부모)의 변수와 메서드를 그대로 물려받아 새로운 클래스(자식)를 확장(extends)하는 방법이다.
  • 성립 조건: "자식은 부모이다 (IS-A)"라는 문장이 논리적으로 맞아야 한다. (예: 페라리는 자동차이다).
  • 참조 변수의 형변환 (업캐스팅/다운캐스팅): 부모 타입의 변수로 자식 객체를 가리킬 수 있다. 하지만 부모 변수 상태일 때는 자식만의 특수 기능을 쓸 수 없다. 자식 전용 기능을 쓰려면 다시 자식 타입으로 변수를 강제로 변환시켜야 한다. (예: (페라리)a자동차)

포함 관계 (Composition - HAS-A 관계)

  • 어떤 클래스의 내부에 다른 클래스 타입의 변수를 선언해서, 마치 부품을 조립하듯 사용하는 방법이다.
  • 성립 조건: "A는 B를 가지고 있다 (HAS-A)"라는 문장이 맞을 때 사용한다. (예: 사람은 팔을 가지고 있다, 사람은 신분증을 가지고 있다).
  • 상속을 쓰기에는 논리적으로 안 맞지만, 다른 클래스의 기능을 가져다 쓰고 싶을 때 사용한다.

6. 추상화: 추상 클래스 vs 인터페이스

둘 다 "자식 클래스야, 이 기능은 네가 반드시 오버라이딩(재정의)해서 완성해라"라고 규칙을 강제하기 위해 만들어진 부모용 설계도이다.

  • 구상 메서드: 내부에 실행될 코드 블록 { }을 가지고 있는 온전한 메서드이다.
  • 추상 메서드: abstract 키워드가 붙으며, 실행될 코드 블록 { } 없이 선언만 되어 있는 껍데기 메서드이다. (예: abstract void 작동();)

추상 클래스 (Abstract Class)

  • 클래스 앞에 abstract가 붙는다. 미완성 설계도라서 new를 통해 직접 객체를 생성할 수 없다.
  • 내부에 일반 변수, 구상 메서드, 추상 메서드를 모두 섞어서 가질 수 있다. (추상 메서드가 없어도 추상 클래스가 될 수는 있다).
  • 자바는 클래스의 다중 상속을 막아두었기 때문에, 오직 하나만 상속(extends) 받을 수 있다.

인터페이스 (Interface)

  • interface 키워드를 사용한다. 순도 100%의 추상 클래스라고 보며, 객체 생성은 불가능하다.
  • 내부에는 변수 대신 값을 변경할 수 없는 상수(final)와 실행 코드가 없는 추상 메서드만 들어갈 수 있다. (구상 메서드나 일반 변수는 절대 불가능하다).
  • 가장 큰 특징은 다중 상속(implements)이 가능하다는 것이다. 하나의 클래스가 여러 개의 인터페이스를 콤마(,)로 연결해서 동시에 상속받아 다양한 규칙을 부여받을 수 있다.
특징추상 클래스 (Abstract Class)인터페이스 (Interface)
추상성0% ~ 100% (일반 기능도 섞일 수 있음)100% (오직 추상적인 껍데기만 존재)
구성 요소일반 변수, 구상 메서드, 추상 메서드 혼용 가능오직 상수와 추상 메서드만 허용
상속 키워드extends (확장)implements (구현)
다중 상속불가능 (단일 상속만 허용)가능 (여러 개 동시 상속 허용)


추상과 구상의 차이

1. 추상 (Abstract) : "말만 하고 행동은 안 함"

추상은 어떤 사물이나 개념의 공통적인 특징(이름, 기능의 정의)만 뽑아내고, 실제 어떻게 작동할지는 정하지 않은 상태를 말한다.

  • 특징: 메서드의 이름과 매개변수는 있지만, 실제 실행되는 코드 블록 { }이 없다.
  • 자바에서의 표현: abstract 키워드를 사용한다.
  • 비유: 상사가 부하직원에게 "보고서 써와"라고 말하는 것과 같다. '보고서 작성'이라는 행동의 이름은 정해졌지만, 한글로 쓸지 워드로 쓸지, 내용은 무엇일지는 아직 정해지지 않은 상태이다.
  • 존재 이유: 자식 클래스들에게 "이 기능은 무조건 만들어야 해!"라고 규칙(가이드라인)을 강제하기 위해서이다.
abstract class 무기 {
    abstract void 작동(); // 추상 메서드: 이름만 있고 몸통{ }이 없음
}

2. 구상 (Concrete) : "실제로 행동함"

구상은 추상적인 개념을 바탕으로 실제로 어떻게 움직일지 코드를 꽉 채워 넣은 상태를 말한다. 우리가 지금까지 써온 일반적인 클래스와 메서드가 모두 '구상'에 해당한다.

  • 특징: 메서드 뒤에 { }가 붙고, 그 안에 실제 실행될 코드(System.out.println 등)가 들어있다.
  • 자바에서의 표현: 별도의 키워드 없이 일반적인 클래스와 메서드로 작성한다. (추상 메서드를 오버라이딩해서 몸통을 채우면 구상 메서드가 된다.)
  • 비유: 부하직원이 상사의 명령을 듣고 "워드 프로그램을 켜서 매출 현황 보고서를 10페이지 작성함"이라는 구체적인 행동을 하는 것과 같다.
  • 존재 이유: 실제로 프로그램이 돌아가서 결과물을 만들어내기 위해서이다.
classextends 무기 {
    void 작동() { // 구상 메서드: 부모의 추상을 이어받아 몸통{ }을 채움
        System.out.println("칼로 베기!"); 
    }
}

3. 추상 vs 구상의 결정적 차이점

구분추상 (Abstract)구상 (Concrete)
완성도미완성 (설계도 단계)완성 (제품 단계)
코드 블록없음 ( ; 으로 끝남 )있음 ( { ... } )
객체 생성불가능 (new 못함)가능 (new 가능)
목적표준화, 규칙 강제, 공통점 추출실제 기능 구현, 데이터 처리
위치주로 부모 클래스(추상클래스, 인터페이스)주로 자식 클래스(일반 클래스)

4. 왜 섞어서 쓰는 걸까? (연계 설명)

질문했던 코드에서 "추상 메서드를 구상 메서드로 바꾸고 생성자를 활용하라"는 말의 의미는 다음과 같다.

  1. 추상으로 시작: 처음에는 무기라는 공통 개념만 잡기 위해 abstract void 작동();으로 시작한다.
  2. 구상으로 통합: 모든 무기가 똑같은 문장을 출력하는 경우, 굳이 자식들에게 시킬 필요 없이 부모가 직접 구상 메서드({ ... })로 내용을 채워버리는 것이다.
  3. 차이점만 생성자로: 대신 무기 이름이 "칼"인지 "활"인지 다르므로, 그 이름만 생성자를 통해 자식한테 받아와서 부모의 구상 메서드에서 사용하는 것이다.

정리하자면:

  • 추상화시킨다: 공통점을 찾아서 껍데기를 만든다.
  • 구상화시킨다: 실제로 돌아가는 알맹이 코드를 짠다.

이 '추상'과 '구상'의 개념이 이해되면 자바에서 클래스 구조를 복잡하게 나누는 이유를 알 수 있게 된다. "나(부모)는 큰 틀(추상)만 잡을 것이니 너(자식)는 구체적인 내용(구상)을 채워라" 또는 "내가 다 짜놨으니(구상) 너는 이름만 알려줘" 같은 대화가 코드 속에서 이루어지는 것이다.

profile
성심당마스코트

0개의 댓글