본문 바로가기
TIL/Java | Spring Boot

[Java] 스터디 2주차_객체지향프로그래밍2 (1) - 상속, 오버라이딩, 제어자, 다형성

by yeon_zoo 2022. 5. 1.

1. 상속

1.1 상속의 정의와 장점

상속 : 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것

상속의 장점 : 

  • 적은 양의 코드로 새로운 클래스 작성이 용이
  • 코드를 공통적으로 관리 -> 추가 및 변경이 매우 용이

상속하는 방법

class Child extends Parent {
	...
}

Parent 클래스 (상속해주는 클래스)  = 조상 클래스 (혹은 부모 클래스, 상위 클래스, 기반 클래스)

Child 클래스 (상속 받는 클래스) = 자손 클래스 (혹은 자식 클래스, 하위 클래스, 파생된 클래스)

위의 예시를 상속 계층도로 그리면 다음과 같다. 

클래스는 타원으로, 클라스 간의 상속 관계는 화살표로 나타낸다. 

 

자손클래스는 조상 클래스의 모든 멤버를 상속받기 때문에 Child 클래스는 Parent 클래스의 멤버(변수, 메서드)들을 포함한다고 표현할 수 있다. 하지만 생성자와 초기화 블럭은 상속되지 않는다. 결론적으로 자손 클래스의 멤버 개수는 항상 부모 클래스보다 같거나 많다. 

 

예를 보자면 다음과 같다. 

class Parent{
	int age;
}

class Child extends Parent {
	void play() {
    	System.out.println("놀자~")
    }
}

class Child2 extends Parent { }
class GrandChild extends Child { }

위의 코드에서 각 클래스가 가지고 있는 멤버(상속된 것 포함)을 정리해보면 다음과 같다. 

클래스 클래스의 멤버
Parent age
Child age, play()
Child2 age
GrandChild age, play()

클래스 간의 상속 관계를 맺어 주면 자손 클래스들의 공통적인 부분은 조상 클래스에서 관리하고 자손 클래스는 자신에 정의된 멤버들만 관리하면 되므로 각 클래스의 코드가 적어져서 관리가 쉬워진다. 전체 프로그램을 구성하는 클래스들을 적절히 상속관계를 맺어주는 것이 객체 지향 프로그래밍에서 가장 중요한 부분이다. 


1.2 클래스 간의 관계 - 포함관계

클래스를 재사용하는 두 번째 방법은 포함관계를 맺어주는 것이다. 

포함관계 : 한 클래스의 멤버 변수로 다른 클래스 타입의 참조변수를 선언하는 것.

// 예
class Circle {
    Point c = new Pointer();
    int r; // 반지름
}

class Pointer{
	int x; // x좌표
    int y; // y좌표
}

단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함관계로 재사용하면 보다 간결하고 손쉽게 클래스를 작성할 수 있다. 또한 작성된 단위 클래스들은 다른 클래스를 작성하는데 재사용될 수 있다. 

 

1.3 클래스 간의 관계 결정하기

클래스 간의 관계를 상속관계로 정할지 포함관계로 정할지 헷갈리기 쉽다. 이럴 때는 클래스를 말로 표현해 보면 된다. 

  • 상속관계 : '~은 ~이다. (is-a)'
  • 포함관계 : '~은 ~을 가지고 있다. (has-a)'

예를 들어 위의 Circle과 Pointer 클래스 사이의 관계를 보면 다음과 같다.

  • 원(Circle)은 점(Point)이다.
  • 원은 점을 가지고 있다.
// 예제
public class DeskTest {
    public static void main(String[] args) {
        Deck d = new Deck();
        Card c = d.pick(0);
        System.out.println(c);

        d.shuffle();
        c = d.pick(0);
        System.out.println(c);
    }
}

class Deck {
    final int CARD_NUM = 52;
    Card cardArr[] = new Card[CARD_NUM];

