자바프로그래밍 다형성

최주영·2023년 3월 20일
0

자바

목록 보기
10/30

✅ 다형성

  • 조건 : 상속관계여야함
  • 여러개의 형태를 갖는다는 의미
  • 한 객체가 여러 타입의 객체로 취급될 수 있는 능력
  • 하나의 행동으로 여러가지 일을 수행
  • 상속을 이용해 부모 타입으로부터 파생된 여러 가지 타입의 자식 객체를 부모 클래스 타입 하나로 다룰 수 있는 기술

✅ 자바에서 부모타입은 자식타입 및 그 하위타입 들도 담을 수 있다

  • 다형적 참조 : 하나의 변수 타입으로 다양한 자식 인스턴스를 참조할 수 있는 기능
Parent p = new Child(); // 부모는 자식을 담을 수 있음 (마음이 넓기때문에)

Parent p2 = new Grandson() // 호출 가능 (Child 클래스의 자식클래스 => GrandSon)

// Child c = new Parent(): // 반대로 자식은 부모를 담을 수 없음 (컴파일 오류 발생)

p.parentMethod() // 호출 가능
// p.childMethod() // 자식의 기능은 호출 불가능 (컴파일 오류)

위와같이 childMethod 를 호출하고 싶을 때는?
-> 캐스팅 이 필요하다


✅ 업캐스팅

  • 조건 : 상속관계여야함
  • 자식 클래스의 객체가 부모 클래스 타입으로 형변환 되는 것

  • 상속 관계에 있는 부모, 자식 클래스 간에 부모타입의 참조형 변수가 모든 자식 타입의 객체 주소를 받을 수 있음

  • 부모 타입으로 변경
//ex

Sonata s = new Sonata(); // Sonata 클래스는 Car 클래스의 자식

// 부모는 자식을 담을 수 있음
Car c = (Car)s; // Sonata 클래스형에서 Car클래스 형으로 업캐스팅되어 바뀜
Car c2 = s;		  // 업캐스팅이 생략된거임 (위 코드와 동일한 결과) 
                     
// 이 경우 c는 원래 부모타입이 였던 멤버만 참조 가능! (자손클래스가 선언된 메소드, 변수)를 읽을 수 ❌

Sonata s = new Car();  // 반대로는 불가능 (이유 : 자식클래스 안에 부모클래스의 것을 저장할 수 없기 때문)

✅ 다운캐스팅

  • 업캐스팅과 반대로 자식 객체의 주소를 받은 부모 참조형 변수를 가지고 자식의 멤버
    참조해야 할 경우, 부모 클래스 타입의 참조형 변수를 자식 클래스 타입으로 형 변환하는 것

  • 부모타입에서 자식타입으로 다운시키는것 = 자식 타입으로 변경

  • 자동으로 처리되지 않기 때문에 반드시 후손 타입 명시해서 형 변환
// ex 
Car c = new Sonata();  // c -> 부모 참조형 변수   c는 자식 객체의 주소를 받음
((Sonata)c).moveSonata(); // movSonata()는 자식의 멤버메소드
// c는 부모참조형 변수라서 자식 메소드에 접근하지 못하므로 자식 클래스 타입으로 형변환하여 접근함!


Car c = new Car();
Sonata s = new Sonata(); // s -> 자식 참조형 변수

s = (Sonata)c; // 이 경우 양쪽 타입이 맞아야 하므로, 부모클래스 c에 자식클래스로 형변환해야함
s.moveSonata();

// 일시적 다운캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
// (Sonata)c.moveSonata(); // 컴파일오류 (연산자 우선순위로 인해 .moveSonata() 가 먼저 실행됨)
// 즉 위 우선순위를 괄호로 해결
((Sonata)c).moveSonate(); // (O) -> Car이 Sonata 타입으로 바뀌는것은 아님 

✅ 다운캐스팅 주의점 -> ClassCastException 예외 발생

