다형성은 아주 중요하다. 왜냐면 강사님이 그렇게 말하심. 그래서 다형성을 구현하는 과제를 내주셨다. 하지만 난 제대로 해내지 못했다. 수요일까지로 기한이 늘어났다. 그러니... 오늘 복습을 하고! 제대로 복습 하고! 다시 열심히 해보자 ^^... 힝 어려워.
다형성: 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질
예... 맞는 설명이긴 한데, 이런식으로 보니까 더 어려움. 타입에 객체 여러개를 갈아 끼워서 사용할 수 있게 만들어주는 것이 다형성이다.
다형성을 위해서 자바는 부모 클래스로의 타입 변환을 허용한다.
→ 이를 통해서 객체를 부품화 할 수 있다.
자동형변환이라고도 하는 자동타입변환은 프로그램 실행 도중 자동적으로 타입 변환이 일어나는 것을 말한다!
부모클래스 변수 = 자식클래스타입
: 어차피 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급! 자동형변환이 일어난다!
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)이란 부모 타입을 자식 타입으로 변환하는 것이다! 부모 타입이 자식 타입의 필드와 메소드를 사용할 수 있게 해주는 것. 우리 조원들과 했던 이야기가 ㅋㅋㅋㅋㅋ 자식이 부모에게 '돈 줘!' 하는 것은 괜찮지만(자동형변환) 부모가 자식에게 하는 건 좀... 하지만 칼을 든 다면? 부모도 자식에게 '돈 줘!' 할 수 있는 것이다. 이것이... 강제 타입 변환... 비유가 좀 그렇지만...
그렇다면 강제 형변환을 사용하는 이유는 뭘까? 자동 형변환은 자식이 부모 타입에 선언된 필드와 메소드를 사용할 수 있게 해준다. 하지만! 부모 타입에 선언된 필드와 메소드만 사용이 가능하다는단점이 있다. 강제 형변환은!
강제 타입 변환은 자식 타입에 선언된 필드와 메소드를 사용해야 할 때! 용이하다.
전제 조건: 자식 타입이 부모 타입으로 자동 변환한 후, 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.
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;
이 부분은 강제 형변환이다. 이를 통해서 자식 필드와 메소드에 접근이 가능해진 것이다!
강제 타입 변환은!! 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하다!!! 공부하다보니... 전 이 부분을 완전히 까먹고 있었더군요... 어쩐지 그래서 프로젝트할 때... 자꾸 뭐가 안 되더라... ㅠㅠ 그래서 강조합니다.
Parent parent = new Parent();
Child child = (Child) parent; // 강제 형변환 불가!
이렇게 냅다... 형변환을 한다고 되는 것이 아니라는 것...
그렇다면 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인을 해야겠지요? (사실... 이 부분 약간... 실제 개발에서 어떻게 쓰이는지 잘 모르겠다. 완전히 이해를 못했는데... 복습하면서 이해가 되길...)
암튼, 어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해서는
instanceof 연산자
를 사용하면 된다.
boolean result = 좌항(객체) instanceof 우항(타입)
: 좌항의 객체가 우항의 인스턴스이면(= 우항의 타입으로 객체가 생성되었다면) true, 아니라면 false를 산출한다.
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