다형성
객체의 타입에 따라 똑같은 명령이어도 서로 다른 결과를 얻는 것.
업캐스팅 (상향 형변환)
하위 클래스 객체를 상위 클래스 참조 변수가 가리키는 것
단, 참조 변수는 하위 클래스의 필드와 메소드는 접근 불가.
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 |