//다운캐스팅을 자동으로 하지 않는 이유
public class CastingMain4 {
     public static void main(String[] args) {
         Parent parent1 = new Child();
         Child child1 = (Child) parent1;
         child1.childMethod(); //문제 없음
         Parent parent2 = new Parent(); // 객체를 생성하면 하위 자식은 생성하지 않음
         Child child2 = (Child) parent2; //런타임 오류 - ClassCastException
         child2.childMethod(); //실행 불가
     }
}

parent1 -> parent1은 생성될 때, Child도 인스턴스에 남아 있기 때문에 다운 캐스팅을 해도 문제 x
-> Parent parent1 = new Child(); 객체 생성시 메모리 상에 자식 타입이 존재

parent2 -> parent2는 생성될 때, Child가 인스턴스에 없기 때문에 다운캐스팅을 하면 문제 발생
-> Parent parent2 = new Parent(); 객체 생성시 메모리 상에 자식 타입이 없음

ClassCastException : 사용할 수 없는 타입으로 다운캐스팅 하는 경우 발생


📌 업캐스팅은 안전한 이유?
ex) A > B > C 관계라고 했을 때 C 를 생성하면
인스턴스 내부에 자신과 부모인 A,B,C 모두 생성되기 때문에 문제가 발생하지 않음
즉 메모리에 부모까지 생성하기 때문에, 부모타입으로 참조하는 것은 문제가 없음


📌 즉 정리하면

부모클래스 = 자식클래스 일 경우 [자동으로 자식클래스가 업캐스팅 되서 부모 클래스가됨]
자식클래스 = 부모클래스 일 경우 [강제로 부모클래스를 자식클래스로 형변환 하지 않으면 오류]


✅ instanceof 연산자

  • 현재 참조형 변수가 어떤 클래스 형의 객체 주소를 참조 하고 있는지 확인 할때 사용하는 연산자
  • 타입이 맞으면 true, 맞지 않으면 false 반환
//ex
public void testPerson() {
		Person p;  // p라는 객체로 3개를 다 접근가능함
		p = new Student("유병승",19,1,"실용음악");
				
		printPerson(p);
		printPerson(new Student("최주영",26,2,"컴공")); // if문으로 감
		printPerson(new Teacher("유병승",19,"web",100)); // else if 문으로감
//		printPerson(new PolyTest()); // Person을 상속받고 있는 것들만 인수로 넣을 수 있음
	
	}


public void printPerson(Person p) { // p를 인수로넣었을 때 Person을 상속받은 것들을 접근가능
		// instanceof 
		if(p instanceof Student) {
			System.out.println(p.getName()+" "+p.getAge()
			+" "+ ((Student)p).getGrade() // grade와 major은 자식 필드이기때문에  
			+" "+ ((Student)p).getMajor()); // 자식클래스로 형변환해서 불러야함
		}else if(p instanceof Teacher) {
			System.out.println(p.getName()+" "+p.getAge()
			+" "+ ((Teacher)p).getSubject() // grade와 major은 자식 필드이기때문에  
			+" "+ ((Teacher)p).getSalary()); // 자식클래스로 형변환해서 불러야함
		}
	}

✅ 다형성과 메서드 오버라이딩

예시를 보면서 이해해보자

부모 클래스 : Parent (멤버변수 : value) (멤버메소드 : method())
자식 클래스 : Child (멤버변수 : value) (멤버메소드 : method())
부모와 자식의 필드, 메소드는 모두 동일한 상태이다

다음은 위 예시를 코드를 쓴 것이다

