백엔드 수업 #07 상속 - 07 타입 변환과 다형성

sookyoung.k·2022년 12월 1일
0

다형성은 아주 중요하다. 왜냐면 강사님이 그렇게 말하심. 그래서 다형성을 구현하는 과제를 내주셨다. 하지만 난 제대로 해내지 못했다. 수요일까지로 기한이 늘어났다. 그러니... 오늘 복습을 하고! 제대로 복습 하고! 다시 열심히 해보자 ^^... 힝 어려워.


다형성: 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질

예... 맞는 설명이긴 한데, 이런식으로 보니까 더 어려움. 타입에 객체 여러개를 갈아 끼워서 사용할 수 있게 만들어주는 것다형성이다.

다형성을 위해서 자바는 부모 클래스로의 타입 변환을 허용한다.
→ 이를 통해서 객체를 부품화 할 수 있다.

○ 자동 타입 변환(Promotion)

자동형변환이라고도 하는 자동타입변환은 프로그램 실행 도중 자동적으로 타입 변환이 일어나는 것을 말한다!
부모클래스 변수 = 자식클래스타입
: 어차피 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급! 자동형변환이 일어난다!

class Animal {
...
}
class Cat extends Animal {
...
}

이러한 상속 관계가 있다면

Cat cat = new Cat(); Animal animal = cat;
Cat 클래스로부터 Cat 객체를 생성하고 Animal 변수에 대입하면 자동형변환이 일어난다!

Animal animal = new Cat();
이렇게도 작성 가능! 왜냐... 어차피 부모껄 받아오니까!!!!

  • 부모 타입으로 자동 형변환이 된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
  • 변수가 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정된다.

그러나 예외가 있으니, 메소드가 자식 클래스에서 오버라이딩이 되었다면 자식 클래스의 메소드가 대신 호출된다!!!! 중요함!!! (다형성, Polymorphism과 관련이 있어서 매우 중요!!!)

public class Parent {
	// 필드
	public int a = 1;
	
	// 메소드
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}

public class Child extends Parent{
	// 필드
	public int b;
	
	@Override
	public void method2() {
		System.out.println("Child-method2()");
	}
	
	public void method3() {
		System.out.println("Child-method3()");
	}

}
public class ChildEX {
	
	public static void main(String[] args) {
		// 자식 객체
		Child child = new Child();
		
		// 자동 타입 변환 시에는 ★오버라이드 된 메소드를 불러온다.★ 
		Parent parent = child; // 자동 타입 변환 (부모한테 자식을 넣어주는 것) (첫 번째 방법) 
		parent.method1();
		parent.method2(); // 부모 이름으로 호출하는거지만 자식한테 있는 method2();를 불러온다. 
		// 객체 간 자동형변환이 일어났을 때, ★자식 객체에 있는 메소드는 불러오지 않는다. 
//		parent.method3(); // 컴파일에러! 
		
		parent.a = 2; // a는 부모한테 있는거니까 문제없어
		// ★자식 객체에 있는 필드는 불러오지 않는다. 
//		parent.b = 1; // b는 자식한테 있는 필드라 접근이 안 된다. 
		
		
		Parent parent = new Child(); // 자동타입 변환 (두 번째 방법) 
		
		// instanceof: 객체타입 확인을 위한 자바에서 제공하는 연산자 
		// parent가 Child 타입이 아니면(Child 객체를 갖고 있지 않다면) 강제 타입 변환을 할 수 없으므로 확인을 함 해보셈 
		if (parent instanceof Child) { // parent가 Child의 타입인지 물어보는 것임 
			// = parent 매개 변수가 Child 객체를 담고 있는지 묻는 것! 스바 일단 쓰긴 하는데... 이해가 되길 
			Child child = (Child)parent; // 강제타입 변환 (부모 변수로 받는 자식 인스턴스를 먼저 생성해줘야 함) 
			child.b = 1; // 자식 객체에 있는 필드에 접근 가능
			child.method3(); // 자식 메소드에 있는 필드에 접근 가능 	
			
		} // true일 때 코드 실행 
	}
}

○ 필드의 다형성

다형성은 동일한 타입을 사용하지만 다양한 결과가 나오는 성질을 말한다.
주로 필드의 값을 다양화함으로써 실행 결과가 다르게 나오도록 구현!
→ 필드 타입은 변함이 없지만 실행 도중에 어떤 객체를 필드로 저장하느냐에 따라서 실행 결과가 달라질 수 있다. 이것이 바로 필드의 다형성.

객체를 교체해서! 다른 결과를 가져올 수 있게 하는 것! 그거시... 바로 다형성... (끄덕)

// Tire 클래스 
public class Tire {
	public void roll() {
		System.out.println("회전합니다.");
	}
}

// Tire의 자식클래스 
public class KumhoTire extends Tire {

	@Override
	public void roll() {
			System.out.println("금호타이어가 느리게 회전합니다.");
	}

}

public class HankookTire extends Tire {

	@Override
	public void roll() {
			System.out.println("한국타이어가 빠르게 회전합니다.");
	}

}

// 실행 클래스 
public class CarEX {