    Deck() {
        int i = 0;

        for (int k = Card.KIND_MAX; k > 0; k--){
            for (int n = 0; n < Card.NUM_MAX; n++){
                cardArr[i++] = new Card(k, n+1);
            }
        }
    }

    Card pick(int index){
        return cardArr[index];
    }

    Card pick(){
        int index = (int) (Math.random() + CARD_NUM);
        return pick(index);
    }

    void shuffle(){
        for (int i=0; i < cardArr.length; i++){
            int r = (int) (Math.random() * CARD_NUM);

            Card temp = cardArr[i];
            cardArr[i] = cardArr[r];
            cardArr[r] = temp;
        }
    }
}

class Card{
    static final int KIND_MAX = 4;
    static final int NUM_MAX = 13;

    static final int SPADE = 4;
    static final int DIAMOND = 3;
    static final int HEART = 2;
    static final int CLOVER = 1;
    int kind;
    int number;

    Card(){
        this(SPADE, 1);
    }

    Card(int kind, int number){
        this.kind = kind;
        this.number = number;
    }

    public String toString(){
        String[] kinds = {"", "CLOVER", "HEART", "DIAMOND", "SPADE"};
        String numbers = "0123456789JQK";
        return "kind : " + kinds[this.kind]
                + ", number : " + numbers.charAt(this.number);
    }
}

※ cardArr은 카드객체 배열이다. 이 때 카드 객체의 참조변수 배열이기 때문에 배열에 실제로 저장된 것은 객체 자체가 아닌 객체의 주소이다. 즉, cardArr[0]에 저장된 값은 0x200 과 같은 주소값이라는 것이다. 

 

※ toString()은 모든 클래스의 조상인 Object에 정의된 것으로, 어떤 종류의 객체에 대해서도 toString()을 호출하는 것이 가능하다. 참조변수의 출력이나 덧셈 연산자를 이용한 참조변수와 문자열의 결합에는 toString()이 자동으로 호출되어 참조변수를 문자열로 대치한 후 처리한다. 


1.4 단일 상속

자바에서는 다중상속이 불가하고 단일 상속만을 허용한다. 

  • 다중상속의 장점 : 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있음. 
  • 다중상속의 단점 : 클래스간의 관계가 매우 복잡해지고, 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 없음.

따라서 단일 상속만을 허용하여 클래스 간의 관계를 명확히하고 코드를 더욱 신뢰할 수 있게 만들어준다. 

대신 클래스를 포함관계로 만들고 메서드를 일치하는 이름으로 만든다면 용이하게 사용할 수 있다. 아래 예시를 보면 도움이 될 것이다. 

package javaStudy;

class TV {
    boolean power;
    int channel;
    
    void power(){ power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}

class VCR {
    boolean power;
    int counter = 0;
    void power() { power = !power; }
    void play() { /*내용 생략*/ }
    void stop() { /*내용 생략*/ }
}

public class TVCR extends TV{
    VCR vcr = new VCR();
    
    void play()  {
        vcr.play();
    }
    
