## 타입변환과 다형성(polymorphism)
# 다형성
-같은 타입이지만 실행 결과가 다양한 객체 대입(이용) 가능한 성질
-부모 타입에는 모든 자식 객체가 대입 가능
-자식 타입은 부모 타입으로 자동 타입 변환
- 추상 클래스를 사용하는 이유는 표준화이다. (객체를 사용할 때 기준을 잡아주는 것이다)
- 클래스 : 설계도, 인터페이스 : 사용 설명서 (사용자가 필요하다)
▶다형성 : 쉬는 시간 되면 잠깐 쉬었다 하겠습니다.
- 명령을 하면, 다양하게 행동을 하게 되는 것
- 일일이 해야하는 것을 정해주게 되면 너무 시간이 길어진다.
- 상위 명령을 하나 툭 던져주면 각자 그 명령을 받아서 개개인의 행동을 정하고 수행한다.
※ 도형이니까 너 그냥 그려 (draw) 삼각형은 알아서 삼각형으로, 사각형은 알아서 사각형으로 각자 할 행동들을 만들어서 수행한다.
- 전달하는 명령은 동일하지만 (draw) 각각의 상속받은 클래스들에 오버라이딩 되어 있는 (draw) 명령으로 실행이 되는 것
▶캡슐화 : 사용자는 리모콘이 어떤 원리로 작용하고 동작하는지 모르고, 그냥 사용법만 알면 된다.
인터페이스와 클래스의 용도는 분명히 다르다.
추상 클래스 : 제품 생산 (이런 제품들은 이런 기능이 있어야해 하면서 만드는 것)
인터페이스 : 메뉴얼 (사용방법)
-> 클래스는 일일이 인스턴스 멤버와 메서드를 만들어서 그 제품 자체를 만드는데,
인터페이스는 그냥 사용방법만 알고 그 사용방법대로만 쓰면 된다.
다형성은 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말한다.
코드 측면에서 보면 다형성은 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 해준다.
다형성을 위해 자바는 부모 클래스의 타입 변환을 허용한다.
즉 부모 타입에 모든 자식 객체가 대입될 수 있다. 이것을 이용하면 객체는 부품화가 기능하다.
예를 들어 자동차를 설계할 때 타이어 클래스 타입을 적용했다면
이 클래스를 상속한 실제 타이어들은 어떤 것이든 상관없이 장착(대입)이 가능하다.
타입 변환이란 데이터 타입을 다른 데이터 타입으로 변환하는 행위를 말한다.
기본 타입의 변환에 대해서는 이미 2장에서 학습한 바 있다.
클래스 타입도 마찬가지로 타입 변환이 있다.
클래스 타입의 변환은 상속 관계에 있는 클래스 사이에서 발생한다.
자식 타입은 부모 타입으로 자동 타입 변환이 가능하다.
위 그림에서 HankookTire와 KumhoTire는 Tire를 상속했기 때문에 Tire 변수에 대입할 수 있다.
# 자동 타입 변환(Promotion)
자동 타입 변환(Promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.
자동 타입 변환은 다음과 같은 조건에서 일어난다.
부모 클래스 = 자식 클래스 타입; // 자동 타입 변환
자동 타입 변환의 개념은 자식은 부모의 특정과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다는 것이다.
예를 들어 고양이는 동물의 특징과 기능을 상속했다.
그래서 “고양이는 동물이다.”가 성립한다.
Animal과 Cat 클래스가 다음과 같이 상속 관계에 있다고 보자.
Cat 클래스로부터 Cat 객체를 생성하고 이것을 Animal 변수에 대입하면 자통 타입 변환이 일어난다.
Cat cat = new Cat (); // Cat 클래스로부터 cat 객체를 생성
Animal animal = cat; // Animal 클래스로부터 만들어진 animal 객체에 cat 객체를 대입할 수 있다.
OR
Animal animal = new Cat; // animal 객체에 new 연산자를 사용하여 cat 객체를 바로 대입도 가능
위 코드로 생성되는 메모리 상태를 그림으로 묘사하면 다음과 같다.
cat과 animal 변수는 타입만 다를 뿐, 일한 Cat 객체를 조한다.
여기서 사용되는 method2(); 는 Override된 메서드를 사용한다.
즉, 부모는 자기의 필드와 메서드를 사용할 수 없고, 오직 자식만 부모의 필드와 메서드를 모든게 사용이 가능하다.
허나, 부모는 자식에게 대입이 될 수 있다.
※ 각각의 타이어가 extends 하면 여러 가지 타이어가 Tire에 상속되어있다.
그때, Car에서 Tire tire;을 선언하면, 모든 타이어가 착용 가능하다.
다형성 총평 (총정리 매우 쉽게)
package example0423.chapter07.example07;
public class Car {
int speed;
boolean power;
// 필드 Tire 타입의 tire 선언
public Tire tire;
public void run() {
tire.roll();
}
}
1. 요런 car class가 있습니다.
2. 자동차에는 tire가 있쬬
package example0423.chapter07.example07;
public class Tire {
public void roll() {
System.out.println("타이어를 달고 바퀴가 굴러갑니다. 나는 타이어니까.");
}
}
3. tire에는 여러 종류가 있습니다
4. 각각의 roll이라는 tire 클래스의 메서드를 참조하고 있지만,
Override를 먹여서 그런지 약간의 차이가 있네요 개개인의 개성이 강한 타이어 입니다.
한 번 굴려볼까요?
package example0423.chapter07.example07;
public class CarMain {
public static void main(String[] args) {
// car 생성자 호출
Car car = new Car();
// car에 필드값으로 저장해놓은 tire 호출
car.tire = new Tire();
// 굴리면 기본 타이어
car.run();
System.out.println();
// 한국타이어 교체
car.tire = new HankookTire();
// 굴리면 한국타이어
car.run();
System.out.println();
// 금호타이어 교체
car.tire = new KumhoTire();
// 굴리면 금호타이어
car.run();
System.out.println();
car.tire = new SnowTire();
car.run();
System.out.println();
}
}
5. 결론 같은 타이어 메서드를 쓰지만, Override된 메서드를 자동차의 타이어 필드 값에 저장시켜서 불러올 수 있다.
여기까지가 쉽게 설명한 부분
또 다른 예제로 가볍게 집고 넘어가자
1. 자동차 클래스의 양옆 다리를 지정해준다. (타이어 클래스의 생성자를 통해 변수 이름으로 정해준다)
package example0423.chapter07.example08;
public class Car {
public Tire frontLeftTire = new Tire();
public Tire frontRightTire = new Tire();
public Tire rearLeftTire = new Tire();
public Tire rearRightTire = new Tire();
public void run() {
frontLeftTire.roll();
frontRightTire.roll();
rearLeftTire.roll();
rearRightTire.roll();
}
}
2. Car에 들어간 roll 메서드를 지정해주는것
package example0423.chapter07.example08;
public class Tire {
public void roll() {
System.out.println("일반 타이어가 굴러갑니다.");
}
}
3. 세상엔 여러가지 타이어들이 있으니까 Override 해주고..
package example0423.chapter07.example08;
public class KumhoTire extends Tire {
Car car = new Car();
@Override
public void roll() {
System.out.println("금호 타이어가 현재 부릉부릉중입니다.");
if ((car.frontLeftTire != car.frontRightTire) || ( car.rearLeftTire == car.rearRightTire)) {
System.out.println("어 그런데 혹시 왜 양쪽다 같은 타이어를 쓰지 않으셨죠??");
}
}
}
4. CarMain에 와서 Car를 new 생성자로 myCar를 만들어준다.
내 차가 생겼으니, 운전해봐야겠지
처음 run 메소드는 일반타이어로 지정해서 기본 타이어로 차가 나간다.
하지만, 여기서 HankookTire 클래스와 KumhoTire 클래스의 변수를 새로 new 생성자로 만든다.
각각의 타이어에 변수를 할당해준다.
package example0423.chapter07.example08;
public class CarMain {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
HankookTire han = new HankookTire();
KumhoTire kum = new KumhoTire();
myCar.run();
myCar.frontLeftTire = han;
myCar.frontRightTire = han;
myCar.rearLeftTire = kum;
myCar.rearRightTire = kum;
myCar.run();
}
}
# 필드 다형성
그렇다면 왜 자동 타입 변환이 필요할까?
그냥 자식 타입으로 사용하면 될 것을 부모 타입으로 변환해서 시용히는 이유가 무엇일까?
그것은 다형성을 구현하는 기술적 방법 때문이다.
다형성이란 동일한 타입을 사용하지만 다양한 결과가 나오는 성질을 말한다.
주로 필드의 값을 다양화함으로써 실행 결과가 다르게 나오도록 구현하는데,
필드의 타입은 변함이 없지만, 실행 도중에 어떤 객체를 필드로 저장하느냐에 따라 실행 결과가 달라질 수 있다.
이것이 필드의 다형성이다.
자동차를 구성하는 부품은 언제든지 교체할 수 있다.
부품은 고장 날 수도 있고, 보다 더 성능이 좋은 부품으로 교체되기도 한다.
객체 지향 프로그램에서도 마찬가지이다.
프로그램은 수많은 객체들이 서로 연결되고 각자의 역할을 하게 되는데, 이 객체들은 다른 객체로 교체별 수 있어야 한다.
예를 들어 자동차 클래스에 포함된 타이어 클래스를 생각해보자.
자동차 클래스를 처음 설계할 때 사용한 타이어 객체는
언제든지 성능이 좋은 다른 타이어 객체로 교체 할 수 있어야 한다.
새로 교체되는 타이어 객체는 기존 타이어와 사용 방법은 통일하지만 실행 결과는 더 우수하게 나와야 할 것이다.
이것을 프로그램으로 구현하기 위해서는 상속과 오버라이딩 그리고 타입 변환을 이용하는 것이다.
부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있으니
사용 방법이 통일할 것이고, 자식 클래스는 부모의 메소드를 오버라이딩 ( 재정의)해서
메소드의 실행 내용을 변경함으로써 더 우수한 실행 결과가 나오게 할 수 있다.
class Car {
//필드
Tire frontLeftTire = new Tire();
Tire frontRightTire = new Tire();
Tire rearLeftTire = new Tire();
Tire rearRightTire = new Tire();
//메소드
int run() { }
}
-필드 다형성은 타입은 동일하지만(사용방법은 동일하다.),
대입되는 객체가 달라져서 실행의 결과가 다양하게 나올수 있는 것을 말한다.
-Car 클래스에 Tire 필드가 선언되었다.
-Car 객체 생성 후 타이어를 장착하기 위해서 아래와 같이
HankookTire와 KumhoTire객체를 Tire필드에 대입할 수 있다.
-자동 타입변환에 의해서 자식은 부모에게 대입되기 때문이다.
# 매개변수 다형성
-다형성은 필드보다는 메소드를 호출할 때 더 많이 발생한다.
-메소드가 클래스 타입의 매개변수를 가지고 있을 경우 호출할 때 동일한 타입의 객체를 제공하는 것이 정상적이지만
자식 객체를 제공할 수도 있다. 여기서 다형성이 발생한다.
-다음과 같이 Dirver라는 클래스가 있고 veihicle 매개변수를 갖는 drive() 메서드가 정의되어 있다고 가정해보자
-drive 메서드는 매개값으로 전달받은 vehicle의 run() 메소드를 호출한다.
public class Driver {
public void drive() {
veihicle.run()
}
}
일반적으로 drive() 메서드를 호출한다면 다음과 같이 Vehicle 객체를 제공할 것이다.
Driver driver = new Driver();
Veihicle veihicle = new Veihicle()
driver.drive(veihicle)
그러나 매개값으로 Vehicle 객체만 제공할 수 있는 것은 아니다.
자동 타입변환으로 인해 Vehicle의 자식 객체도 제공할 수 있다.
-예시
1. Vehicle 클래스를 만들고, 자식 클래스를 생성한다.
2. Driver이라는 단독적인 클래스를 생성하고, drive 메서드를 생성한 뒤, 매개변수에 Vehicle 타입에 vehicle을 넣어준다. 그러고, vehicle.run 메서드를 실행 메서드로 넣어준다.
3. 메인 클래스에서 new 생성자로 객체를 만들어서 클래스의 정보를 받아온 뒤에, driver(driver 변수).drive(메서드 실행)(vehicle과 관련된 본인 및 자식 클래스)를 연결시켜준다.
-예제 제품 구입후 구매금액 차감과 보너스 금액 적립
※ 여기서 Override를 해줬는데 사실 Override 말고도, Product의 생성자에서 매개변수로 String ProductName을 받아버리면 되는거라 둘다 맞는 해결 방법이다.
※ 이 문제를 언급하는 이유는 아래에 p.productName을 넣었을 때 생성자에 매개변수 값을 받지 않고 넣으면 그냥 부모 클래스에 있는 String productName 값을 받아버리기 때문에 값이 null로 나왔기 때문이다.
궁금증이 해결방법을 하나 더 만든셈..
(그리고 아래에 return 값을 넣어주지 않으면, 메서드가 끝나지 않고 쭉 실행되기에 내가 가지고 있는 money의 값보다 더 큰 상품도 가격 계산이 되어버려서 문제가 발생된다 return문이 하나의 break와 같은 셈이다 체리 저소음 적축 사운드도 안들리고 아주 좋은 타건감이다..)
-위에 예제를 새로이 풀어보는 방법
Override와 메소드를 이용해서 값을 받아왔다.
## 강제 타입 변환 (Casting)
강제 타입 변환(Casting)은 부모 타입을 자식 타입으로 변환하는 것을 말한다.
그렇다고 해서 모든 부모 타입을 자식 클래스 타입으로 강제 변환할 수 있는 것은 아니다.
# 조건
* 자식 타입이 부모 타입으로 자동 변환한 후
* 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.
자식클래스 변수 = (자식클래스) 부모클래스타입;
(자식 타입이 부모 타이으로 변환된 상태)
# 강제 타입 변환이 필요한 경우
* 자식 타입이 부모 타입으로 자동 변환하면, 부모 타입에 선언된 필드와 메소드만 사용 가능
* 자식 타입에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환이 필요
-부모 타입을 자식 타입으로 변환하는 것
-조건 : 자식 타입을 부모 타입으로 자동 변환 후, 다시 자식 타입으로 변환할 때
-강제 타입 변환이 필요한 경우
-자식 타입이 부모 타입으로 자동 변환 : 부모 타입에 선언된 필드와 메소드만 사용 가능
-자식 타입에 선언된 필드와 메소드를 다시 사용해야 할 경우
# 객체 타입 확인
-강제 타입 변환은 자식 타입이 부모 타입으로 변환되어있는 상태에서만 가능하기 때문에
다음과 같이 부모 타입의 변수가 부모 객체를 참조할 경우 자식 타입으로 변환할 수 없다.
Parent parent = new Parent(); // 부모 객체 생성
Child child = new Child(); // 자식 객체 생성
child = (child)parent; // 강제 타입 변환할 수 없다.
-매개변수가 클래스 타입인 경우 해당 타입의 객체이거나 해당 객체의 자식 타입이면 매개값으로 사용이 가능하다.
-매개변수의 다형성에서 실제로 어떤 객체가 매개값으로 제공되었는지를 확인하려면 instanceof 연산자를 사용할 수 있다.
-instanceof의 좌항에는 객체가 올수 있고 우항에는 타입이 올 수 있다.
-좌항의 객체가 우항의 타입이며 true를 리턴하고 그렇지 않으면 false를 리턴한다.
Vehicle vehicle = new Vehicle(); // Vehicle 부모 객체 생성
Bus bus = new Bus(); // Bus 자식 객체 생성
vehicle = bus; // 자식 객체를 부모 객체에 대입
boolean result = bus instanceof Vehicle; // bus 객체가 Vehicle 타입으로 생성되었는지를 확인한다.
-instanceof 연산지는 매개값의 타입을 조사할 때 주로 사용된다.
-메소드 내에서 강제 타입 변환이 필요할 경우 반드시 매개값이 어떤 객체인지 instanceof 연산자로 확인하고
안전하게 강제 타입 변환해야 한다.
Vehicle vehicle = new Vehicle(); // Vehicle 부모 객체 생성
Bus bus = new Bus(); // Bus 자식 객체 생성
vehicle = bus; // 자식 객체를 부모 객체에 대입
boolean result = bus instanceof Vehicle; // bus 객체가 Vehicle 타입으로 생성되었는지를 확인한다.
-instanceof를 활용해서 각각의 변수가 무슨 타입이 맞는지 확인시켜주는 예제
## 추상클래스
-추상클래스는 미완성 클래스이기 때문에 객체 생성이 불가능하다.
-자식이 미완성 클래스를 완전한 메서드로 만든다.
-자식 기준 : 내가 형태만 물려받아서 완성시키겠다.
# 추상 클래스의 개념
사전적 의미로 추상(abstract)은 실체 간에 공통되는 특성을 추출한 것을 말한다.
예를 들어 새, 곤충, 물고기 등의 실체에서 공통되는 특성을 추출해보면 동물이라는 공통점이 있다.
또 다른 예로 삼성, 현대, LG 등의 실체에서 공통되는 특성을 추출해보면 회사라는 공통점이 있다.
이와 같이 동물이나 회사는 구체적인 실체라기보다는
실체들의 공통되는 특성을 가지고 있는 추상적인 것이라고 볼 수 있다.
클래스에서도 추상 클래스가 존재한다.
객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면
이 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상클래스라고 한다.
추상 클래스와 실제 클래스는 상속의 관계를 가지고 있다.
추상 클래스가 부모이고 실제 클래스가 자식으로 구현되어
실체 클래스는 추상 클래스의 모든 특성을 물려받고, 추가적인 특성을 가질 수 있다.
여기서 특성이란 필드와 메소드들을 말한다.
예를 들어 Bird.class. Insect.class. Fish.class 등의 실체 클래스에서
공통되는 필드와 메소드를 따로 선언한 Animal.class 클래스를 만들 수 있는데, 이것이 바로 추상클래스라고 볼 수 있다.
추상클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에
객체를 직접 생성해서 사용할 수 없다.
다시 말해서 추상클래스는 new 연산자를 사용해서 인스턴스(객체)를 생성시키지 못한다.
Animal animal = new Animal (); // ( x )
추상클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용된다.
코드로 설명하면 추상 클래스는 extends 뒤에만 올 수 있는 클래스이다.
# 추상 클래스의 용도
실제 클래스들의 공통적인 특성 (필드, 메소드)을 뽑아내어 추상클래스로 만드는 이유가 무엇일까?
다음 두 가지 이유가 있다.
# 첫 번째, 실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적
-실제 클래스를 설계하는 사람이 여러 사람일 경우 실체 클래스마다 필드와 메소드가 제각기 다른이름을 가질 수 있다.
-예를 들어 소유자의 이름을 저장하는 필드를 Telephone에서는 owner라고하고 SmartPhone에서는 user라고 할 수 있다.
그리고 전원을 켜다라는 메소드를 Telephone에서는 turnOn()으로 설계하고
SmartPhone에서는 powerOn() 이라고 설계할 수 있다.
-동일한 데이터와 기능임에도 불구하고 이름이 다르다 보니, 객체마다 사용 방법이 달라진다.
이것보다는 Phone이라는 추상 클래스에 소유자인 owner 필드와 turnOn() 메소드를 선언하고,
Telephone과 SmartPhone은 Phone을 상속함으로써 필드와 메소드 이름을 통일시킬 수 있다.
※ 여러 사람이 프로젝트를 만들면 필드명과 메서드의 이름이 다르지만, 의미는 같을 수 있기 때문에, 하나의 공통적으로 사용되는 필드와 메서드등을 만들어주는게 이 아이의 목표이다.
# 두 번째, 실체 클래스를 작성시 시간 절약
공통적인 필드와 메소드는 추상 클래스인 Phone에 모두 선언해 두고
실체 클래스마다 다른 점만 실체 클래스에 선언하게 되면 실제 클래스를 작성하는데 시간을 절약할 수 있다.
# 추상 클래스 선언
추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙여야 한다.
abstract를 붙이게 되면 new 연산자를 이용해서 객체를 만들지 못하고 상속을 통해 자식 클래스만 만들 수 있다.
public abstract class 클래스 {
//필드
//생성자
//메소드
}
추상 클래스도 일반 클래스와 마찬가지로 필드 생성자 메소드 선언을 할 수 있다.
new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때
super(...)를 호출해서 추상클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 한다.
public void method() { -> 선언부
// 실행할 구문 -> 구현부
}
※ 추상클래스의 메서드를 상속되어 있는 자식 클래스에서 사용하려면, 무조건 재정의 (Override)를 해야 사용이 가능하다.
-고양이와 강아지가 짖는 소리가 다르기 때문에 abstract로 지정해 재정의를 필수적으로 받아준다.
package example0424.example03;
public abstract class Animal { // 추상 클래스
String name;
int age;
String type;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void move() {
System.out.println(type + "이(가) 이동합니다.");
}
public void eat() {
System.out.println(type + "이(가) 사료를 먹습니다.");
}
public abstract void bark(); // 추상 메서드
}
package example0424.example03;
public class Cat extends Animal{
public Cat(String name, int age) {
super(name,age);
}
@Override
public void bark() {
System.out.println("고양이가 우네요 야아아아아옹");
}
}
-Cat 클래스를 만들어주려면 Animal에 상속받는 생성자 매개변수와 오버라이드된 메서드를 꼭 넣어줘야 생성된다.
'java(2)↗' 카테고리의 다른 글
java 예외 (0) | 2024.04.25 |
---|---|
java 인터페이스 (0) | 2024.04.24 |
java 상속 (0) | 2024.04.23 |
java 클래스(2) (0) | 2024.04.22 |
java 클래스 (0) | 2024.04.19 |