	public static void main(String[] args) {
		Car myCar = new Car();
		
		// Tire 객체를 넣어준다. 
		myCar.tire = new Tire(); // 3. 그래서! 여기에서 객체를 넣어주면 됨! 그러면 run(); 이 실행이 된다. 
		myCar.run(); // 1. 원래 이렇게 하면 됐는데... 이렇게만 하면 에러가 납니다. 
		// 2. 이유! 필드 선언만 되어 있고 필드에 객체가 들어있지 않기 때문임. 
		
		myCar.tire = new HankookTire(); // 자동타입변환이 일어나서 오버라이딩한 메소드 호출됨!! 
		myCar.run(); // "한국타이어가 빠르게 회전합니다" 출력 
		// 코드 한 줄만 바꾸면 뭘 바꿀 수 있으니까 효율성 개좋음 
		
		myCar.tire = new KumhoTire(); // 자동타입변환이 일어나서 오버라이딩한 메소드 호출됨!! 
		myCar.run(); // "금호타이어가 느리게 회전합니다" 출력 
		
	}

}

[출력결과]
회전합니다.
한국타이어가 빠르게 회전합니다.
금호타이어가 느리게 회전합니다.

○ 매개 변수의 다형성

자동형변환은 필드 값을 대입할 때도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.

메소드를 호출할 때 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값을 다양화하기 위해 매개 변수에 자식 타입 객체를 지정할 수도 있다.


public class Vehicle {
	public void run() {
		System.out.println("차량이 달립니다.");
	}
}

public class Driver {
	// 메소드 (매개변수로 다형성을 구현한다.)
	public void drive(Vehicle vehicle) { // 매개변수로 객체를 받아온다. (타입, 변수)
    // = 매개변수에 자식타입의 객체를 받아온 것!!!
//		Vehicle vehicle = new Bus();
		vehicle.run(); // 받아와서 run() 메소드 실행 (구현 끝)
	}
}

public class Bus extends Vehicle {

	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
	
}
public class Taxi extends Vehicle {

	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}
	
}

public class DriverEX {

	public static void main(String[] args) {
		Driver driver = new Driver(); // Driver객체를 생성한다. 
		
		Bus bus = new Bus();
		driver.driver(bus);	
		
		Taxi taxi = new Taxi();
		driver.driver(taxi); // Driver 객체의 drive 함수에 taxi 객체를 매개변수로 준다. 
		// 갈아 끼우기만 하면 되니까 아주 편리하다고 함.
	}

}

[출력 결과]
버스가 달립니다.
택시가 달립니다.

여기에서 이 아래 부분을 다시 한 번 집중해서 보자면,

public class Driver {
	public void drive(Vehicle vehicle) { 
		vehicle.run(); 
	}
}

Vehicle 클래스는 Bus와 Taxi를 자식 클래스로 받고 있고,
Driver 클래스는 매개변수로 Vehicle 객체를 받는다.

이 때 매개변수로 Vehicle의 자식 클래스인 Bus 객체를 drive() 메소드의 매개변수로 넘겨준다면!
main 메소드에서 driver.driver(bus); 이 부분, 여기에서 자동 형변환이 일어나게 되는 것이다.

public class DriverEX {

	public static void main(String[] args) {
		Driver driver = new Driver(); 
		
		Bus bus = new Bus();
		driver.driver(bus);	
		
		Taxi taxi = new Taxi();
		driver.driver(taxi); 

	}

}

Driver 객체와 Bus, Taxi 객체를 생성하고, Driver 객체의 drive() 메소드를 호출할 때 매개값으로 Bus 객체, Taxi 객체를 제공한 상황.
이 경우 Vehicle vehicle = bus ← 이러한 상황이 발생하게 되는 것이다.

매개 변수의 타입이 클래스일 경우, 해당 클래스의 객체 뿐만 아니라 자식 객체까지도 매개값으로 사용할 수 있다!
:매개값으로 어떤 자식 객체가 제공되느냐에 따라서 메소드의 실행 결과가 다향해질 수 있다.
(= 다형성)

○ 강제 타입 변환(Casting)

강제 타입 변환(Casting)이란 부모 타입을 자식 타입으로 변환하는 것이다! 부모 타입이 자식 타입의 필드와 메소드를 사용할 수 있게 해주는 것. 우리 조원들과 했던 이야기가 ㅋㅋㅋㅋㅋ 자식이 부모에게 '돈 줘!' 하는 것은 괜찮지만(자동형변환) 부모가 자식에게 하는 건 좀... 하지만 칼을 든 다면? 부모도 자식에게 '돈 줘!' 할 수 있는 것이다. 이것이... 강제 타입 변환... 비유가 좀 그렇지만...

그렇다면 강제 형변환을 사용하는 이유는 뭘까? 자동 형변환은 자식이 부모 타입에 선언된 필드와 메소드를 사용할 수 있게 해준다. 하지만! 부모 타입에 선언된 필드와 메소드만 사용이 가능하다는단점이 있다. 강제 형변환은!

강제 타입 변환은 자식 타입에 선언된 필드와 메소드를 사용해야 할 때! 용이하다.