    void stop(){
        vcr.stop();
    }
}


1.5 Object 클래스 - 모든 클래스의 조상

앞서 말했듯이 모든 클래스는 Object라는 클래스를 자동 상속받는다. 따라서 모든 상속계층도의 최상위에는 Object 클래스가 위치한다. Object 클래스에서 정의된 toString()이나 equals(Object o)를 따로 정의하지 않고 모든 클래스에서 사용할 수 있었던 것도 같은 이유에서 이다. 


2. 오버라이딩(overriding)

2.1 오버라이딩이란?

((추후 추가))

 

3. package와 import

이 부분은 간단하므로 생략한다. 

4. 제어자

4.1 제어자란?

접근 제어자 : public, protected, default, private

그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

제어자는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다. 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하지만 접근 제어자는 네 가지 중 하나만 선택해서 사용할 수 있다. 제어자들 간의 선언 순서는 상관이 없지만 보통 쩝근 제어자를 가장 먼저 적는 경향이 있다.

 

4.2 static - 클래스의, 공통적인

대상 의미
static 멤버변수 - 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
- 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능하다.
- 클래스가 메모리에 로드될 때 생성된다.
static 메서드 - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다.
- static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다. 

클래스변수(static멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다. 하나의 변수를 모든 인스턴스가 공유하는 것이다. 또한 static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용이 가능하다. static은 멤버변수, 메서드, 초기화 블럭에 사용이 가능하다. 주로 클래스변수를 초기화하는 데 사용된다.

 

4.3 final - 마지막의, 변경될 수 없는

대상 의미
final 클래스 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. 
어떤 클래스가 final 이라면 그 클래스는 다른 클래스의 조상이 될 수 없다.
final 메서드 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
final 멤버변수 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다. 
final 지역변수

final이 변수에 사용되면 값을 변경할 수 없는 상수가 되고, 메서드에 사용되면 오버라이딩 할 수 없게 되며 클래스에 사용되면 자손클래스를 정의할 수 없다. final은 클래스, 메서드, 멤버변수, 지역변수에 사용될 수 있다. 

 

final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 한다. 하지만 인스턴스 변수의 경우 생성자에서 초기화되도록 할 수도 있다. 

class Card {
    final int NUMBER;
    final String KIND;
    static int width = 100;
    static int height = 250;
    
    Card(String kind, int num){
        KIND = kind;
        NUMBER = num;
    }
    
    Card(){
        this("HEART", 1);
    }
}

이 기능을 이용하면 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖도록 하는 것이 가능하다. 만약 이것이 불가능하다면 클래스에 선언된 final이 붙은 인스턴스 변수는 모든 인스턴스에서 같은 값을 가져야 할 것이다. 

 

4.4 abstract - 추상의, 미완성의

대상 의미
abstract 클래스 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
abstract 메서드 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.

abstract는 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다. 그리고 클래스에 사용되면 클래스 내에 추상 메서드가 존재한다는 것을 쉽게 알 수 있게 한다. 추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도'이므로 인스턴스를 생성할 수 없다. 

 

가끔 추상 메서드가 없는 클래스, 즉 완성된 클래스도 abstract를 붙여서 추상 클래스로 만드는 경우도 있다. (예 : java.awt.event.WindowAdaptor) 아래의 예시 같은 경우는 인스턴스를 생성해도 할 수 있는 것이 없다. 따라서 인스턴스를 생성하지 못하게 클래스 앞에 제어자 'abstract'를 붙여둔 것이다. 

이 클래스 자체로는 쓸모 없어 보이지만, 다른 클래스가 이 클래스를 상속받아서 일부의 원하는 메서드만 오버라이딩 해도 된다는 장점이 있다. 만일 이 클래스가 없다면 아무런 내용도 없는 메서드를 잔뜩 오버라이딩해야 한다. (인터페이스가 여기에 해당한다?)

 

4.5 접근 제어자

접근 제어자가 사용될 수 있는 곳은 클래스, 멤버변수, 메서드, 생성자이며 기본값은 default 이지만 실제로 default를 붙이지는 않는다. 클래스, 멤버변수, 메서드, 생성자에 접근 제어자가 지정되어 있지 않다면 default임을 뜻한다. 

제어자 같은 클래스 같은 패키지 자손 클래스 전체
public O O O O
protected O O O  
(default) O O    
private O      

※ 자손 클래스는 다른 패키지 내의 자손 클래스도 포함하여 의미한다.

public은 접근 제한이 전혀 없는 것이고, private은 같은 클래스 내에서만 사용하도록 제한하는 가장 높은 제한이다. 그리고 default는 같은 패키지 내의 클래스에서만 접근이 가능하도록 하는 것이다. 마지막으로 protected는 패키지에 관계없이 상속관계에 있는 자손클래스에서 접근할 수 있도록 하는 것이 제한 목적이지만, 같은 패키지 내에서도 접근이 가능하다. 따라서 default보다 접근 범위가 넓다. 

대상 사용 가능한 접근 제어자
클래스 public, (default)
메서드 public, protected, (default), private
멤버 변수
지역 변수 없음

 

접근 제어자를 이용한 캡슐화

접근 제어자를 이용하는 이유는 클래스의 내부에서 선언된 데이터를 보호하기 위함이다. 데이터가 유효한 값을 유지하도록, 혹은 비밀번호와 같은 데이터를 외부에서 함부로 변경할 수 없도록 외부로부터 접근을 제한하는 것이 필요하다. 이를 '데이터 감추기'라고 하며 객체지향개념의 캡슐화에 해당하는 내용이다. 

또 다른 이유는 클래스 내에서만 사용되는 멤버변수나 부분작업을 처리하기 위한 메서드 등의 멤버들을 클래스 내부에 감추기 위해서이다. 외부에서 접근할 필요가 없는 멤버들을 private으로 지정하여 외부에 노출시키지 않음으로써 복잡성을 줄일 수 있다. 이것도 역시 캡슐화에 해당한다. 

 

다음의 예시를 확인해볼 수 있다. 

package javaStudy;

public class Time {
    private int hour;
    private int minute;
    private int second;

    public int getHour() {
        return hour;
    }

    public void setHour(int hour) {
        if (hour < 0 || hour > 23) return;
        this.hour = hour;
    }

    public int getMinute() {
        return minute;
    }

    public void setMinute(int minute) {
        if (minute < 0 || minute > 59) return;
        this.minute = minute;
    }

    public int getSecond() {
        return second;
    }

    public void setSecond(int second) {
        if (second < 0 || second > 59) return;
        this.second = second;
    }
}

만약 Public class Time에 public 변수로 hour, minute, second가 있다면 이 클래스의 인스턴스를 생성한 다음, 멤버변수에 직접 접근하여 값을 변경할 수 있다. 이렇게 되면 hour의 값이 30 과 같이 잘못된 값을 가질 수 있고, 이를 제어할 수 있는 방법이 없다. 따라서 멤버변수를 private으로 인스턴스 생성하면서 수정할 수 없도록 해두고, 대신에 값을 설정하는 getter 메서드를 public으로 설정하여 유효한 값만 객체에 담을 수 있도록 하는 것이 바람직하다. 

만일 상속을 통해 확장될 것이라고 예상되는 클래스라면 멤버 변수를 private 대신 protected를 써주면 된다. 접근 제한을 주되 자손클래스에서 접근하는 것은 허용하도록 하는 것이다. 

 

※ 참고로 하나의 소스파일(*.java)에는 public 클래스가 단 하나만 존재할 수 있으며, 소스파일의 이름은 반드시 public 클래스의 이름과 같아야 한다. 

 

생성자의 접근 제어자

생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다. 보통 생성자의 접근 제어자는 클래스의 접근 제어자와 동일하지만, 다르게 지정할 수도 있다. 생성자의 접근 제어자를 private으로 설정한다면 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 된다. 대신 클래스 내부에서 인스턴스를 생성할 수 있다. 클래스 내부에서 인스턴스를 생성하는 메서드는 public으로 제공해야 외부에서 이 클래스의 인스턴스를 사용하도록 할 수 있다. 이 메서드는 public 이면서 동시에 static이어야 한다. (인스턴스가 미리 생성되어야 하기 때문이다.)

final class Singleton{
    private static Singleton singleton = new Singleton();

    private Singleton () {
        //...
    }

    public static Singleton getInstance(){
        if (singleton == null){
            singleton = new Singleton();
            return singleton;
        }
    }
}


public class SingletonTest {
    public static void main(String[] args) {
//        Singleton s = new Singleton();	// 에러 발생
        Singleton s = Singleton.getInstance();
    }
}

이처럼 생성자를 통해 직접 인스턴스를 생성하지 못하게 하고 public메서드를 통해 인스턴스에 접근하게 함으로써 사용할 수 있는 인스턴스의 개수를 제한할 수 있다. (대표적인 예가 싱글톤 디자인 패턴이다!)

 

또, 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없다. 자손클래스의 인스턴스를 생성할 때 조상클래스의 생성자를 호출해야만 하는데, 생성자의 접근 제어자가 private이므로 자손 클래스에서 호출하는 것이 불가능하기 때문이다. 이런 경우 클래스 앞에 final을 추가해서 상속할 수 없는 클래스라는 것을 알리는 것이 좋다. 

 

4.6 제어자의 조합

대상 사용 가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근 제어자, final, abstract, static
멤버변수 모든 접근 제어자, final, static
지역변수 final

제어자를 조합해서 사용할 때의 주의사항은 다음과 같다.

1. 메서드에 static과 abstract를 함께 사용할 수 없다. 

  - static은 몸통이 있는 메서드에만 사용할 수 있기 때문이다.

2. 클래스에 abstract와 final을 동시에 사용할 수 없다.

  - 클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract느 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순된다.

3. abstract 메서드의 접근 제어자가 private일 수 없다. 

  - abstract 메서드는 자손클래스에서 구현해주어야 하는데 접근 제어자가 private이라면, 자손클래스에서 접근할 수 없기 때문이다.

4. 메서드에 private과 final을 같이 사용할 필요는 없다. 

  - 접근 제어자가 private인 메서드는 오버라이딩될 수 없기 때문이다. 이 둘 중 하나만 사용해도 의미가 충분하다. 

 

5. 다형성

5.1 다형성이란?

다형성은 상속과 깊은 관계가 있다.

다형성 : '여러 가지 형태를 가질 수 있는 능력'을 의미. 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현. (= 조상 클래스 타입의 참조 변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것)

class TV {
    boolean power;
    int channel;

    void power(){ power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}

class CaptionTv extends  TV{
    String text;
    void caption(){ /* 내용 생략 */ }
}

위와 같은 코드가 있을 때, 아래와 같이 선언 가능하다.

// 참조변수 변수명 = new 클래스
TV t = new CaptionTv();
CaptionTv c = new CaptionTv();

이 경우 t는 CaptionTv만의 멤버인 text와 caption() 사용 불가하다. c와 t는 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라지게 된다. 

 

※참고 : 아래의 코드는 불가능하다. 

CaptionTv c = new TV();

실제 인스턴스인 TV의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다. (참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.)

 

-> 인스턴스의 타입과 일치하는 참조변수를 사용하면 인스턴스의 멤버들을 모두 사용할 수 있을텐데 왜 조상타입의 참조변수를 사용해서 인스턴스의 일부 멤버만을 사용하도록 할까?

5.2 참조변수의 형변환

  • 자손타입 -> 조상타입(Up-Casting) : 형변환 생략가능
  • 자손타입 <- 조상타입(Down-Casting) : 형변환 생략불가능

up-casting의 경우는 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 문제가 되지 않는다. 반면 반대의 경우는 실제 인스턴스의 멤버 개수보다 참조변수가 사용할 수 있는 멤버의 개수가 많아지므로 문제가 발생할 수 잇다. 

 

형변환은 참조변수의 타입을 변환할 뿐 인스턴스를 변환하는 것은 아니기 때문에 인스턴스에는 아무런 영향을 미치지 않는다. 앞선 예시의 클래스에서 다음과 같이 선언했다면 c는 여전히 text와 caption()을 사용할 수 있는 CaptionTv의 인스턴스라는 것이다. 

CaptionTv c = new CaptionTv();
Tv t = (Tv) c;

※ 단, 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것은 허용되지 않는다. 

Tv t = new Tv()
CaptionTv c = null;

c = (CaptionTv) t; // 컴파일은 가능하지만 실행 시 ClassCastException 이 발생

5.3 instanceof 연산자

instanceof는 다음과 같이 사용할 수 있다. 

class Car {}
class FireEngine extends Car {}
class CarTest{
	public static void main (String args[]){
    	FireEngine fe = new FireEngine();
        fe instanceof FireEngine; // true
        fe instanceof Car; // true
        fe instance of Object; // true
    }
}

어떤 타입에 대한 instanceof 연산의 결과가 true라는 것은 검사한 타입을 형변환이 가능하다는 것을 뜻한다. 

 

5.4 참조변수와 인스턴스의 연결

멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우,  조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고 자손타입의 참조변수를 사용했을 때는 자손클래스에 선언된 멤버변수가 사용된다. 메서드의 경우는 조상클래스의 메서드를 오버라이딩한 경우에도 참조 변수의 타입과 무관하게 실제 인스턴스의 메서드가 호출된다. 

public class BindingTest {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        System.out.println("p.x = " + p.x); // 100
        p.method(); // p는 Child의 인스턴스이므로 오버라이딩 된 Child의 method() 호출
        System.out.println("c.x = " + c.x); // 200
        c.method();
        
    }
}

class Parent {
    int x = 100;
    void method(){
        System.out.println("Parent Method");
    }
}

class Child extends Parent {
    int x = 200;
    void method(){
        System.out.println("Child method");
    }
    void method2(){
    	System.out.println("super.x = " + super.x);  // Parent의 x
        System.out.println("this.x = " + this.x);	// Child의 x
    }
}

 

자손클래스에서 선언된 인스턴스 변수 x와 조상클래스로부터 상속받은 인스턴스 변수 x를 구분하는데는 참조변수 super와 this가 사용된다. super은 부모 클래스에서 선언된 x를, this는 해당 클래스 내에서의 인스턴스 변수를 의미한다. 

 

하지만 주로 멤버 변수들은 private으로 접근을 제어하고 setter를 이용하여 접근할 수 있도록 한다. 예제에서 알 수 있듯이 인스턴스변수에 직접 접근하면 참조변수의 타입에 따라 사용되는 인스턴스 변수가 달라질 수 있으니 주의해야 한다. 

 

5.5 매개변수의 다형성

package javaStudy;

public class PolyArgumentTest {
}
class Product {
    int price;
    int bonusPoint;
    
    Product (int price){
        this.price = price;
        bonusPoint = (int) (price/10.0);
    }
}

class Tv extends Product{
    Tv(){
        super(100);
    }
    public String toString(){
        return "Tv";
    }
}

class Computer extends Product{
    Computer(){
        super(200);
    }
    public String toString(){
        return "Computer";
    }
}

class Buyer {
    int money;
    int bonusPoint = 0;
    
    void buy(Product p){
        if (money < p.price) {
            System.out.println("잔액 부족");
            return;
        }
        money -= p.price;
        bonusPoint += p.bonusPoint;
        System.out.println(p + "원을 구입하셨습니다");
    }
}

이런 구성을 가능하게 하는 것이 매개변수의 다형성이다. 또 다른 예로는 PrintStream 클래스에 정의되어 있는 print(Object obj) 메서드이다. 

 

5.6 여러 종류의 객체를 배열로 다루기

Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

위와 같은 경우로 배열로도 다룰 수 있다. 이 때 Product 배열의 크기를 3으로 설정해뒀기 때문에 4개 이상의 제품을 추가할 수는 없는 문제가 있다. 이럴 경우 Vector클래스를 사용하면 된다. Vector 클래스는 내부적으로 Object 타입의 배열을 가지고 있어서 이 배열에 객체를 추가하거나 제거할 수 있게 작성되어 있다. 또한 배열의 크기를 알아서 관리해주시 때문에 저장할 인스턴스의 개수에 신경쓰지 않아도 된다.

Vector의 메서드는 다음과 같다. 

  • boolean add(Object o) : true는 추가에 성공
  • boolean remove (Object o) : true는 삭제에 성공
  • isEmpty() : true는 비어 있음
  • Object get(int index) 
  • int size()

다음 글 확인하기: https://challenging.tistory.com/100

댓글