본문 바로가기

JAVA

다형성 - 객지설 5주차

다형성
객체의 타입에 따라 똑같은 명령이어도 서로 다른 결과를 얻는 것.

업캐스팅 (상향 형변환)

하위 클래스 객체를 상위 클래스 참조 변수가 가리키는 것

단, 참조 변수는 하위 클래스의 필드와 메소드는 접근 불가.

ex. 부모(상위) 클래스 Shape / 자식(하위) 클래스 Circle

      => Shape s1 = new Circle(); // 부모(상위) 클래스의 참조변수 s1가 하위 클래스 객체를 가리킴

      => s1은 Shape 객체의 변수이므로, Shape로 부터 상속 받은 필드와 메소드만 사용이 가능하다.

      => s1은 Shape 부모 클래스 참조 변수는 자식 클래스를 가리키긴 하지만, 무엇을 추가하였는지 알 수가 없다.

      *어떤 멤버를 사용할 수 있는지는 변수의 타입에 의해 결정이 됨. (객체의 타입에 의해 결정되는 것이 X)

class Shape {
	int x, y;
	public void draw() { 
		System.out.println("Shape Draw.");
	}
}
class Rectangle extends Shape {
	int width, height;
	public void draw() {
		System.out.println("Ractangle Draw.");
	}
}
public class Test {
	public static void main(String[] args) {
		Shape s1 = new Shape(); // Shape 객체를 Shape 참조 변수가 가리킴
		Shape s2 = new Rectangle(); // Rectangle 객체를 Shape 참조 변수가 가리킴
		
		s2.x = 0; // 올바른 코드
		s2.y = 0; //올바른 코드
		
		//Rectangle 객체를 가리키고 있긴 하지만, Shape 참조 변수이므로 Shape로부터 상속 받은 필드와 메소드만 사용 가능함
		s2.width = 100;  //컴파일 오류 -> 접근하고 싶다면 Rectangle 클래스 참조 변수를 생성해야함.
		s2.height = 100;  //컴파일 오류 -> 접근하고 싶다면 Rectangle 클래스 참조 변수를 생성해야함.
	}
}

 

다운 캐스팅 (하향 형변환)

상위 클래스 객체를 하위 클래스 참조 변수가 가리키는 것 -> 꼭 명시적으 선언해주어야 함. (형변환 필요)

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

	public static void main(String[] args) {
		Parent p = new Child(); //업 캐스팅, 상속 받은 멤버만 사용 가능하다
		p.print(); //"Child 메소드 호출" 출력
		
		Child c = (Child) p; //다운 캐스팅, 형 변환 안 하면 컴파일 오류
		c.print(); //"Child 메소드 호출" 출력
	}
}

* p.print()가 "Child 메소드 호출"을 출력하는 이유 : 가장 마지막에 정의된 메소드가 실행됨.

 

다운 캐스팅 주의사항

올바른 코드

class Shape {
	int x, y;
	public void draw() { 
		System.out.println("Shape Draw : " + x + ", " + y);
	}
}
class Rectangle extends Shape {
	int width, height;
	public void draw() {
		System.out.println("Ractangle Draw: " + width + ", " + height);
	}
}
public class Test {
	public static void main(String[] args) {
		Shape r1 = new Rectangle(); // 업캐스팅
		Rectangle r2 = (Rectangle) r1; // 다운 캐스팅
		
		r2.width = 100;
		r2.height = 100;
		
		r2.draw(); //"Ractangle Draw: 100, 100" 출력
	}
}

 

잘못된 코드

class Shape {
	int x, y;
	public void draw() { 
		System.out.println("Shape Draw : " + x + ", " + y);
	}
}
class Rectangle extends Shape {
	int width, height;
	public void draw() {
		System.out.println("Ractangle Draw: " + width + ", " + height);
	}
}
public class Test {
	public static void main(String[] args) {
		Shape s = new Shape();
		Rectangle r = (Rectangle) s; //다운 캐스팅
		
		r.width = 100; //런타임 에러
		r.height = 100; //런타임 에러
		
		r.draw();
	}
}

런타임 에러가 발생하는 이유 : Rectangle 클래스의 참조 변수 r이지만 Shape를 가리키고 있기 때문에 Rectangle의 멤버에는 접근할 수 없음

 

업캐스팅을 사용하는 이유

1. 동적 바인딩

객체의 타입에 따라 서로 다른 메소드가 호출되게 하는 것. (객체의 실제 타입이 호출되는 메소드를 결정하는 것)