전제 조건: 자식 타입이 부모 타입으로 자동 변환한 후, 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.

public class Parent {
	// 필드
	public int a = 1;
	
	// 메소드
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}

public class Child extends Parent{
	// 필드
	public int b;
	
	@Override
	public void method2() {
		System.out.println("Child-method2()");
	}
	
	public void method3() {
		System.out.println("Child-method3()");
	}

}

public class ChildEX {
	
	public static void main(String[] args) {
		Parent parent = new Child(); // 자동
        
        parent.a = "abc"; 
        parent.method1();
        parent.method2();
        
//      parent.b = "abc"; // 불가
//      parent.method3();  // 불가 
        
        Child child = (Child) parent;  // 강제
        child.b = "def";
        child.method3();
		
	}
}

Parent parent = new Child(); 이 부분은 자동 형변환이다. 때문에 자식 객체를 생성했으나 부모의 필드와 메소드에 접근이 가능하다. 하지만 자식의 필드와 메소드는 접근이 불가하다.
Child child = (Child) parent; 이 부분은 강제 형변환이다. 이를 통해서 자식 필드와 메소드에 접근이 가능해진 것이다!

○ 객체 타입 확인(instanceof)

강제 타입 변환은!! 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하다!!! 공부하다보니... 전 이 부분을 완전히 까먹고 있었더군요... 어쩐지 그래서 프로젝트할 때... 자꾸 뭐가 안 되더라... ㅠㅠ 그래서 강조합니다.

Parent parent = new Parent();
Child child = (Child) parent; // 강제 형변환 불가!

이렇게 냅다... 형변환을 한다고 되는 것이 아니라는 것...

그렇다면 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인을 해야겠지요? (사실... 이 부분 약간... 실제 개발에서 어떻게 쓰이는지 잘 모르겠다. 완전히 이해를 못했는데... 복습하면서 이해가 되길...)

암튼, 어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해서는 instanceof 연산자를 사용하면 된다.

boolean result = 좌항(객체) instanceof 우항(타입)
: 좌항의 객체가 우항의 인스턴스이면(= 우항의 타입으로 객체가 생성되었다면) true, 아니라면 false를 산출한다.

  • 매개값의 타입을 조사할 때 주로 사용된다.
  • 메소드 내에서 강제형변환이 필요한 경우! 반드시 매개값이 어떤 객체인지 instanceof 연산자로 확인한 다음 안전하게 강제형변환을 해야 한다.
public class Parent {
	// 필드
	public int a = 1;
	
	// 메소드
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}

public class Child extends Parent{
	// 필드
	public int b;
	
	@Override
	public void method2() {
		System.out.println("Child-method2()");
	}
	
	public void method3() {
		System.out.println("Child-method3()");
	}
}


public class ChildEX {
	
	public static void main(String[] args) {
		Parent parent = new Child(); // 자동타입 변환  
		
		// instanceof: 객체타입 확인을 위한 자바에서 제공하는 연산자 
		// parent가 Child 타입이 아니면(Child 객체를 갖고 있지 않다면) 강제 타입 변환을 할 수 없으므로 확인을 함 해봐야 한다. 
		if (parent instanceof Child) { // parent가 Child의 타입인지 물어보는 것임 
			// = parent 매개 변수가 Child 객체를 담고 있는지 묻는 것! 어렵다...
		
			Child child = (Child) parent; // 강제타입 변환 (부모 변수로 받는 자식 인스턴스를 먼저 생성해줘야 함) 
			child.b = 1; // 자식 객체에 있는 필드에 접근 가능
			child.method3(); // 자식 메소드에 있는 필드에 접근 가능 	
			
		} // true일 때 코드 실행 
	}
}

[출력 결과]
Child-method3()

public class Person {
	// 필드 (클래스에서 사용하는 속성)
	public String name;
	
	// 생성자 (객체가 생성될 때 초기화를 시켜주는 역할)
	public Person(String name) {
		this.name = name; // 매개변수를 받아와서 초기값을 할당해줍니다.
	}
	
	// 메소드
	public void walk() {
		System.out.println("걷습니다.");
	}
}

public class Student extends Person { // Person 클래스를 상속받음 
	// 필드 
	public int studentNo; // 학번 
	
	// 생성자
	public Student(String name, int StudentNo) {
		super(name); // 부모 생성자 호출 
		this.studentNo = StudentNo;
	}
	
	// 메소드
	public void study() {
		System.out.println("공부를 합니다.");
	}
}

public class InstanceofEx {
	// 메소드 
	public static void personInfo(Person person) {
		System.out.println("name: " + person.name);
		person.walk();
		
		if(person instanceof Student) { // person이 Stundent객체(타입)를 참조하고 있냐?
			Student student = (Student)person; // 강제타입변환
			System.out.println(student.studentNo);
		}
	}
	
	public static void main(String[] args) {
		Person p1 = new Person("홍길동");
		personInfo(p1);
	
		Person p2 = new Student("권수경", 12153003);
		personInfo(p2);
	}	

}

[출력 결과]
name: 홍길동
걷습니다.
name: 권수경
걷습니다.
12153003

profile
영차영차 😎

0개의 댓글