다형성
개념
- 하나의 객체가 여러 타입의 객체를 가질 수 있음
- 즉, 클래스의 상속 관계에서
- 부모 클래스 타입의 참조 변수로 자식 클래스의 인스턴스(객체)를 참조할 수 있음
1. 부모 클래스의 참조변수가 부모 클래스 본인의 객체뿐만 아니라, 자식 클래스의 인스턴스(객체)도 참조할 수 있음
2. 자식 클래스의 인스턴스(객체)를 자식 클래스 타입의 참조 변수 뿐만 아니라, 부모 클래스 타입의 참조 변수로 참조할 수 있음
참조변수의 다형성
- 클래스들 사이에서 반드시 상속 관계가 전제할 것
- 다형성을 위하여 부모 클래스 타입의 참조변수로 자식 클래스 타입의 인스턴스(객체)를 참조할 수 있도록 함
- 반대의 경우, 자식 클래스 타입의 참조 변수로는 부모 클래스 타입의 인스턴스를 참조할 수 없음
- 그 이유는, 참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버(부모 클래스 인스턴스)보다 많기 때문
- 자식 클래스는 부모의 멤버를 상속 받기 때문에, 사용할 수 있는 멤버의 개수가 부모 클래스보다 같거나 더 많음
class Parent { }
class Child extends Parent { }
public class Main {
public static void main(String [] args) {
Parent pa = new Parent(); // 허용
Child ch = new Child(); // 허용
// 다형성 이용
Parent pa2 = new Child(); // 허용
// 자식 타입의 참조변수로, 부모 클래스 타입의 인스턴스 참조 X
Child ch2 = new Parant(); // 오류 발생, (X)
}
}
예제
class Animal {
String category;
}
class Cat extends Animal { // Animal 클래스(상위 클래스)를 상속
String name;
int age;
}
public class AnimalTest {
public static void main(String [] args) {
// 상위 클래스 타입의 참조변수(a)가 그 상위 클래스의 객체를 가리킴
Animal a = new Animal();
// 다형성: 상위 클래스 타입의 참조변수(b)가 하위 클래스의 객체를 가리킬 수 있음
Animal b = (Animal)new Cat();
Animal b = new Cat(); // (형변환타입) 생략 가능
}
}
자바의 참조형 캐스팅(형 변환)
캐스팅(casting)
타입을 변환하는 것, 형 변환
상속 관계의 클래스 간에 형 변환하기
- 상속 관계의 클래스는 부모 클래스와 자식 클래스로 구분
- 자바의 상속 관계에 있는 부모와 자식 클래스 간에는만 서로 형 변환이 가능함
- 기본형 타입들끼리 서로 형 변환이 가능한 것과 같이
- 참조형 타입들끼리도 형 변환이 가능
- 자식 클래스 타입에서 부모 클래스 타입으로의 타입 변환은, 형 변환 생략 가능(업캐스팅)
- 부모 클래스 타입에서 자식 클래스 타입으로의 타입 변환은, 반드시 명시(다운캐스팅)
클래스는 참조형 타입(reference 타입)이므로, 부모클래스와 자식 클래스 간의 형 변환을 '참조형 캐스팅'이라고 함
참조형 캐스팅의 종류
- 업캐스팅
- 다운 캐스팅
- 자식 클래스의 객체는 부모 클래스를 상속하고 있기 때문에, 자신의 멤버는 물론이고 부모의 멤버(변수, 메서드 등)도 모두 사용할 수 있음
- 반면 부모 클래스의 객체는 자식 클래스의 멤버(변수, 메서드 등)를 갖지 않으므로, 사용할 수 없음
업캐스팅(upcasting)
개념
- 클래스의 상속 구조에서
- 부모 클래스 타입의 참조 변수가 자식 클래스 인스턴스를 가리킬 수 있는 능력
- 즉, 자식 클래스 타입이 부모 클래스 타입으로 변환되는 것(타입의 변환)
특징
- 자식 클래스 타입을 부모 클래스 타입으로 변환된다는(업캐스팅) 것은, 멤버의 개수가 감소한다는 것
- 즉, 자식 클래스에만 있는 변수와 메서드는 실행하지 못한다는 것
- = 부모 클래스 타입으로 타입이 변환되었기 때문에 자식 클래스의 멤버 사용 불가
- 부모 타입의 참조 변수로 부모 클래스에 있는 변수와 메서드만 접근할 수 있음
- 업캐스팅을 하고 나서, 메서드를 호출할 때
- 만일 자식 클래스에서 부모 클래스의 메서드를 오버라이딩 한 메서드(@override)가 있는 경우
- 부모 클래스의 메서드가 아닌 오버라이딩 된 메서드가 실행됨
업캐스팅을 하는 이유가 뭘까?
- 공통적으로 사용하려고 하는 부분을 만들어서 관리하기 위함
- 즉 상속 관계에서 자식 클래스의 개수에 상관 없이, 하나의 인스턴스(부모 클래스 하나)로 묶어서 관리할 수 있음
상속 관계를 맺어 부모 클래스 타입으로 업캐스팅을 하면, 하나의 타입(부모 타입)으로 관리할 수 있음
업캐스팅 형식
// () 괄호에 변환할 타입을 작성하여 형 변환
Animal a = (Animal) new Cat(); // Animal타입으로 형 변환
// 업캐스팅은 자동 형변환 가능, () 괄호 생략 가능
Animal a = new Cat();
- Cat 클래스는 Anumal클래스를 상속받은 클래스이므로
- Cat은 Cat 타입이면서 동시에 Animal 타입이기도 함
- 따라서, Cat 클래로 인스턴스를 생성할 때, 이 인스턴스의 타입을 Animal형으로 형 변환을 할 수 있음
- 원래의 Cat 인스턴스의 타입은 Cat인데
- new Cat을 Animal형으로 변경할 수 있음. 즉, (Animal) new Cat;
- 참조변수 a가 가리키는 것은
- 참조 변수 클래스형에 기반한 멤버변수와 메서드에 접근할 수 있음
- 즉, Animal 클래스의 멤버변수와 메서드만 접근 가능
업캐스팅 시에 주의할 점
- 사용할 수 있는 멤버(멤버변수, 메서드)의 개수가 감소하여, 멤버에 대한 접근이 제한됨
자식 클래스 타입을 부모 클래스 타입으로 형 변환을 하는 것이기 때문에, 자식 타입을 마음대로 사용할 수 있을 것 같지만, 업캐스팅된 상태에서 자식 타입에 마음대로 접근할 수 없음
[ 이유 ]
부모를 상속하여 부모의 멤버(멤버변수, 메서드)는 물론 본인의 멤버까지 사용할 수 있었던 자식 클래스에서, 부모 클래스로 업캐스팅(형변환)을 하였으니 당연히 멤버 개수가 감소하게 됨. 즉, 부모 클래스로 형 변환을 하였으니, 부모의 멤버(멤버변수, 메서드)만 사용할 수 있게 됨
[ 원칙 ]
업캐스팅이 된 상태에서 부모 클래스 타입의 참조변수로, 자식 클래스의 필드나 메서드에 접근할 수 없음
[ 다운캐스팅 ]
자식 클래스의 필드나 메서드에 접근하기 위해서는, 다시 자식 클래스 타입으로 다운캐스팅 필요
업캐스팅 시에 자식 클래스의 멤버에 대한 접근이 제한
1. 업캐스팅이 된 상태에서 메서드 호출 시에, 부모 클래스에서 오버라이딩 된 메서드를 먼저 호출
부모 클래스에 bark() 메서드가 있고, 자식 클래스에서 오버라이딩 된 메서드(같은 메서드명을 가지는 bark() 메서드)가 있을 때, 업캐스팅이 된 상태에서 bark() 메서드를 호출하면 부모와 자식 클래스의 bark 메서드 중에 어떤 메서드가 호출이 될까?
✔️ 원칙적으로는, 부모 클래스 타입의 참조변수로는 자식 클래스의 메서드를 호출할 수 없음️
✔️ 하지만 업캐스팅 된 상태에서는, 하위 클래스에서 오버라이딩 된 메서드가 먼저 호출이 됨
2. 업캐스팅이 된 상태에서 멤버변수를 호출하면, 부모 클래스의 멤버변수가 호출됨
- 부모 클래스 타입의 참조변수로는, 자식 클래스의 멤버변수나 메서드를 접근할 수 없음
- 부모 클래스 타입의 참조변수로는 부모 클래스의 멤버변수에 접근할 수 있음
3. 예제
class Animal {
String category;
void bark() {
System.out.println(“동물이 짖는다”);
}
}
class Cat extends Animal {
@override
void bark() {
System.out.println(“고양이가 짖는다 : 니야옹”);
}
}
- 부모 클래스(Animal)의 bark() 메서드를
- 자식 클래스(Cat)에서 오버라이드(@override)하여 메서드를 재정의 함
- 따라서, a.bark() 메서드 호출 시에, 자식 클래스에서 오버라이딩 된 메서드가 호출됨
- 부모 클래스 타입의 참조변수(a)로
- 자식 클래스의 멤버변수나 메서드를 호출할 수 없음
[주의할 점]
상위 클래스 타입의 참조변수(a)로, 하위 클래스의 멤버변수나 메서드를를 호출할 수 없음
다운캐스팅(down-casting)
[ 자식 클래스에만 있는 고유한 메서드나 멤버변수를 실행하려면 어떻게 해야 할까? ]
오버라이딩 한 메서드가 아닌 이상은, 업캐스팅한 부모 클래스 타입으로 자식 클래스의 고유한 메서드를 실행할 수 없음
따라서, 업캐스팅한 객체를 다시 자식 클래스 타입으로 다시 형을 변환해야 함. 즉 다운 캐스팅이 필요
개념
- 다운캐스팅은 부모 클래스 타입이 자식 클래스 타입으로 형변환(캐스팅)되는 것
- 부모 클래스 타입으로 업캐스팅한 객체를 다시 자식 클래스 타입의 객체로 되돌리는 것
다운 캐스팅의 목적
- 부모 클래스로 업캐스팅된 자식 클래스를 다시 복구하여, 자식 클래스 본래의 멤버변수(필드)와 메서드를 사용
- 즉, 자식 클래스 본래의 기능을 사용하기 위하여 다운캐스팅을 함
다운캐스팅 형식
- 형 변환 연산자를 반드시 사용(생략 불가)
// [업캐스팅]
Animal a = new cat(); // (업캐스팅)형 변환 연산자 생략 가능
// [다운 캐스팅]
Cat c = (Cat)a; // (다운캐스팅)형 변환 연산자 생략 X
형 변환 연산자를 반드시 써줘야 하는 이유
- 다운 캐스팅을 하면, 다시 사용할 수 있는 멤버(멤버변수, 메서드)의 개수가 증가하는 것이므로
- 참조변수가 실제로 가리키는 객체가 무엇인지를 알려줘야 함
- 괄호 안에 사용할 타입을 적어주어, 어떤 클래스의 멤버가 증가하는지를 알려줘야 함
예제
class Animal {
String category;
void bark() {
System.out.println(“동물이 짖는다”);
}
}
class Cat extends Animal {
String name;
int age;
@Override
void bark() {
System.out.println(“고양이가 짖는다 : 니야옹”);
}
void eating() {
System.out.println(“고양이의 츄르 먹기”);
}
}
public class AnimalTest {
public static void main(String [] args) {
// [업캐스팅]
Animal a = new Cat(); // Animal(부모클래스)타입으로 형 변환
// [다운캐스팅]
Cat c = (Cat)a; // 다운캐스팅은, 형변환 연산자 생략 불가
c.eating(); // 출력: "츄르 먹기"
c.bark(); // 출력: "고양이 니야옹"
c.name = "냐옹이"; // 자식클래스의 멤버변수 접근
System.out.println(c.name); // 출력: "냐옹이"
}
}
다운 캐스팅을 할 때 주의사항
- 업캐스팅한 객체를 본래의 형태로 되돌리는 용도로 사용하여야 함
- 다운 캐스팅 할 객체가 업캐스팅된 부모 클래스의 객체여야 함
- 부모 클래스의 객체(업캐스팅이 된 적 없는)를 다운 캐스팅 하면
- 컴파일 시점에는 오류가 없지만
런타임시 오류를 발생시킴
1. 부모 클래스 객체(업캐스팅 된 적 없음)를 다운 캐스팅할 때 : 에러 발생
- 컴파일(저장) 시점에는 에러가 없지만
- 실행(runtime) 시점에는 형 변환 오류가 발생함
2. 업캐스팅이 된 객체를 대상으로 다운 캐스팅 할 때
✔ instanceof 연산자
개념
- 어떤 객체의 참조변수가 어떤 클래스의 타입인지를 판별하여 true, false를 반환해줌
- 객체에 대한 클래스 타입(참조형 타입)인 경우에만 사용이 가능
[ instanceof 연산자 ]
기본형 타입(int, long 등)에는 사용이 불가능
사용하는 경우
- 다형성으로 인해 런타임 시에 참조 변수가 실제로 참조하고 있는 인스턴스의 타입을 확인할 필요가 있음
문법
전달된 참조변수 instanceof 클래스이름
예제
class Animal {
String category;
void bark() {
System.out.println("동물이 짖는다");
}
}
class Cat extends Animal {
String name;
int age;
@Override
void bark() {
System.out.println("고양이 니야옹");
}
void eating() {
System.out.println("고양이의 츄르 먹기");
}
}
class Dog extends Animal {
String name;
int age;
@Override
void bark() {
System.out.println("강아지 멍멍");
}
void playing() {
System.out.println("강아지의 한강 산책");
}
}
1. [ 실행 결과 ] 업캐스팅 가능
public class AnimalTest {
public static void main(String [] args) {
// [업캐스팅이 되었는지를 판단]
Cat cat = new Cat(); // 같은 클래스 타입으로 객체 생성
// 객체의 참조변수(cat)가 어떤 클래스 타입인지를 판별
if(cat instanceof Animal) { // 참조변수(cat)이 Animal 클래스 타입이면 업캐스팅
System.out.println("업캐스팅 가능");
Animal a = cat; // 업캐스팅
} else {
System.out.println("업캐스팅 불가능");
}
}
}
2. [ 실행 결과 ] 강아지의 한강 산책
public class AnimalTest {
public static void main(String [] args) {
Animal animal = new Dog(); // [업캐스팅]
if(animal instanceof Dog) { // Dog클래스 타입이라면, Dog에 있는 메서드를 호출
( (Dog)animal ).playing();
}
else {
System.out.println("나는 Animal 클래스 타입");
}
}
}
3. [ 실행 결과 ] 고양이의 츄르 먹기
public class AnimalTest {
public static void main(String [] args) {
Animal ani= new Cat(); // [업캐스팅]
if(ani instanceof Dog) { // 참조변수가 Dog클래스 타입이라면, Dog에 있는 메서드를 호출
((Dog)ani).playing();
}
else if(ani instanceof Cat) { // 참조변수가 Cat클래스 타입이라면, Cat에 있는 메서드를 호출
((Cat)ani).eating();
}
else {
System.out.println("나는 Animal 클래스 타입");
}
}
}
4. [ 실행 결과 ] 나는 Animal 클래스 타입
public class AnimalTest {
public static void main(String [] args) {
Animal ani= new Animal();
if(ani instanceof Dog) {
((Dog)ani).playing();
}
else if(ani instanceof Cat) {
((Cat)ani).eating();
}
else { // 참조변수(ani)가 Dog클래스타입, Cat클래스타입도 아닌 경우
System.out.println("나는 Animal 클래스 타입");
}
}
}
출처
'JAVA' 카테고리의 다른 글
[JAVA] 메서드 오버라이딩, 오버로딩과 오버라이딩 (0) | 2024.04.21 |
---|---|
[JAVA] super와 super(), this와 this() (0) | 2024.04.21 |
[JAVA] JAVA 메모리 영역(method, stack, heap 영역) (0) | 2024.04.19 |
[JAVA] 접근 제한자(public < protected < default < private) (0) | 2024.04.18 |
[JAVA] 상속(특징, 자식과 부모의 인스턴스 생성) (0) | 2024.04.18 |