class Shape {
	int x, y;
	public void draw() { 
		System.out.println("Shape Draw.");
	}
}
class Rectangle extends Shape {
	int width, height;
	public void draw() {
		System.out.println("Ractangle Draw.");
	}
}
class Triangle extends Shape {
	int base, height;
	public void draw() {
		System.out.println("Triangle Draw.");
	}
}
class Circle extends Shape {
	int radius;
	public void draw() {
		System.out.println("Circle Draw.");
	}
}
public class Test {
	public static void main(String[] args) {
		Shape[] arr = new Shape[3];
		
		arr[0] = new Rectangle();
		arr[1] = new Triangle();
		arr[2] = new Circle();
		
		for(Shape i : arr) {
			i.draw();
		}
	}
}

 

Shape 형 배열을 만들면 Shape형 '참조변수'가 3개 생기는 것임 ('객체'가 3개 생성되는 것이 아님)

그 3개의 '참조변수'는 각각 Rectangle, Triangle, Circle을 가리키고 있음 (업캐스팅)

그래서 배열을 이용하여 각 참조변수가 가리키고 있는 객체의 draw를 호출할 수 있음.

 

2. 객체를 매개변수로 활용할 때 유용

매개변수를 Shape s 로 주면 인수에서 하위 클래스를 전달해 주면 업캐스팅이 일어남. 즉, Shape에서 파생된 모든 타입의 객체를 받을 수 있게 됨. => 훨씬 넓은 범위의 객체를 받을 수 있음.

//다음과 같은 메소드를 선언할 수 있다
public static void printLocation(Shape s) {
     System.out.println("x = " + s.x + " y = " + s.y);
}
class Shape {
	int x, y;
	public void draw() { 
		System.out.println("Shape Draw.");
	}
}
class Rectangle extends Shape {
	int width, height;
	
	public Rectangle() {
        x = 1; // Shape 클래스의 x 값 초기화
        y = 2; // Shape 클래스의 y 값 초기화
    }
	
	public void draw() {
		System.out.println("Ractangle Draw.");
	}
}
class Triangle extends Shape {
	int base, height;
	
	public Triangle() {
        x = 10; // Shape 클래스의 x 값 초기화
        y = 20; // Shape 클래스의 y 값 초기화
    }
	
	public void draw() {
		System.out.println("Triangle Draw.");
	}
}
class Circle extends Shape {
	int radius;
	
	public Circle() {
        x = 100; // Shape 클래스의 x 값 초기화
        y = 200; // Shape 클래스의 y 값 초기화
    }
	
	public void draw() {
		System.out.println("Circle Draw.");
	}
}
public class Test {
	//다음과 같은 메소드를 선언할 수 있다
	public static void printLocation(Shape s) {
	     System.out.println("x = " + s.x + ", y = " + s.y);
	}
	
	public static void main(String[] args) {
		Rectangle s1 = new Rectangle();
		Triangle s2 = new Triangle();
		Circle s3 = new Circle();
		
		printLocation(s1); //x = 1, y = 2 출력
		printLocation(s2); //x = 10, y = 20 출력
		printLocation(s3); //x = 100, y = 200 출력
		
	}
}

 

변수가 가리키는 객체의 실제 타입 알아보는 방법

instance 연산자 (반환값을 boolean)

if ( obj instanceof Rectangle ) { }
// 참조 변수 obj가 Rectangle을 참조하고 있다면 true, 아니면 false 반환

* instance 연산자는 서로 "상속 관계"에 있는 클래스끼리만 사용 가능함. 만약 Shape 클래스의 참조변수로 instance를 사용하고 싶다면 오른쪽에는 Shape의 부모 클래스나 자식클래스만 올 수 있음.

*아예 상관 없는 클래스가 올 경우에는 반환 자체가 안 되어서 컴파일 에러가 뜸

* 참고로 형제 클래스도 상속 관계가 아니기 때문에 컴파일 에러가 뜸 (ex. Rectangle 클래스와 Circle 등등)

class Shape {
	
}
class Rectangle extends Shape {
	
}
class Triangle extends Shape {
	
}
class Circle extends Shape {
	
}
public class Test {
	public static void print(Shape obj) {
		if (obj instanceof Rectangle) { System.out.println("실제 타입은 Rectangle"); }

		if (obj instanceof Triangle) { System.out.println("실제 타입은 Triangle"); }

		if (obj instanceof Circle) { System.out.println("실제 타입은 Circle"); }
	}


	public static void main(String[] args) {
		Shape[] arr = new Shape[3];
		
		arr[0] = new Rectangle();
		arr[1] = new Triangle();
		arr[2] = new Circle();

		print(arr[0]); //"실제 타입은 Rectangle" 출력
		print(arr[1]); //"실제 타입은 Triangle" 출력
		print(arr[2]); //"실제 타입은 Circle" 출력

	}
}

 

'JAVA' 카테고리의 다른 글

예외 처리 - 객지설 7~9주차  (0) 2024.05.10
인터페이스 - 6주차  (0) 2024.05.05
랩퍼 클래스 - 객지설 4주차  (0) 2024.05.03
객프 13주차  (0) 2023.11.29
객프 11주  (0) 2023.11.21