다형성

  • 부모 클래스 타입 참조변수 하나로 상속 관계에 있는 여러 타입의 자식 객체를 참조할 수 있는 기술

  • 다형성을 이용한 객체 배열
    부모 타입 참조 자료형의 변수를 하나의 묶음으로 다루는 것


  • 업 캐스팅
    부모타입의 참조형 변수가 모든 자식 타입 객체의 주소를 참조할 수 있음 (자식만의 고유한 필드, 메소드를 사용할 수 없다.)
  • 다운 캐스팅
    부모 타입 참조 변수가 자식 객체를 참조하는 업캐스팅 상태에서만 진행할 수 있는 기술로, 부모 타입을 자식 타입으로 "강제 형변환"해서 자식 객체의 본래 필드, 메소드를 사용 가능하게 함
    *효율적인 다운 캐스팅 방법: 얕은 복사

  • instanceof 연산자
    객체의 자료형을 검사하는 연산자
    -> 참조하는 객체가 특정 자료형이거나 부모쪽 상속관계인지를 확인

  • 바인딩: 실제 실행할 메소드 코드와 호출할 코드를 연결시키는 것

    -정적 바인딩: 프로그램 실행 전 컴파일 단계에서 메소드와 메소드 호출부 연결
    -동적 바인딩: 컴파일 시 정적 바인딩된 메소드를 실행할 당시의 객체타입 기준으로 바인딩 되는 것

    *동적바인딩 장점: 업 캐스팅 상태의 참조변수를 별도의 다운 캐스팅 없이 자식의 오버라이딩된 메소드를 수행할 수 있다.

Car

package edu.kh.poly.ex1.model.vo;

public class Car {
	
	private String engine; //엔진
	private String fuel; //연료
	private int wheel; //바퀴 개수
	
	public Car() {//기본 생성자
		super(); //부모 생성자
	}

	//매개변수 생성자 자동완성
	//alt shiht S -> O -> enter
	public Car(String engine, String fuel, int wheel) {
		super();
		this.engine = engine;
		this.fuel = fuel;
		this.wheel = wheel;
	}

	//getter setter 자동완성
	//alt shift S -> r -> tab -> space -> shift tab -> enter
	public String getEngine() {
		return engine;
	}

	public void setEngine(String engine) {
		this.engine = engine;
	}

	public String getFuel() {
		return fuel;
	}

	public void setFuel(String fuel) {
		this.fuel = fuel;
	}

	public int getWheel() {
		return wheel;
	}

	public void setWheel(int wheel) {
		this.wheel = wheel;
	} 
	
	//Object.toString() 오버라이딩
	@Override
	public String toString() {
		return engine + " / " + fuel + " / " + wheel;
	}
	

}

Tesla

package edu.kh.poly.ex1.model.vo;

public class Tesla extends Car{ //전기차
	
	private int batteryCapacity; //배터리 용량
	
	public Tesla() {
		super(); //부모 생성자 Car
	}

	//매개변수 생성자
	//alt shift S -> O -> 위쪽에 메뉴 바꾼 후 enter
	public Tesla(String engine, String fuel, int wheel, int batteryCapacity) {
		super(engine, fuel, wheel);
		this.batteryCapacity = batteryCapacity;
	}

	//getter setter
	public int getBatteryCapacity() {
		return batteryCapacity;
	}

	public void setBatteryCapacity(int batteryCapacity) {
		this.batteryCapacity = batteryCapacity;
	}

	//Car.toString 오버라이딩
	@Override
	public String toString () {
		return super.toString() + " / " +batteryCapacity;
	}
	

}

Spark

package edu.kh.poly.ex1.model.vo;

public class Spark extends Car{ //경차
	
	private double discountOffer; //경차 할인 혜택
	
	//기본 생성자
	public Spark() {
		super(); //생략 시 컴파일러가 자동 추가
	}

	//매개변수 생성자(상속버전)
	//alt shift S -> O -> 아래방향키 -> enter
	public Spark(String engine, String fuel, int wheel, double discountOffer) {
		super(engine, fuel, wheel);
		this.discountOffer = discountOffer;
	}

