1. 강제 형변환
1-1. 강제 클래스 형변환 개념
- 강제 클래스 형변환
- 실제로 생성한 하위클래스 타입의 객체를 상위클래스 타입의 참조변수가 참조하고 있는 경우,
실제 생성한 객체의 속성과 기능을 사용할 수 없다.
- 실제로 생성한 객체가 (상위클래스로) 클래스 형변환 되어 참조되고 있는 경우,
실제 생성한 객체를 참조하기 위해서는 강제 형변환 필요.
- 상위클래스타입 참조변수1 = new 하위클래스타입( ); // 자동 클래스 형변환
- 참조변수1은 하위클래스타입 객체의 손성과 기능을 사용할 수 없음
- 하위클래스타입 참조변수2 = (하위클래스타입) 참조변수1; // 강제 클래스 형변환
- 참조변수1이 참조하는 객체에서 지정된 하위클래스타입의 주소값을 참조변수2에 대입
- 참조변수2는 하위클래스타입 객체의 속성과 기능을 사용할 수 있다.
- Phone phone1 = new Iphone( );
하위객체인 Iphone객체를 상위객체인 Phone객체에 담을거야
phone1 변수 -> Iphone객체의 상위객체인 Phone 객체를 참조
* phone1은 Phone객체의 tell(), sms() 기능만 실행
SmartPhone phone2 = (SmartPhone) phone1;
phone1이 바라보는 상위객체 Phone객체를 하위객체 SmartPhone객체에 넣을거야 -> 강제형변환
이제 phone1은 SmartPhone객체를 참조
* phone2는 SmartPhone객체의 internet(), sendEmail() 기능도 실행 가능
Iphone phone3 = (Iphone) phone1;
phone1이 바라보는 상위객체 SmartPhone객체를 하위객체 Iphone객체에 넣을거야 -> 강제형변환
* phone3은 Iphone객체의 applePay(), appleMusic(), faceId() 기능도 실행 가능
※ Iphone타입의 참조변수를 따로 만들지 않고, 강제형변환해서 Iphone객체의 속성과 기능 사용 (간단한 경우)
((Iphone)phone1).applePay();
((Iphone)phone1).appleMusic();
((Iphone)phone1).faceId();
- Phone phone1 = new Iphone( );
Car car1 = new MediumCar(); // Car의 하위객체인 Medium객체를 car1에 넣고싶어
// 하위객체 Medium을 상위객체 Car에 넣음
// => 자동형변환 (Medium -> Car)
// car1 변수는 Medium의 상위객체인 Car 객체를 바라보고 있음
// car1변수는 MediumCar의 상위객체인 Car객체의 속성, 기능만 사용 가능
Object obj = car1; // car1이 참조하는 객체의 주소값을 Object객체의 obj로 대입
// 하위객체 Car를 상위객체 Object에 넣음
// => 자동형변환 ( Car -> Object )
// obj 변수는 Car의 상위객체인 Object 객체를 바라보고 있음
LightCar car2 = (LightCar) car1; // car1이 참조하는 객체의 주소값을 LightCar 객체의 car2에 넣고싶어
// 상위객체 Car를 하위객체 LightCar에 넣을 수 없어 (참조할 수 없어)
// => 강제형변환 ( Car -> Light )
// Light객체의 속성, 기능도 사용 가능
LuxuryCar car3 = (LuxuryCar) car1; // 처음에 car1 이 MediumCar 객체를 만들고 대입한거 (Luxury는 없어)
// -> Medium을 Luxury로 강제형변환 할 수 없어
// ClassCastException 오류
// 문법상 오류는 없는데, 클래스형변환이 올바르지 않을 때 발생하는 오류
1-2. 강제 클래스 형변환 사용
public class CarApp3 {
public static void main(String[] args) {
System.out.println("## MediumCar객체를 Car타입의 참조변수로 참조 ##");
Car car1 = new MediumCar();
car1.speedUp(); // Car객체의 공개된 기능 사용가능
car1.speedDown(); // Car객체의 공개된 기능 사용가능
car1.drive(); // Car객체의 공개된 기능 사용가능
car1.stop(); // Car객체의 공개된 기능 사용가능
// car1 참조변수가 참조하는 객체는 실제로는 MediumCar 객체를 생성했을 때 생성된 Car 객체
// car1 참조변수가 참조하는 객체의 MediumCar 객체를 참조하게 할 수 있다. (강제 형변환)
System.out.println("## 강제 클래스형변환으로, 참조변수가 MediumCar 객체를 참조 ##");
MediumCar car2 = (MediumCar) car1;
car2.speedUp(); // Car객체의 공개된 기능 사용가능
car2.speedDown(); // Car객체의 공개된 기능 사용가능
car2.drive(); // Car객체의 공개된 기능 사용가능
car2.stop(); // Car객체의 공개된 기능 사용가능
car2.audio(); // LightCar객체의 공개된 기능 사용가능
car2.aircon(); // MediumCar객체의 공개된 기능 사용가능
car2.navigation(); // MediumCar객체의 공개된 기능 사용가능
// 참조변수를 따로 사용하지 않고, 강제형변환해서 기능 사용하기
System.out.println("## 참조변수를 따로 생성하지 않고, 강제형변환해서 기능 사용 ##");
((LightCar)car1).audio();
((MediumCar)car1).navigation();
// car1 참조변수가 참조하는 객체는 실제로 MediumCar객체를 생성했을 때 생성된 Car객체
// car1 참조변수가 참조하는 객체의 어디에도 LuxuryCar객체는 존재하지 않는다.
// LuxuryCar car3 = (LuxuryCar) car1; 이 코드에서 ClassCastException 예외 발생
// ClassCastException은 클래스형변환이 올바르지 않을 때 발생하는 오류
LuxuryCar car3 = (LuxuryCar) car1; // ClassCastException 오류 발생
car3.speedUp();
car3.speedDown();
car3.drive();
car3.stop();
car3.audio();
car3.aircon();
car3.navigation();
car3.autoDrive();
}
}
## MediumCar객체를 Car타입의 참조변수로 참조 ##
## 속도 증가. 현재속도: 10
## 운전 중 ##
## 정차 중 ##
## 강제 클래스형변환으로, 참조변수가 MediumCar 객체를 참조 ##
## 속도 증가. 현재속도: 20
## 속도 감소. 현재속도: 10
## 운전 중 ##
## 정차 중 ##
## 오디오 재생 ##
## 에어컨 가동중 ##
## 길안내 실행중 ##
## 참조변수를 따로 생성하지 않고, 강제형변환해서 기능 사용 ##
## 오디오 재생 ##
## 길안내 실행중 ##
Exception in thread "main" java.lang.ClassCastException: class day13.car.MediumCar cannot be cast to class day13.car.LuxuryCar (day13.car.MediumCar and day13.car.LuxuryCar are in unnamed module of loader 'app')
at day13.car.CarApp3.main(CarApp3.java:66)
중요 1) car1 참조변수가 참조하는 객체는 실제로는 MediumCar 객체를 생성했을 때 생성된 Car 객체
car1 참조변수가 참조하는 객체의 MediumCar 객체를 참조하게 할 수 있다. (강제 형변환)
MediumCar car2 = (MediumCar) car1;
중요 2) 참조변수를 따로 사용하지 않고, 강제형변환해서 기능 사용하기 (간단하게 사용하는 경우)
((LightCar)car1).audio();
((MediumCar)car1).navigation();
1-3. 참조변수가 참조하는 객체 안에 어떤 객체가 있는지 알아내기
- 배열에서 클래스 형변환하는 경우
- cars변수가 참조하는 배열객체에는 Car타입의 객체만 담을 수 있어
1번째 칸에 넣을려고 LightCar를 생성했는데, Car만 들어갈 수 있어
-> 원래는 LightCar를 바라봐야 되는데, Car만 담을 수 있기 때문에
-> 자동형변환 (LightCar -> Car) - 0번째, 1번째,2번째에서 꺼내도 Car객체의 기능들만 이용할 수 있어
- 모두 Car를 참조하고 있기 때문에 일단 Car객체의 기능은 확실히 들어있는거 알겠는데,
실제 만들어진 객체가 Light인지 Medium인지 몰라
그 만들어진 객체의 기능까지 이용할 수 있게 강제 형변환을 할거야
어떤 객체의 기능을 이용하기 위해서는 그 객체로 강제 형변환을 해야되는데, 그 객체가 뭔지를 아직 모르잖아
-> 실제 생성한 객체가 뭔지 알아야돼
- instance of 연산자를 이용해서 내가 찾고싶은 객체가 있는지 없는지 확인
* a(참조변수) instanceof b(객체) : a가 참조하는 객체에 내가 찾고싶은 b타입의 객체가 있는지 확인하는 연산자
public class CarApp4 {
public static void main(String[] args) {
Car car1 = new Car();
LightCar car2 = new LightCar();
MediumCar car3 = new MediumCar();
LuxuryCar car4 = new LuxuryCar();
boolean result1 = car1 instanceof Car; //car1이 참조하는 객체가 Car인지 물어보는 연산자
boolean result2 = car1 instanceof LightCar;
boolean result3 = car1 instanceof MediumCar;
boolean result4 = car1 instanceof LuxuryCar;
System.out.println("car1이 참조하는 객체에 Car 타입의 객체가 있습니까?" + result1);
System.out.println("car1이 참조하는 객체에 LightCar 타입의 객체가 있습니까?" + result2);
System.out.println("car1이 참조하는 객체에 MediumCar 타입의 객체가 있습니까?" + result3);
System.out.println("car1이 참조하는 객체에 LuxuryCar 타입의 객체가 있습니까?" + result4);
boolean result5 = car2 instanceof Car;
boolean result6 = car2 instanceof LightCar;
boolean result7 = car2 instanceof MediumCar;
boolean result8 = car2 instanceof LuxuryCar;
System.out.println("car2가 참조하는 객체에 Car 타입의 객체가 있습니까?" + result5);
System.out.println("car2가 참조하는 객체에 LightCar 타입의 객체가 있습니까?" + result6);
System.out.println("car2가 참조하는 객체에 MediumCar 타입의 객체가 있습니까?" + result7);
System.out.println("car2가 참조하는 객체에 LuxuryCar 타입의 객체가 있습니까?" + result8);
boolean result9 = car4 instanceof Car;
boolean result10 = car4 instanceof LightCar;
boolean result11 = car4 instanceof MediumCar;
boolean result12 = car4 instanceof LuxuryCar;
System.out.println("car4가 참조하는 객체에 Car 타입의 객체가 있습니까?" + result9);
System.out.println("car4가 참조하는 객체에 LightCar 타입의 객체가 있습니까?" + result10);
System.out.println("car4가 참조하는 객체에 MediumCar 타입의 객체가 있습니까?" + result11);
System.out.println("car4가 참조하는 객체에 LuxuryCar 타입의 객체가 있습니까?" + result12);
car1이 참조하는 객체에 Car 타입의 객체가 있습니까?true
car1이 참조하는 객체에 LightCar 타입의 객체가 있습니까?false
car1이 참조하는 객체에 MediumCar 타입의 객체가 있습니까?false
car1이 참조하는 객체에 LuxuryCar 타입의 객체가 있습니까?false
car2가 참조하는 객체에 Car 타입의 객체가 있습니까?true
car2가 참조하는 객체에 LightCar 타입의 객체가 있습니까?true
car2가 참조하는 객체에 MediumCar 타입의 객체가 있습니까?false
car2가 참조하는 객체에 LuxuryCar 타입의 객체가 있습니까?false
car4가 참조하는 객체에 Car 타입의 객체가 있습니까?true
car4가 참조하는 객체에 LightCar 타입의 객체가 있습니까?true
car4가 참조하는 객체에 MediumCar 타입의 객체가 있습니까?true
car4가 참조하는 객체에 LuxuryCar 타입의 객체가 있습니까?true
- 향상된 for문, instanceof를 사용
public class CarApp4 {
public static void main(String[] args) {
Car car1 = new Car();
LightCar car2 = new LightCar();
MediumCar car3 = new MediumCar();
LuxuryCar car4 = new LuxuryCar();
Car[] cars = new Car[4];
cars[0] = car1;
cars[1] = car2;
cars[2] = car3;
cars[3] = car4;
for(Car car : cars) { // cars가 참조하는건 Car배열객체, 여기서 꺼내는건 모두 Car타입 객체
System.out.println(car instanceof Car);
System.out.println(car instanceof LightCar);
System.out.println(car instanceof MediumCar);
System.out.println(car instanceof LuxuryCar);
}
true
false
false
false
true
true
false
false
true
true
true
false
true
true
true
true
- (최종 사용범) 객체를 찾아서 강제형변환하고, 기능 이용하기
public class CarApp4 {
public static void main(String[] args) {
Car car1 = new Car();
LightCar car2 = new LightCar();
MediumCar car3 = new MediumCar();
LuxuryCar car4 = new LuxuryCar();
Car[] cars = new Car[4];
cars[0] = car1;
cars[1] = car2;
cars[2] = car3;
cars[3] = car4;
for(Car car : cars) {
System.out.println("## car가 참조하는 객체의 기능 실행해보기 ##");
car.speedUp();
car.speedDown();
car.drive();
car.stop(); // 여기까지는 다 Car의 기본 기능이니까 다 됨
if(car instanceof LightCar) { // 배열에서 뽑은 객체중에 LightCar가 있으면, 얘로 형변환해서 이 기능 실행
((LightCar)car).audio();
}
if(car instanceof MediumCar) { // 배열에서 뽑은 객체중에 MediumCar가 있으면 이 기능 실행
((MediumCar)car).navigation();
((MediumCar)car).aircon();
}
if(car instanceof LuxuryCar) {
((LuxuryCar)car).autoDrive();
}
System.out.println();
}
}
}
## car가 참조하는 객체의 기능 실행해보기 ##
## 속도 증가. 현재속도: 10
## 속도 감소. 현재속도: 0
## 운전 중 ##
## 정차 중 ##
## car가 참조하는 객체의 기능 실행해보기 ##
## 속도 증가. 현재속도: 10
## 속도 감소. 현재속도: 0
## 운전 중 ##
## 정차 중 ##
## 오디오 재생 ##
## car가 참조하는 객체의 기능 실행해보기 ##
## 속도 증가. 현재속도: 10
## 속도 감소. 현재속도: 0
## 운전 중 ##
## 정차 중 ##
## 오디오 재생 ##
## 길안내 실행중 ##
## 에어컨 가동중 ##
## car가 참조하는 객체의 기능 실행해보기 ##
## 속도 증가. 현재속도: 10
## 속도 감소. 현재속도: 0
## 운전 중 ##
## 정차 중 ##
## 오디오 재생 ##
## 길안내 실행중 ##
## 에어컨 가동중 ##
## 자율운전중 ##
cars변수가 가리키는 배열에서 첫번째 Car타입 객체를 꺼내서 car변수에 담아
-> 첫번째 Car 타입은 Car객체야
-> Car 객체의 기본 기능들을 실행해
-> if문 실행 -> LightCar, MediumCar, Luxury 객체가 있으면 기능실행하는데, Car객체에는 없어
-> 두번째 Car 타입은 LightCar 객체야
-> Car 객체의 기본 기능들을 일단 실행하고
-> if문 실행 -> LightCar 객체가 있으면 기능 실행 -> 있으니까 저 기능을 실행해
1-4. 매개변수의 타입을 통해서 해당 기능이 지원되는 객체만 매개변수 인자로 선별해서 제공받기
( instanceof 기능 필요 X )
1) 타입이 다르니까 Medium을 찾아갈건데, Medium이 없어 -> 참조실패 (컴파일 오류)
2) Medium을 참조할건데 있어 -> 참조
그 Medium 객체 안에 있는 navi 기능 사용 가능
3) Luxury객체의 상위객체인 Medium를 바라봄 (자동형변환)
그 Medium 객체 안에 있는 navi 기능 사용 가능
* instanceof를 통해서 내가 찾는 객체가 있는지 없는지 확인할 필요 없이,
매개변수 타입을 지정하면 -> 그 타입의 객체를 갖고 있는 객체들만 연결(참조)이 돼
-> 참조할 수 없는 객체는 아예 걸러짐
-> 매개변수의 타입을 통해서 객체의 범위를 정해줘버림
public class CarApp5 {
public static void main(String[] args) { // 객체생성없이 메인메소드에서 바로 호출하기 위해 아래 메소드들을 만듦
System.out.println("## 자동차 주행기능 테스트 ##");
CarApp5.testDrive(new Car()); // 이 메소드의 매개인자로는 Car객체를 갖고있는 모든 객체가 가능
CarApp5.testDrive(new LightCar());
CarApp5.testDrive(new MediumCar());
CarApp5.testDrive(new LuxuryCar());
System.out.println("## 자동차 오디오기능 테스트 ##");
// CarApp5.testAudio(new Car()); // Car는 이기능을 쓸 수 없어. 매개인자로 넣을 수도 없음.
CarApp5.testAudio(new LightCar()); // LightCar 객체를 포함하는 모든 객체가 가능
CarApp5.testAudio(new MediumCar());
CarApp5.testAudio(new LuxuryCar());
System.out.println("## 자동차 길안내기능 테스트 ##");
// CarApp5.testNavigation(new Car());
// CarApp5.testNavigation(new LightCar());
CarApp5.testNavigation(new MediumCar());
CarApp5.testNavigation(new LuxuryCar());
System.out.println("## 자동차 자율주행기능 테스트 ##");
// CarApp5.testAutoDrive(new Car());
// CarApp5.testAutoDrive(new LightCar());
// CarApp5.testAutoDrive(new MediumCar());
CarApp5.testAutoDrive(new LuxuryCar());
}
// 객체생성 없이 메인메소드에서 바로 호출하기 위해 static 메소드들 사용
public static void testDrive(Car car) { // Car, LightCar, MediumCar, LuxuryCar 객체는 drive 기능을 테스트할 수 있다.
car.drive(); // 이 메소드의 매개인자로는 Car 타입의 객체를 넣을거야
}
public static void testAudio(LightCar car) { // LightCar, MediumCar, LuxuryCar 객체는 audio 기능을 테스트할 수 있다.
// LIghtCar car = new Car(); 가 안담기니까 Car객체는 여기에 포함될 수 없어
car.audio(); // 이 메소드의 매개인자로는 LightCar 타입의 객체를 넣을거야
}
public static void testNavigation(MediumCar car) { // MediumCar, LuxuryCar 객체는 navigation 기능을 테스트할 수 있다.
// MediumCar car = new LuxuryCar(); 를 하면 형변환없이 navi기능을 이용할 수 있다.
car.navigation(); // 이 메소드의 매개인자로는 MediumCar 타입의 객체를 넣을거야
}
public static void testAutoDrive(LuxuryCar car) { // LuxuryCar 객체는 autoDrive 기능을 테스트할 수 있다.
car.autoDrive(); // 이 메소드의 매개인자로는 LuxuryCar 타입의 객체를 넣을거야
}
}
<<자동차의 주행기능 테스트>>
## 운전 중 ##
## 운전 중 ##
## 운전 중 ##
## 운전 중 ##
<<자동차의 오디오기능 테스트>>
## 오디오 재생 ##
## 오디오 재생 ##
## 오디오 재생 ##
<<자동차의 길안내기능 테스트>>
## 길안내 실행중 ##
## 길안내 실행중 ##
<<자동차의 자율주행기능 테스트>>
## 자율운전중 ##
2. 메소드 재정의와 다형성
- 메소드 재정의
- 하위클래스로 갈 수록
기능 추가 + 본연의 기능 향상 - 부모객체로부터 상속받아서 기능을 추가할거야
-> 기능마다 다른 메소드를 사용하면 -> 엄청 많은 종류의 메소드가 나옴
-> 부모객체의 메소드에 기능을 추가해서 메소드 재정의
- 메소드 재정의를 사용하지 않은 경우 -> 소스코드 변경 필요
컴퓨터 객체 안의
- 프린터 printer = new 프린터( ); // 이 printer 변수는 프린터객체를 바라보고 있음
- void print(Data data) { // 프린터객체의 기능
흑백출력 기능;
}
- void 리포트출력하기(Data data) { // 리포트출력하기라는 메소드를 만들고
printer.print(data); // printer가 가리키는 객체의 print메소드 실행하면 흑백출력기능이 실행됨
} // 프린터를 레이저프린터로 변경해도,
printer가 가리키는 객체의 print 메소드는 여전히 흑백출력기능임
근데 이제 프린터를 바꿀거야.
프린터 -> 레이저프린터로 바꾸면
프린터 객체, 프린터 printer = new 프린터( ); 도 지워지고
레이저프린터가 연결돼
- 프린터 printer = new 레이저프린터( ); // 이제 이 프린터 객체를 상속받아서 레이저프린터 객체를 만들거야
- void laserPrint(Data data) { // 레이저프린터객체의 기능
레이저출력 기능;
}
근데 이 상태에서
void 리포트출력하기(Data data) {
Printer.print(data)
}
메소드를 실행하면 , 여전히 흑백출력이 실행됨! ( 프린터 타입의 객체를 바라보니까 )
연결은 가능해서 연결을 했는데, 내가 원하는 기능 실행이 안돼
내가 원하는 기능을 실행하려면, 수행문을 새로 작성해야 함
- void 리포트출력하기(Data data) {
((레이저프린터) printer).laserPrint(data); // printer가 가리키는 객체는 레이저프린터의 상위객체인 프린터 객체
// 레이저프린터의 레이저 출력기능을 이용하기 위해 강제 형변환 필요
// 수행문을 새로 작성해야 함
(소스코드를 변경하지 않고 printer.print(date)로 하면, 흑백출력 실행됨)
=> 새 메소드를 정의하더라도, 실행이 안돼 => 수행문을 바꿔야돼
=> 비효율적이야
- 메소드 재정의 -> 소스코드를 바꿀 필요가 없음
- 프린터 printer = new 프린터( ); // 이 printer 변수는 프린터객체를 바라보고 있음
- void print(Data data) { // 프린터객체의 기능
흑백출력하기;
}
- void 리포트출력하기(Data date) {
printer.print(data) // 흑백출력하기 실행
} (printer가 가리키는 객체의 print메소드는 흑백출력하는 기능이니까)
- 프린터 printer = new 레이저프린터( ); // 프린터 객체를 상속받아서 레이저프린터 객체 생성
- void print(Data data) {
레이저출력하기; // 레이저프린터 객체의 print 메소드는 레이저출력하기 (메소드 재정의)
}
- void 리포트출력하기(Data date) {
printer.print(data) // 레이저출력하기 실행
}
* 이때 컴퓨터 객체의 printer변수는 상위객체인 프린터 객체를 바라보고 있지만,
그럼에도 레이저출력하기가 실행 (재정의된 메소드 실행)
-> 생성한 객체의 제일 마지막에 재정의 된 것을 실행
* 변수의 타입은 항상 프린터
(레이저가 올지, 잉크젯이 올지 어떤 프린터가 올지 모르지만, 부모객체로 해놓으면 뭐가 와도 상관이 없음)
- Printer 객체
public class Printer {
public void print(String data) {
System.out.println("["+data+"]를 흑백으로 출력.");
}
}
- LaserPrinter 객체 (재정의)
public class LaserPrinter extends Printer {
// Printer 클래스로부터 상속받은 void print(String data) 메소드를 재정의
public void print(String data) {
System.out.println("["+data+"]를 레이저 출력방식으로 출력");
}
}
- 실행 객체
public class PritnerApp {
public static void main(String[] args) {
Printer p1 = new Printer();
p1.print("회계분석 자료");
Printer p2 = new LaserPrinter(); //재정의된 메소드 출력
p2.print("재고량 분석 참고");
}
}
[회계분석 자료]를 흑백으로 출력.
[재고량 분석 참고]를 레이저 출력방식으로 출력
- 메소드 재정의 하기위한 변수의 타입 결정
obj.print(data)를 실행하면 원래 '레이저출력하기'가 실행이 되어야 하는데,
obj가 가리키는 Object 객체 안에 print 메소드가 없어
print 메소드를 실행하려면 적어도 print 메소드를 갖고 있는 Printer 객체는 바라보고 있어야돼
가리키는 객체에 메소드가 없으면, 아예 실행을 할 수가 없어 -> 컴파일 오류
그래서, 어떤 객체를 바라보든지 간에
print메소드를 실행하려면, 적어도 이 메소드를 갖고있는 Printer 객체보다는 하위객체여야돼
- 같은 메소드를 갖고 있는 객체들 끼리만 메소드 재정의 할 수 있음. 그중 제일 상위객체를 참조해야 함
- 연결 객체만 바꿔서 다르게 출력하기 (의존 객체를 스스로 생성)
import day14.ColorLaserPrinter;
import day14.Printer;
public class Company {
private Printer printer = new ColorLaserPrinter(); //다른 패키지에 있는것은 항상 import 해야 함
//프린터를 여러상황에서 사용할거니까 위에다가 빼줌
//얘가 연결잭인데, 메소드 안에 만들면 해당 메소드 내에서만 사용할 수 있으니까
public void printReporter(String data) {
// 프린터객체의 출력기능 사용해서 리포터 출력
System.out.println("### 보고서 출력 ###");
printer.print(data);
}
public void printChart(String data) {
// 프린터객체의 출력기능 사용해서 차트 출력
System.out.println("### 차트 출력 ###");
printer.print(data);
}
public void printPdf(String data) {
//프린터객체의 출력기능 사용해서 pdf 출력
System.out.println("### PDF 출력 ###");
printer.print(data);
}
}
public class CompanyApp {
public static void main(String[] args) {
Company company = new Company();
company.printReporter("2분기 실적 분석 보고서"); //컬러레이저출력 (ColorLaserPrinter가 연결되어있으니까)
company.printChart("2분기 실적 분석 차트");
company.printPdf("2분기 실적 분석.pdf");
}
}
### 보고서 출력 ###
[2분기 실적 분석 보고서] 자료를 컬러레이저 출력방식으로 출력
### 차트 출력 ###
[2분기 실적 분석 차트] 자료를 컬러레이저 출력방식으로 출력
### PDF 출력 ###
[2분기 실적 분석.pdf] 자료를 컬러레이저 출력방식으로 출력
- 이걸 has a, is a 관계로 나타내면
company has a printer (의존)
laserprinter is a printer (포함)
colorlaserprinter is a printer (포함)
- private Printer printer = new Printer(); 에서
// 이게 연결 잭 // 여기에 이 객체를 조립(연결)
- 근데 이제 컬러로 뽑고싶으면?
private Printer printer = new ColorLaserPrinter();
// 이 연결 객체만 바꾸면, 소스코드를 하나도 안바꿔도 자동으로 컬러 출력
- print라는 메소드를 실행할 수만 있으면 -> 메소드를 얼마든지 재정의 할 수 있어
- 연결 객체 없이 다르게 출력하기 (의존성 주입 - 의존객체를 외부에서 전달받음)
<의존성 역전>
1) 의존하는 객체(필요한 객체)를 스스로 생성
Printer printer = new Printer( );
Printer printer = new ColorLaserPrinter( );
===> 필요한 객체를 직접 입력
- 의존성 주입-
2) 의존하는 객체(필요한 객체)를 외부에서 전달받음
제3의 객체에서
Advanced~는 구체적인 클래스 이름을 몰라도돼
(AdvancedCompany 객체의 내용은 하나도 안건들고)
private Printer printer ;
아무거나 연결해도 가능한 연결잭을 일단 만들어놓는거야.
<CompanyApp 객체>
1) Printer printer = new Printer();
Printer 객체를 바라보고 있는 printer변수의 주소값을 -> company.setPrinter(printer); 로 대입
-> company가 가리키는 객체의 setPrinter(Printer printer) 매개변수로 -> 이 매개변수를 통해 this객체의 printer변수로
-> private Printer printer; 로
=> AdvancedCompany 객체의 printer 변수에 Printer 객체의 주소값이 들어감 (조립) ( 잭에 각 객체 연결을 안해도 )
( 의존성 주입, 필요한 객체 제공 )
2) ColorLaserPrinter p = new ColorLaserPrinter( );
ColoLaserPrinter객체의 주소값을 p3에 대입해서 company.setPrinter(Printer printer)의 매개변수로 대입하려는데
setPrinter의 매개변수로는 Printer 객체 타입을 받아야돼
-> 클래스 형변환 -> ColorLaserPrinter의 상위객체인 Printer객체의 주소값이 들어감
(내가 전해준 건 ColorLaserPrinter의 주소값이지만, 얘가 받은건 Printer의 주소값)
(근데 처음에 아예 바로 Printer p = new ColorLaserPrinter로 해도 됨)
-> 매개변수로 받은 Printer 객체의 주소값이 AdvancedCompany객체의 printer 변수로 들어감
=> AdvancedCompany 객체는 Printer 객체를 참조함 (조립)
-> printer.print(data);의 printer가 가리키는게 Printer객체니까 그 기능(흑백출력)을 실행하는데,
메소드가 재정의 되어있어 (생성된 객체에서 제일 마지막에 재정의 된 메소드가 실행) -> 컬러레이저출력 실행
* AdvancedCompany 입장에서는
잭의 종류를 Printer로 적으면 Printer가 연결되든, ColorPrint가 연결되든 항상 Printer 객체를 참조함
( Printer 객체 타입만 받으니까 어떤 객체가 연결되든간에 클래스 형변환을 통해 Printer 객체만 참조 )
-> 근데 재정의된 메소드가 있으면 -> 그걸 실행 -> 메소드 재정의에 의해서 각각 다른 기능의 메소드 사용 가능
-> 소스코드 변경 없이 , 자신이 사용하는 객체(조립되는 객체)를 바꿀 수 있음
import day14.Printer;
public class AdvancedCompany {
// Printer류 객체와 연결되는 멤버변수
private Printer printer;
// Printer류 객체를 매개변수 인자로 전달받아서 멤버변수에 대입
public void setPrinter(Printer printer) { //객체를 달라는 의미
this.printer = printer;
}
public void printReporter(String data) {
// 프린터객체의 출력기능 사용해서 리포터 출력
System.out.println("### 보고서 출력 ###");
printer.print(data);
}
public void printChart(String data) {
// 프린터객체의 출력기능 사용해서 차트 출력
System.out.println("### 차트 출력 ###");
printer.print(data);
}
public void printPdf(String data) {
//프린터객체의 출력기능 사용해서 pdf 출력
System.out.println("### PDF 출력 ###");
printer.print(data);
}
}
import day14.ColorLaserPrinter;
import day14.LaserPrinter;
import day14.Printer;
public class AdvancedCompanyApp {
public static void main(String[] args) {
Printer p1 = new Printer();
LaserPrinter p2 = new LaserPrinter();
ColorLaserPrinter p3 = new ColorLaserPrinter();
AdvancedCompany company = new AdvancedCompany();
company.setPrinter(p1); // 이 변수만 바꿔주면 객체가 바뀜
company.printReporter("2분기 경영실적 분석 보고서");
company.printChart("2분기 경영실적 분석 차트");
}
}
### 보고서 출력 ###
[2분기 경영실적 분석 보고서]를 흑백으로 출력.
### 차트 출력 ###
[2분기 경영실적 분석 차트]를 흑백으로 출력.
다형성 요약
- 메소드 중복정의
- 하나의 클래스에 안에 동일한 이름의 메소드를 여러 개 정의하는 것
- 접근제한자, 반환타입의 상관없다.
- 메소드이름이 동일하고, 매개변수의 타입, 갯수가 다르기만 하면 된다.
- 목적
- 하나의 클래스안에 매개변수만 다르고 수행하는 작업은 비슷한 경우, 일관성있고 동일한 메소드 이름으로 실행하게 한다.
- 메소드 재정의
- 상속/구현관계에 있는 클래스의 자식 클래스에서 부모로부터 물려받은 메소드를 재정의하는 것이다.
- 방법
- 반환타입, 메소드이름, 매개변수의 갯수와 타입이 모두 일치하고, 구현내용(수행문의 내용)만 재정의하는 것
- 접근제한자는 부모측의 접근제한자와 동일하거나 더 느슨하게 정의해야 한다.
- 내용
- 동일한 업무지만 자식클래스들 마다 세부 업무내용이 다른 경우 구체적인 구현을 자식클래스들이 부모의 메소드를 재정의해서 구현하도록 하는 것이다.
- 목적
- 부모의 메소드와 자식의 메소드가 메소드 재정의 관계를 형성하게 되면 부모객체를 참조하고 있어도 자식에 재정의된 메소드를 실행할 수 있게 할 수 있다.
- 객체가 달라지더라도, 사용방법이 똑같게 하기 위함
- 필드의 다형성, 매개변수의 다형성을 획득할 수 있다.
- 똑같은 사용방법인데 어떤 객체를 연결했냐에 따라서 결과가 달라 => 다형성 (확장에 열려있다)
- 다형성의 조건
1) 모두 Printer의 한 종류여야 돼
2) 모두 메소드 재정의를 하고 있어야돼
*객체지향 개발방법 5대 원칙
1) S (단일책임) - 너는 선언기능만, 너는 업무로직기능만, 너는 실행기능만해 (각자 맡은일만 함)
2) O (개방폐쇄) - 확장에 열려있고, 변화에 닫혀있음 ( 다형성 )
3) L
4) I
5) D
'수업내용 > Java' 카테고리의 다른 글
[2022.09.26.월] 추상화클래스 활용 (0) | 2022.09.26 |
---|---|
[2022.09.23.금] 추상화, 인터페이스 (0) | 2022.09.24 |
[2022.09.21.수] 생성자 메소드, 클래스형변환 (0) | 2022.09.21 |
[2022.09.20.화] static, 상속 (0) | 2022.09.20 |
[2022.09.19.월] 클래스의 역할 분담 (0) | 2022.09.20 |