// 부모클래스
public class Parent {
     public String value = "parent";
     public void method() {
     System.out.println("Parent.method");
   }
}
// 자식클래스
public class Child extends Parent {
     public String value = "child";
     @Override // Parent 클래스의 method() 메소드를 재정의한 것
     public void method() {
     System.out.println("Child.method");
   }
}
// main문
    //자식 변수가 자식 인스턴스 참조
     Child child = new Child(); // Child -> Child (1)     
     System.out.println("value = " + child.value);
     child.method();
     
     //부모 변수가 부모 인스턴스 참조
     Parent parent = new Parent(); // Parent -> Parent (2)
     System.out.println("value = " + parent.value);
     parent.method();
     
     //부모 변수가 자식 인스턴스 참조(다형적 참조)
     Parent poly = new Child(); // Parent -> Child (3)
     System.out.println("value = " + poly.value); // 변수는 오버라이딩X
     poly.method(); //메서드 오버라이딩
     
     // 실행결과
     value = child
     Child.method
     
     value = parent
     Parent.method

     value = parent
     Child.method
  • (1) 번인 경우
    자식 변수가 자식 인스턴스 참조
    자식 클래스 Child 인스턴스를 생성 -> 부모 인스턴스까지 생성

    자식에서 접근한 멤버변수 , 멤버메소드 가 있는지 확인해보고 없으면 부모에서 찾음
    자식에서 다 있기 때문에, 자식꺼 호출
  • (2) 번인 경우
    부모 변수가 부모 인스턴스 참조
    부모 클래스 Parent 인스턴스를 생성 -> 부모 인스턴스만 생성 (자식이 있는지 알 수 없음)

    부모타입이기 때문에, 부모에서 접근한 멤버변수, 멤버메소드 호출
  • (3) 번인 경우
    부모 변수가 자식 인스턴스 참조(다형적 참조)
    부모 클래스 Parent 인스턴스 생성 + 자식 클래스 Child 인스턴스 생성

    이 경우
    오버라이딩 된 메소드는 항상 우선권을 가진다 (가장 하위 자식의 오버라이딩 된 메소드가 호출)
    필드는 호출한 클래스 것으로 호출된다 (value = Parent)

또 다른 예시
Animal 클래스에 sound 메소드를 만들면
여러 동물 클래스들은 Animal 클래스를 상속받아서
각 동물클래스 마다 오버라이딩 된 sound 메소드 호출하기

  public class Animal { // 동물 클래스
     public void sound() {
       System.out.println("동물 울음 소리");
     }
 }
public class Dog extends Animal { // 강아지 클래스
   @Override
   public void sound() {
     System.out.println("멍멍");
   }
}
public class Cat extends Animal { // 고양이 클래스
   @Override
   public void sound() {
     System.out.println("냐옹");
   }
}
public class Caw extends Animal{ // 소 클래스
   @Override
   public void sound() {
     System.out.println("음매");
   }
}
// main
public static void main(String[] args) {
   Dog dog = new Dog();
   Cat cat = new Cat();
   Caw caw = new Caw();
   soundAnimal(dog); // 멍멍 -> Animal animal = dog
   soundAnimal(cat); // 냐옹 -> Animal animal = cat
   soundAnimal(caw); // 음매 -> Animal animal = caw
 }
 
 //동물이 추가 되어도 변하지 않는 코드
 private static void soundAnimal(Animal animal) {  // 부모는 자식을 담을 수 있음
   System.out.println("동물 소리 테스트 시작");
   animal.sound(); 
   System.out.println("동물 소리 테스트 종료");
   }
}


메서드 안에서 animal.sound 메서드를 호출하면
오버라이딩 한 메서드가 우선권을 가지기 때문에, 각 동물의 sound 메서드가 호출된다

다형성 덕분에 새로운 동물을 추가해도 코드를 재사용할 수 있다
새로운 기능을 추가할 때, 변하는 부분을 최소화 하는 것이 잘 작성된 코드

📌 문제점

  • Animal 클래스는 동물이라는 클래스로, 직접 생성해서 사용할 일이 없음
  • Aniaml 클래스를 상속받는 곳에서 sound() 메서드 오버라이딩을 하지 않을 수도 있음
    -> 이러면 부모클래스의 sound()를 호출하게됨 (기대 값과 다르게 나옴)

제약이 있는 프로그램이 좋은 프로그램
이러한 문제점은 추상클래스추상메소드 를 통해서 해결 할 수 있다


profile
우측 상단 햇님모양 클릭하셔서 무조건 야간모드로 봐주세요!!

0개의 댓글