	//getter setter
	public double getDiscountOffer() {
		return discountOffer;
	}

	public void setDiscountOffer(double discountOffer) {
		this.discountOffer = discountOffer;
	}
	
	
	@Override
	public String toString() {
		return super.toString() + " / " + discountOffer;

	}
	

}

PolyService

package edu.kh.poly.ex1.model.sevice;

import edu.kh.poly.ex1.model.vo.Car;
import edu.kh.poly.ex1.model.vo.Spark;
import edu.kh.poly.ex1.model.vo.Tesla;

public class PolyService {
	
	public void ex1() {
		
		//다형성 확인 예제
		
		//Car 객체 생성
		Car car = new Car();
		//부모 타입 참조변수 Car로 부모 객체 참조
		
		//Tesla 객체 생성
		Tesla tesla = new Tesla();
		//자식 타입 참조변수 Tesla로 자식 객체 참조
		
		//******다형성(업캐스팅)******
		Car car2 = new Tesla(); //-> 오류 발생 안 함
		//Tesla 객체를 참조하는 변수의 타입이 Car(부모)이기 때문에
		//Tesla 객체가 Car(부모) 객체로 변화함
		
		Car car3 = new Spark();
		//부모타입 참조변수 =(참조) 자식 객체
		
		//******다형성(업캐스팅) 작성법******
		
		//1) 자식 객체가 부모 객체로 변했기 때문에
		//	 자식만의 고유한 필드, 메소드를 사용할 수 없다.
		
		//1-1) car(부모 = 부모)
		car.setEngine("v6 6기통 엔진");
		car.setFuel("휘발유");
		car.setWheel(4);
		// Car 메소드 모두 사용 가능
		
		//1-2) tesla (자식 = 자식)
		tesla.setEngine("전기모터");
		tesla.setFuel("전기");
		tesla.setWheel(4);
		tesla.setBatteryCapacity(1_000_000);
		// tesla 메소드 모두 사용 가능
		
		//1-3) car2 (부모 = 자식(Tesla))
		car2.setEngine("전기모터");
		car2.setFuel("전기");
		car2.setWheel(4);
		//car2.setBatteryCapacity(1_000_000);
		//The method setBatteryCapacity(int) is undefined for the type Car
		
		//1-4) car3 (부모 = 자식(Spark))
		car3.setEngine("경차엔진");
		car3.setFuel("휘발유");
		car3.setWheel(4);
		//car3.setDiscountOffer(0.5); 
		//The method setDiscountOffer(double) is undefined for the type Car
		
		//=============================================================
		
		//2) 다형성을 이용한 객체 배열
		
		// 객체 배열: 같은 객체 참조 자료형의 변수를 하나의 묶음으로 다루는 것
		
		// + 다형성 적용 -> 부모 타입 참조 자료형의 변수를 하나의 묶음으로 다루는 것
		
		Car[] arr = new Car[3]; //부모 타입 참조 변수 배열 선언 및 할당
								//각 배열 요소가 Car 타입 참조 변수
		
		arr[0] = car; //Car 주소 == Car 객체 
		//Car 참조 변수  
		
		arr[1] = car2; //Tesla 주소 == Tesla 객체
		//Car 참조변수
		
		arr[2] = car3; //Spark 주소 == Spark 객체
		//Car 참조변수
		
		//상속 + 다형성
		//상속 특징: 일련의 클래스들에 대한 공통적인 규약 정의
		//			-> Car 상속 클래스는 모두 getEngine()을 가지고 있다를 정의
		
		//다형성(업캐스팅): 부모 타입 참조 변수 arr[i]로 자식 객체를 참조할 수 있다.
		for (int i = 0; i<arr.length; i++) {
			System.out.println(i+ "번째 인덱스의 엔진: "+arr[i].getEngine());
		}

		
	}
	
