[JAVA] 다형성 (Polymorphism)

Coastby·2022년 9월 26일
0

LIKELION Back-End School

목록 보기
18/61

○ 다형성이란?

💡 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다.

  • 객체들의 타입이 다르면 똑같은 메세지 (같은 이름의 함수)가 전달되더라도 서로 다른 동작을 하는 것
  • 조상 클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다.
  CaptionTv() c = new CaptionTv(); 
   Tv 	     t = new CaptionTv();	//조상 타입의 참조변수로 자손 인스턴스를 참조
  • 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

  • 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 불가능하다.
  • 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
    • 모든 참조변수는 null 또는 4 byte의 주소값이 저장되며, 참조변수의 타입은 참조할 수 있는 객체의 종류와 사용할 수 있는 멤버의 수를 결정한다.

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

  • 멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우,
    • 조상타입의 참조변수를 사용 : 조상 클래스에 선언된 멤버변수
    • 자손타입의 참조변수를 사용 : 자손 클래스에 선언된 멤버변수 사용
  • 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조 변수의 타입과 상관없이 항상 실제 인스턴스의 메서드 (오버라이딩된 메서드)가 호출된다.
public class Test {
    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();
    }

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

💡 하지만, 멤버 변수들은 주로 private으로 접근을 제한하고, 외부에서는 메서드를 통해서만 멤버변수에 접근할 수 있도록 한다. 대부분 다른 외부 클래스에서 참조변수를 통해 직접적으로 인스턴스 변수에 접근할 수 있게 하지 않는다.
인스턴스 변수에 직접 접근하면, 참조변수의 타입에 따라 사용되는 인스턴스변수가 달라질 수 있으므로 주의해야 한다.


○ 다형성 예제

class Shape {
	
	Shape() {
		System.out.println("도형입니다.");
	}
	
	public void draw () {
		System.out.println("도형을 그립니다.");
	}
}

class Rectangle extends Shape {
	Rectangle () {
		System.out.println("사각형 생성자");
	}
	
	@Override
	public void draw() {
		System.out.println("사각형을 그립니다.");
	}
	
	public void draw2() {
		System.out.println("사각형을 그립니다.2222");
	}
}

public class PolyTest {

	public static void main(String[] args) {
		
		//1번
		Shape shape = new Shape();
		shape.draw();
		
		//2번
		Rectangle rec = new Rectangle();
		rec.draw();

		Shape rec2 = new Rectangle();
		//에러
		//rec2.draw2();
	}

}

다형성의 활용

Shape 타입으로 선언하면 훨씬 넓은 범위의 객체를 받을 수 있다.

public static void printArea (Shape shape) {
	System.out.println (shape.getArea());
}

💡 같은 함수로 인스턴스마다 다른 동작을 하여 유연성이 생기지만, 부모에게서 상속된 멤버만 사용하므로 임의로 만든 멤버는 사용을 제외시킬 수 있어서 일관성을 얻을 수 있다.


○ 업캐스팅 / 다운캐스팅

기본형 변수와 같이 참조변수도 형변환이 가능하다. 단, 서로 상속 관계에 있는 클래스 사이에서만 가능하다.
바로 윗 조상이나 자손이 아닌, 조상의 조상으로도 형변환이 가능하다. 따라서 모든 참조변수는 모든 클래스의 조상인 Object 클래스 타입으로 형변환이 가능하다.

기본형에서 작은 자료형에서 큰 자료형은 형변환은 생략이 가능하듯이, 참조형 변수의 형변환에서는 자손타입의 참조변수를 조상타입으로 형변환하는 경우에는 형변환을 생략할 수 있다.

  • 자손타입 → 조상타입 (Up-casting) : 형변환 생략가능
Parent p = new Child();
  • 조상타입 → 자손타입 (Down-casting) : 형변환 생략불가
Child c = new Parent();		//불가능

형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위를 조절하는 것 뿐이다.

다운캐스팅이 가능한 경우

참조하고 있는 인스턴스가 자손 클래스의 인스턴스일 경우 다운캐스팅이 가능하다.

Parent p = new Child();
Child c = (Child) p;		//다운캐스팅 가능

Child c = new Parent();		//불가능

자손타입으로 형변환은 생략할 수 없으며, 형변환을 수행하기 전에 instanceof 연산자를 사용해서 참조변수가 참조하고 있는 실제 인스턴스 타입을 확인하는 것이 안전하다.

class Parent { void print() { System.out.println("Parent 메서드 호출"); }}

class Child extends Parent {
	@Override
	void print () { System.out.println("Child 메서드 호출"); }
}


public class Casting {

	public static void main(String[] args) {
		
		Parent p = new Child();		//업캐스팅 : 자식 객체를 부모 객체로 형변환
		p.print();					//동적 메소드 호출 : 자식의 print() 호출
		
		//Child c = new Parent();
		
		Child c = (Child) p;		//다운캐스팅 : 부모 객체를 자식 객체로 형변환
		p.print();					//메서드 오버라이딩, 자식 객체의 print() 호출

	}

}

//result
Child 메서드 호출
Child 메서드 호출

○ instanceof

💡 객체가 해당 인스턴스를 가지고 있느냐

  • 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용한다.
참조변수 instanceof 데이터타입(클래스명)
  • 주로 조건문에 사용되며, 연산의 결과로 boolean 값인 true와 false 중의 하나를 반환한다.
  • true를 반환받으면, 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
  • 조상타입의 참조변수로는 실제 인스턴스의 멤버들을 모두 사용할 수 없기 때문에, 실제 인스턴스와 같은 타입의 참조변수로 형변환을 해야만 인스턴스의 모든 멤버들을 사용할 수 있다.
if (shape instanceof Rectangle) {
	Rectangle rec = (Rectangle) shape;
	/**
	캐스팅이 가능하면 자손의 캐스팅 후 필요한 멤버를 사용한다.
	**/
}
  • 참조 변수에 담긴 주소를 찾아가서 해당 클래스의 인스턴스가 있는지 찾아본다.
  • 자식 인스턴스를 만들 때 부모의 인스턴스가 먼저 올라오므로 부모클래스에도 true를 반환한다.
//참조형 변수 r은 ture 출력
boolean a = r instanceof Object;
System.out.println(a);

instanceof 예제

public class PolyTest {
	
	public static void print (Shape shape) {
		if (shape instanceof Rectangle) {
			System.out.println("실제 타입은 Rectangle 입니다.");
		} else if (shape instanceof Circle) {
			System.out.println("실제 타입은 Circle입니다.");
		} else {
			System.out.println("알수 없는 타입니다.");
		}
	}
	

	public static void main(String[] args) {

		Shape[] shape = { new Triangle(10, 10), new Rectangle(10, 10), new Circle (10) };
		
		for (Shape s : shape) {
			print(s);
		}
	}
}

//result
알수 없는 타입니다.
실제 타입은 Rectangle 입니다.
실제 타입은 Circle입니다.
profile
훈이야 화이팅

0개의 댓글