공부/Java

[Java-07] 객체지향프로그래밍Ⅱ - 다형성, 추상클래스

줭♪(´▽`) 2021. 3. 2. 14:40

1. 다형성(Polymorphism)

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() { // caption을 출력하는 내용 }
}

class Main {
    public static void main(String[] args) {
    	// (1)
    	Tv t = new Tv();
        CaptionTv c = new CaptionTv();
        
        // (2)
        Tv t2 = new CaptionTv();
    }
}

(1) 참조변수 타입 = 인스턴스 타입

(2) 참조변수 타입은 조상 클래스(Tv) , 인스턴스 타입은 자식 클래스(CaptionTv)

-> 꼭 타입이 같지 않아도 됨

 

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

(1) c는 CaptionTv인스턴스의 모든 멤버를 사용할 수 있음

(2) t는 CaptionTv인스턴스의 모든 멤버를 사용할 수 없음, Tv클래스의 멤버들(상속받은 멤버 포함)만 사용 가능

-> 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라짐

 

c와 t의 인스턴스, 색이 칠해진 멤버만 사용 가능

 

CaptionTv c = new Tv();		// 컴파일 에러!!

-> 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 불가능함

-> 조상타입에는 존재하지 않는 멤버를 사용하고자 할 가능성이 있기 때문

-> 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 함

 

 

1. 조상타입의 참조변수로 자손타입의 인스턴스를 참조 (O)
2. 자손타입의 참조변수로 조상타입의 인스턴스를 참조 (X)

 

 

2) 참조변수의 형변환

 

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

업캐스팅(Up-casting) <-> 다운캐스팅(Down-casting)

 

 

class Car {
	...
}

class FireEngine extends Car {
	...
}

class Ambulance extends Car {
	...
}


class Main {
    public static void main(String[] args) {
    	// (1) 상속관계가 아닌 클래스 간의 형변환 불가능
        FireEngine f;
        Ambulance a;
        a = (Ambulance)f;		// 에러
        f = (FireEngine)a;		// 에러
        
        // (2) 업캐스팅
        Car c = null;
        FireEngine fe = new FireEngine();
        c = fe;					// 형변환 생략, 업캐스팅
        
        // (3) 다운캐스팅\
        Ambulance am = null;
        am = (Ambulance)c;		// 형변환 필수, 다운캐스팅
    }
}

- 참조변수간의 형변환 역시 캐스트연산자()를 사용함

- 형변환은 참조변수의 타입을 변환하는 것 (O), 인스턴스를 변환하는 것 (X) 이므로 인스턴스에 아무런 영향을 미치지 않음

- 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것뿐임

 

 

※ 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않음

...
    Car car = new Car();
    FireEngine fe = null;
    
    fe = (FireEngine)car;	// 실행 시 에러 발생!
...

 

 

3) instanceof 연산자

- 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용

- 주로 조건문에 사용

 

 

- 연산의 결과로 true/false 반환

true : 참조변수가 검사한 타입으로 형변환이 가능함

false : 참조변수가 검사한 타입으로 형변환이 불가능함 또는 참조변수의 값이 null임

...
    if (c instanceof FireEngine) {
    	...
    }
    
    else if (c instanceof Ambulance) {
    	...
    }
...

 

 

4) 참조변수와 인스턴스의 연결

- 조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의했을 때, 인스턴스의 타입에 따라 서로 다른 결과를 얻음

- 메서드 : 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출

- 멤버변수 : 참조변수의 타입에 따라 달라짐

 (1) 조상타입의 참조변수 사용 : 조상 클래스에 선언된 멤버변수 사용

 (2) 자손타입의 참조변수 사용 : 자손 클래스에 선언된 멤버변수 사용

 

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");
}

class Main {
    public static void main(String[] args) {
    	Parent p = new Child();
        Child c = new Child();
        
        System.out.println(p.x);
        p.method();
        
        System.out.println(c.x);
        c.method();
        
        
        /* 출력결과
        100
        Child Method
        200
        Child Method
        */
}

 

 

5) 매개변수의 다형성

- 참조변수의 다형적인 특징은 메서드의 매개변수에도 적용됨

class Product {
    int price;
}

class Tv extends Product { }
class Computer extends Product { }

class Buyer {
	int money = 200;
    // void buy(Tv t) { money -= t.price; }
    // void buy(Computer c) { money -= c.price; }
    // 조상 클래스인 Product 타입을 매개변수로 하여 간단히 처리 가능
    void buy(Product p) { money -= p.price; }
}

 

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

- 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있음

class Buyer {
    int money = 1000;
    Product[] item = new Product[10];
    int i = 0;
    
    void buy(Product p) {
    	itme[i++] = p;
    }
}

 

- 배열 크기가 가변적인 것은 Vector 클래스를 사용함

- Vector 클래스 : 동적으로 크기가 관리되는 객체배열

 

메서드 / 생성자 설 명
Vector() 10개의 객체를 저장할 수 있는 Vector인스턴스를 생성
10개 이상의 인스턴스가 저장되면, 자동으로 크기 증가됨
boolean add(Object o) Vector에 객체를 추가함
추가에 성공하면 true를, 실패하면 false를 반환함
boolean remove(Object o) Vector의 객체를 제거함
제거에 성공하면 true를, 실패하면 false를 반환함
boolean isEmpty() Vector가 비어있으면 true를, 비어있지 않으면 false를 반환함
Object get(int index) 지정된 위치(index)의 객체를 반환
반환타입이 Object타입이므로 형변환 필요
int size() Vector에 저장된 객체의 개수 반환

 


 

2. 추상클래스(Abstract class)

1) 추상클래스란?

- 미완성된 클래스, 즉 미완성 메서드(추상메서드)를 포함하고 있는 클래스

- 인스턴스를 생성할 수 없음

- 상속을 통해 자손클래스에 의해서만 완성될 수 있음

 

Q. 추상클래스는 왜 사용하는가?

A. 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 가짐

 

abstract class 클래스명 {
    ...
}

 

2) 추상메서드(abstract method)

- 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 메서드

 

Q. 추상메서드는 왜 구현부를 작성하지 않는가?

A. 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 실제 내용(구현부)은 상속받는 클래스에서 구현하도록 비워두는 것임

 

/* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명함 */
abstract 리턴타입 메서드이름();

 

- 만약 조상의 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해줘야 함

abstract class Player {	// 추상클래스
    abstract void play(int pos);    	// 추상메서드1
    abstract void stop();		// 추상메서드2
}

class Player1 extends Player {
    void play(int pos) { ... }  	// 추상메서드1 구현
    void stop() { ... }			// 추상메서드2 구현
}

abstract class Player2  extends Player { // 추상클래스
    void play(int pos) { ... }  // 추상메서드1 구현
    // 추상메서드2 구현X -> 추상클래스가 됨
}

 

 

- 추상클래스는

 (1) 여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고,

 (2) 기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들기도 함

 

※ 추상화와 구체화의 차이

추상화
: 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 : 상속을 통해 클래스를 구현, 확장하는 작업

 

 

 

 

 

 

 

 

참고 - Java의 정석 3rd Edition (저자 : 남궁성, 출판 : 도우출판)