	public void ex2() {
		
		//3) 다형성(업캐스팅)을 이용한 매개변수 사용법
		Tesla t = new Tesla("전기모터", "전기", 4, 1000000);
		Spark s = new Spark("경차 엔진", "휘발유", 4, 0.5);
		Car c = new Car("경유 엔진", "경유", 12);
		
		printCar(t);
		printCar(s);
		printCar(c);
		
		System.out.println("=================================");
		
		//4) 다형성을 이용한 반환형 사용법
		
//		Car arr[] = {new Car(), new Tesla(), new Spark()};
		
		Car[] arr = {createCar(1), createCar(2), createCar(3)};
					//반환형 Car			Car			Car
									//(Tesla)		(Spark)
		
		// arr[0];// Car
		// arr[1];// Tesla
		// arr[2];// Spark
		
		//instanceof 연산자: 객체의 자료형을 검사하는 연산자
		// -> 참조하는 객체가 특정 자료형이거나 부모쪽 상속 관계인지를 확인
		
		
		//arr[1]이 참조하는 객체가 Tesla이면 true,아니면 false를 반환
		System.out.println(arr[1] instanceof Tesla); //true
		
		//arr[1]이 참조하는 객체가 Spark이면 true,아니면 false를 반환
		System.out.println(arr[1] instanceof Spark); //false
		
		//arr[1]이 참조하는 객체가 Car이면 true,아니면 false를 반환
		System.out.println(arr[1] instanceof Car); //true
		//Car는 Tesla의 부모쪽 상속 관계이기 때문에 true
		
		System.out.println("==============================");
		
		for (int i = 0; i<arr.length; i++) {
			if(arr[i] instanceof Tesla) {
				System.out.println("Tesla 객체입니다.");
			} else if (arr[i] instanceof Spark) {
				System.out.println("Spark 객체입니다.");
			} else {
				System.out.println("Car 객체입니다.");
			}
		}
		//Car 조건문을 먼저 넣으면 전부 다 Car로 나옴
		
		
	}
	
	//전달받은 Car 또는 자식 객체의 엔진, 연료, 바퀴 개수를 출력하는 메소드
	//매개변수에 부모타입 참조변수를 작성하면 모든 자식의 객체를 전달 받을 수 있다.
	public void printCar(Car temp) { //부모타입 참조변수 =(대입) 자식 객체
		//매개변수에 작성된 참조형 변수에는 주소가 저장됨(얕은 복사)
		//메소드 내부 변수 + 매개변수 == 지역변수(Local Variable)
		
		System.out.println("엔진: " + temp.getEngine());
		System.out.println("연료: " + temp.getFuel());
		System.out.println("바퀴 개수: " + temp.getWheel());
		System.out.println();
		
		
	}
	
	//전달받은 매개변수에 따라서 Car 또는 자식 객체를 생성하고 
	//생성된 객체의 주소를 반환
	public Car createCar(int num) {
		
		Car result = null; //아무것도 참조하고 있지 않음
		
		switch(num) {
		
		case 1: result = new Car(); break;
		case 2: result = new Tesla(); break;
		case 3: result = new Spark(); break;
		
		}
		
		//반환형이 Car이지만
		//case가 2, 3이면 Car의 자식 객체의 주소가 반환된다.
		
		return result;
	}
	
	public void ex3() {
		
		//******다형성 중 다운캐스팅******
		
		// 다운캐스팅: 부모 타입 참조 변수가 자식 객체를 참조하는
		//			업캐스팅 상태에서만 진행할 수 있는 기술로
		//			부모 타입을 자식 타입으로 "강제 형변환"해서
		//			자식 객체의 본래 필드, 메소드를 사용 가능하게 한다.
		
		Car c1 = new Tesla("전기모터", "전기", 4, 50000);
		
		System.out.println(((Tesla)c1).getBatteryCapacity()); //50000	
		//주의!!! "." 연산자가 (Tesla)형변환 연산자보다 우선순위가 높음
		//-> 괄호로 묶어서 우선순위 재배정
		
		//<효율적인 다운캐스팅 방법>
		// -얕은 복사 이용
		Tesla t1 = (Tesla)c1;
		System.out.println(t1.getBatteryCapacity()); //50000
		
		
	}
	
	public void ex4() {
		
		//!!!!!!다운캐스팅 주의 사항!!!!!!
		
		Car c1 = new Tesla();
		
//		Spark s1 = (Spark) c1; //다운캐스팅
		//java.lang.ClassCastException (형변환 예외)
		//-> c1이 참조하는 객체는 Tesla인데 
		//	 Spark 참조변수로 Tesla를 참조하려 해서 문제 발생
		
		//해결방법: instanceof와 같이 사용
		if (c1 instanceof Spark) {
			Spark s1 = (Spark) c1; //다운 캐스팅
			System.out.println("성공");
		}else {
			System.out.println("실패(Spark 타입 아님)");
		}

	}
	
	public void ex5() {
		
		// 바인딩(Binding)
		// - 실제 실행할 메소드 코드와 호출하는 코드를 연결시키는 것
		
		Car c1 = new Car("경유 엔진", "경유", 8);
		
		System.out.println(c1.getEngine());
		//Car 객체에 있는 getEngine() 메소드 호출 == 바인딩
		//String edu.kh.poly.ex1.model.vo.Car.getEngine()
		
		//프로그램 "실행 전"
		//-컴파일러는 getEngine() 메소드가 Car에 있는 걸로 인식해서
		// c1.getEngine() 호출 코드와 
		// String edu.kh.poly.ex1.model.vo.Car.getEngine() 메소드 코드를 연결해줌
		//-> [정적 바인딩]
		// 컴파일 단계에서 메소드와 메소드 호출부를 연결
		
		System.out.println(c1.toString());
		//String edu.kh.poly.ex1.model.vo.Car.toString()
		//Car 참조변수 c1을 이용해서
		//Car 객체에 있는 오버라이딩된 toString() 메소드 호출
		
		//**다형성 적용 시 바인딩**
		Car c2 = new Spark("경차엔진", "휘발유", 4, 0.5);
		//업캐스팅 -> 부모 부분만 참조 가능한 상태
		
		System.out.println(c2.toString());
		//String edu.kh.poly.ex1.model.vo.Car.toString()
		//참조변수 c2가 Car 타입이므로 
		//toString() 도 Car의 toString()을 호출 - 정적 바인딩
		//하지만 실행해보면 자식(Spark)의 toString()이 호출되는 것을 확인 가능
		//-> 컴파일 시에는 부모(Car)와 바인딩 ==[정적 바인딩]
		//-> 실행 시에는 자식(Spark)에 오버라이딩된 메소드와 바인딩 == [동적 바인딩]
		//컴파일 시 정적 바인딩된 메소드를 실행할 당시의 객체 타입 기준으로 바인딩되는 것
		
		
		System.out.println("==============================");
		
		// **동적 바인딩 활용 방법**
		Car[] arr = {
						new Car("경유 엔진", "경유", 12),
						new Tesla("전기 모터", "전기", 4, 50000),
						new Spark("경차 엔진", "무연", 4, 0.3)
		};
		
		//arr 배열 요소가 참조하는 모든 객체의 필드 값 출력
		for (int i =0 ; i<arr.length; i++) {
			System.out.println(i + "번째 요소" +arr[i].toString());
			//실행 전: String edu.kh.poly.ex1.model.vo.Car.toString() - 정적 바인딩
			//실행 후: 각 객체의 오버라이딩된 toString() 호출됨 - 동적 바인딩
		}
		
		//**동적 바인딩의 장점**
		//-업캐스팅 상태의 참조변수를 별도의 다운캐스팅 없이
		// 자식의 오버라이딩된 메소드를 수행할 수 있다.
		
		
	}
	

}

PolyRun

package edu.kh.poly.ex1.run;

import edu.kh.poly.ex1.model.sevice.PolyService;

public class PolyRun {

	public static void main(String[] args) {

		PolyService service = new PolyService();
		
//		service.ex1();
//		service.ex2();
//		service.ex3();
//		service.ex4();
		service.ex5();
	}

}
profile
개발 일지

0개의 댓글