Java - 다형성

kojam9041·2022년 3월 20일
0

KH정보교육원 - JAVA

목록 보기
11/12

다형성

 * 객체지향 프로그래밍(OOP:Object Oriented Programming)
 * 1. 캡슐화 : 정보은닉(필드에는 private, 간접접근으로 getter/setter메소드)
 * 2. 상속 : 공통적인 내용물을 추출하여, 부모클래스로써 정의하고, 이를 자식클래스에서 가져다 쓰는 기능
 * 3. 다형성 : 상속된 관계에서 객체간의 형(Class)변환(UpCasting, DownCasting)
 * 즉, 기본자료형과 마찬가지로, 객체간에도 형변환을 할 수 있다는 것을 의미함.
 * 여기서, 다형성에서도 기본자료형과 마찬가지로 값처리 규칙을 적용함.
 *
 * [형변환에서의 값처리규칙]
 * 1. 대입연산자를 기준으로 좌항, 우항의 자료형이 같아야한다.
 * 2. 같은 자료형끼리만 연산이 가능하다.
 * 3. 연산결과도 같은 자료형이어야 한다.
 *
 * 클래스 간에 형변환이 가능함.(단, 상속 관계일 경우에만 가능.)
 * 업캐스팅(Up-Casting) : 자식타입이 부모타입으로 변경되는 자동형변환을 일컬음.
 * 						 형변환 연산자를 생략 가능함.
 * 다운캐스팅(Down-Casting) : 부모타입이 자식타입으로 변경되는 강제형변환을 일컬음.
 * 							 형변환 연산자를 생략 불가함.

1. 부모클래스
Parent클래스
package com.kh.chap01_poly.part01_basic.model.vo;

public class Parent {
//	필드부
	private int x;
	private int y;
//	생성자부
	public Parent() {
		/*super()*/ 
//		super()은 생략해도 JVM이 알아서 만들어줌.
	}
	public Parent(int x, int y) {
		super();
//		모든 객체는 Object의 상속을 받음.
		this.x = x;
		this.y = y;
	}
//	메소드부
//	getter, setter
	public int getX() { return x; }
	public void setX(int x) { this.x = x; }
	public int getY() { return y; }
	public void setY(int y) { this.y = y; }  
	@Override
	public String toString() {
//		보통 Object의 toString()메소드는 주소값을 불러오는 메소드이나,
//		현재는 return문을 통해, 다음과 같은 출력문을 출력하도록 
//		나의 입맛에 맞게 오버라이딩 된 상태임.
		return "Parent [x=" + x + ", y=" + y + "]";
	}
	public void printParent() {
		System.out.println("나는 부모다.");
	}
//	동적바인딩    
	public void print() {
		System.out.println("나는 부모다.");
	}
}

2. 자식클래스
Child1 클래스
package com.kh.chap01_poly.part01_basic.model.vo;
//	Parent -----> Child1
public class Child1 extends Parent{
//	필드부
	/*
	private int x;
	private int y;
	 */
	private int z;

//	생성자부
	public Child1() {
		super();
	}
	public Child1(int x, int y,int z) {
		super(x, y);
//		나대신 부모클래스 매개변수생성자의 값을 초기화해달라.
		this.z = z;
	}
//	메소드부
//	getter, setter
	public int getZ() { return z; }
	public void setZ(int z) { this.z = z; }
    
	@Override
	public String toString() {
		return "Child1 [x="+super.getX()+", y="+super.getY()
					  +"z=" + z + "]";
	}
	public void printChild1() {
		System.out.println("나는 자식1이다.");
	}
//	동적바인딩
	public void print() {
		System.out.println("나는 자식1이다.");
	}
}

Child2 클래스
package com.kh.chap01_poly.part01_basic.model.vo;
//	Parent-------->Child1
//			 └---->Child2
public class Child2 extends Parent {
//	필드부
	private int n;


//	생성자부
	public Child2() {
		super();
	}
	public Child2(int x, int y, int n) {
		super(x,y);
		this.n = n;
	}
	
//	메소드부
	public int getN() { return n; }
	public void setN(int n) { this.n = n; }
	
	@Override
	public String toString() {
		return "Child2 [x="+super.getX()+"y="+super.getY()+"n=" + n + "]";
	}
	public void printChild2() {
		System.out.println("나는 자식2이다.");
	}
//	동적바인딩
	public void print() {
		System.out.println("나는 자식2이다.");
	}
}

PolyRun클래스
package com.kh.chap01_poly.part01_basic.run;
import com.kh.chap01_poly.part01_basic.model.vo.Child1;
import com.kh.chap01_poly.part01_basic.model.vo.Child2;
import com.kh.chap01_poly.part01_basic.model.vo.Parent;

public class PolyRun {

	public static void main(String[] args) {
		System.out.println("1. 부모 타입의 참조형변수로 부모 객체를 다루는 경우");
		Parent p1 = new Parent();
//		좌항의 Parent와 우항의 Parent()로 자료형이 같은것을 볼 수 있음.

		p1.printParent(); // "나는 부모다"
//		p1이라는 참조형 변수로, Parent클래스에 있는 메소드 접근 가능.
		/*p1.printChild();*/ 
//		아무리 상속관계여도, 부모에서 자식에 있는 메소드는 호출할 수 없음.
//		참고 : 자식에서 부모에 있는 메소드는 호출이 가능했었음.

		System.out.println("2. 자식 타입의 참조형변수로 부모 객체를 다루는 경우");
		Child1 c1 = new Child1();
//		좌항의 Child1과 우항의 Child1()로 자료형이 같은것을 볼 수 있음.

		c1.printChild1(); // "나는 자식1이다."
		c1.printParent(); // "나는 부모다"
//		상속으로 인해, Parent클래스와 Child1클래스에 있는 메소드 접근 가능.

		System.out.println("3. 부모 타입의 참조형변수로 자식 객체를 다루는 경우");
		Parent p2 = /*(Parent)*/ new Child1();
//		좌항은 Parent, 우항은 Child1으로 자료형이 다름.

//		부모클래스		      자식클래스
//		(부모)자식1  <--------  자식
//			 		  형변환
//		=> Child1타입의 객체가 new구문에 의해 생성되고 나서, 
//		=> Parent형으로 자동형변환이 일어나서 주소값에 담긴 것으로 유추 가능.
//		=> "상속"이라는 구조가 전제로, 클래스 간에 형변환이 가능하다.
		
		p2.printParent();
//		부모클래스  --------> 부모클래스
//		(부모)자식1           부모메소드
//		부모타입의 참조형변수로 부모클래스의 부모메소드 접근 가능
		((Child1)p2).printChild1(); 
//		부모클래스  --------> 자식클래스
//		(자식1)자식1          자식메소드
//      => 부모타입의 참조형변수에서 자식클래스의 자식메소드는 다룰 수 없기 때문에,
//      => 자식타입으로 다운캐스팅을 한 후, 자식클래스의 자식메소드에 접근함.
//		=> 점(.)이 괄호보다 우선되는 연산자이기 떄문에, 
//		=> 괄호를 두번 쓰지 않으면 호출을 먼저 수행해서 오류가 남.
		
//		Parent <-------- Child1 : 자동 형변환.
//		Parent --------> Child1 : 강제 형변환.
		
//		다형성을 쓰는 이유 : 객체배열을 편리하게 쓰기 위해서
//		Child1 객체 2개, Child2 객체 2개가 필요한 상황이라고 가정	
//		객체 배열을 이용하여, Child1은 child1끼리, Child2는 Child2끼리 묶음.
		Child1[] arr1 = new Child1[2];
		arr1[0] = new Child1(1,2,4);
		arr1[1] = new Child1(2,1,5); 
		
		Child2[] arr2 = new Child2[2];
		arr2[0] = new Child2(5,7,2);
		arr2[1] = new Child2(2,3,5);
		
//		배열의 특징 : 한가지 자료형의 여러값들을 묶어서 보관 가능.
//		=> 원래 배열은 한가지 자료형만 담을수 있기 때문에, 
//		=> Child1, Child2에 해당되는 배열을 각각 한개씩 만들어주어야 했다면,
//		=> 이제는 다형성을 적용하여, 부모 타입의 객체배열 하나로 다양한 자식 객체들을 한번에 담아서 보관하겠다.
		
		System.out.println("===== 다형성을 접목한 객체배열=====");
//		부모타입의 객체 배열을 1개 생성
		Parent[] arr = new Parent[4];
//		=> 원래는 Parent타입의 객체만 4개 들어갈 수 있음.
//		=> 여기서, '자식이 부모로 자동형변환이 된다.'라는 점을 이용하여, 부모객체만 넣을 수 있는 배열에 자식객체를 넣는다면?
		arr[0] = /*(Parent)*/new Child1(1,2,4);
		arr[1] = /*(Parent)*/new Child1(2,1,5);
		arr[2] = /*(Parent)*/new Child2(5,7,2);
		arr[3] = /*(Parent)*/new Child2(2,3,5);
//		Child1, Child2 객체가 Parent 객체로 자동형변환이 되어 객체배열에 담길 수 있게 됨.
//		다형성이라는 개념을 통해서, 값을 한번에 묶어서 보관이 가능한 상태가 됨.
		
		arr[0].printParent();
		arr[1].printParent();
		arr[2].printParent();
		arr[3].printParent();
//		Parent객체로 자동형변환되어 printParent메소드를 사용할 수 있음.
		
		
		((Child1)arr[0]).printChild1();
		((Child1)arr[1]).printChild1();
		((Child2)arr[2]).printChild2();
		((Child2)arr[3]).printChild2();
		
		((Child1)arr[0]).printParent();
		((Child1)arr[1]).printParent();
		((Child2)arr[2]).printParent();
		((Child2)arr[3]).printParent();
//		Child1, Child2로 강제형변환되어 printChild1, printChild2메소드를 각각 사용할 수 있으며, 
//		상속으로 각각 printParent메소드를 사용할 수 있음.
//		=> 부모타입의 배열에 담을때에는 자동형변환으로 가능하지만, 
//		=> 꺼내서 처리를 하려면 강제형변환으로 원상복구하고 사용해야 함.
		
//		((Child2)arr[0]).printChild2();
//		[오류메세지]
//		java.lang.ClassCastException
//		클래스 간의 형변환이 잘못 되었을 경우 발생하는 에러
//		arr[0]은 Child1인데 Child2로 형변환하여 생기는 에러임.
//		즉, 자식간에는 상속관계가 아니기 때문에 다형성이 발생하지 않음.
		
//		배열을 사용하는 이유는 반복문을 사용하기 위해서임!!!!!
		System.out.println();
		System.out.println("====== 반복문을 이용해보기 =====");
		for(int i=0 ; i<arr.length; i++) {
//			((child1) 또는 (Child2)arr[i]).printChild1() 또는 printChild2(); 
//			배열의 인덱스별로, 실제로 참조하고 있는 자식클래스로 다운캐스팅을 한 다음에, 메소드를 호출해야 함.
			
//			instanceof 연산자
//			=> 현재 객체가 실제로 어떤 자식클래스의 주소를 참조하고 있는지 확인하고자 할때 사용.
//			[표현법]
//			객체명(객체배열명) instanceof 검사하고 싶은 클래스명
//			맞다면 true, 아니면 false
//			Quiz.
//			1. 부모객체 instanceof 부모클래스 == true
//			2. 자식객체 instanceof 부모클래스 == true	(부모님꺼는 내꺼니깐)
//			3. 부모객체 instanceof 자식클래스 == false	(내꺼만 내꺼니깐)
//			4. 자식객체 instanceof 자식클래스 == true
			
//			방법1
			/*
 			if(arr[i] instanceof Child1) { // arr[i]가 원래는 Child1 형태라면
				((Child1)arr[i]).printChild1();
			}else { // arr[i]가 원래는 Child2 형태라면
				((Child2)arr[i]).printChild2();
			}
			*/
            
//			방법2
//			부모클래스에 print메소드를 만들어서
//			각각의 자식 클래스에도, 같은 이름으로 내용만 달리하여, print메소드를 오버라이딩하여 작성함.
//			동적바인딩에 의해, 같은 메소드명이면 부모클래스보다 자식클래스의 메소드를 먼저 호출함.
//			*동적바인딩 : 실질적으로 참조하고 있는 자식클래스의 오버라이딩된 메소드를 찾아가서 알아서 실행함.
			arr[i].print();
//			여기에서는 조건을 이용한 instanof연산자를 사용할 필요도 없고, 강제형변환을 해줄 필요도 없음.
//			(오버라이딩을 이용한 것이기 때문에, 굳이 다형성의 형변환을 사용할 필요가 없음)
 		}	
	}
}

다형성 적용 전


1. ElectronicController1 클래스
package com.kh.chap01_poly.part02_electronic.controller;

import com.kh.chap01_poly.part02_electronic.model.vo.Desktop;
import com.kh.chap01_poly.part02_electronic.model.vo.NoteBook;
import com.kh.chap01_poly.part02_electronic.model.vo.Tablet;

// 다형성을 적용하기 전
public class ElectronicController1 {
//	[필드부]
//	용산 전자상가에 새로 차린 가게
//	필드를 클래스, 객체명으로 지어줌.
// 	부모클래스
	/*private Electronic electronic : brand, name, price */
// 	자식클래스
	private Desktop desk; 	// CPU(상수필드) = "intel", graphic 	 
	private NoteBook note;	// usbPort
	private Tablet tab;		// penFlag
	
//	[생성자부]
	
	
//	[메소드부]
//	데스크탑 재고가 들어올 경우,
//	오버로딩을 이용하여 메소드 이름은 같게, 매개변수는 다르게.
//	다만, 매개변수가 자료형을 클래스로, 매개변수명을 객체로 구성한 것일 뿐
	
//	형태는 setter메소드와 같음.
//	데스크탑을 생성해주는 메소드
	public void insert(Desktop d) {
//		Desktop d = new Desktop("삼성","데스크탑",1200000, "Geforce1070");
//		desk = new Desktop("삼성","데스크탑",1200000,"Geforce1070");
		desk = d;
	}
//	노트북을 생성해주는 메소드
	public void insert(NoteBook n) {
		note = n;
	}
//	태블랫을 생성해주는 메소드
	public void insert(Tablet t) {
		tab = t;
	}
//	형태는 getter메소드와 같음.
//	오버로딩이 불가(리턴형은 매개변수가 없기 때문에)하기 때문에,
//	각각의 메소드명을 아래와 같이 지었음.
	public Desktop selectDesktop() {
		return desk;
	}
	public NoteBook selectNoteBook() {
		return note;
	}
	public Tablet selectTablet() {
		return tab;
	}
		
}

1-1. ElectronicRun 클래스
package com.kh.chap01_poly.part02_electronic.run;

import com.kh.chap01_poly.part02_electronic.controller.ElectronicController1;
import com.kh.chap01_poly.part02_electronic.model.vo.Desktop;
import com.kh.chap01_poly.part02_electronic.model.vo.Electronic;
import com.kh.chap01_poly.part02_electronic.model.vo.NoteBook;
import com.kh.chap01_poly.part02_electronic.model.vo.Tablet;

public class ElectronicRun {

	public static void main(String[] args) {
//		1. 다형성을 적용하기 전(ElectronicController1)
//		납품업체
		ElectronicController1 ec = new ElectronicController1();
		ec.insert(new Desktop("삼성","데스크탑",1200000,"GTX 1070"));
//				 	==> Desktop d = new Desktop("삼성","데스크탑",1200000,"GTX 1070");
//					==> ec.insert(d);
		ec.insert(new NoteBook("LG","그램",2000000,3));
		ec.insert(new Tablet("애플","아이패드",500000,false));
//		=>각각 데스크탑, 노트북, 태블릿 한대씩을 납품받음.
		
//		손님
//		가게에 있는 제품들을 조회
		/*
		Desktop d = ec.selectDesktop();
		NoteBook n = ec.selectNoteBook();
		Tablet t = ec.selectTablet();
		
		System.out.println(d);
		System.out.println(n);
		System.out.println(t);
//		각각의 물건을 각각의 객체로 정의하여 이를 출력함.
//		=>매번 타입이 다르기때문에 한번에 배송을 받을수가 없음(부분배송)
		*/
        
		System.out.println(ec.selectDesktop());
		System.out.println(ec.selectNoteBook());
		System.out.println(ec.selectTablet());
//		객체로 담지 않고 그냥 출력해도 됨.
}

다형성 적용 후


2. ElectronicController2 클래스
package com.kh.chap01_poly.part02_electronic.controller;

import com.kh.chap01_poly.part02_electronic.model.vo.Electronic;

//	다형성을 적용한 후
public class ElectronicController2 {
//	[필드부]
//	필드를 객체배열, 객체명으로 지어줌
//	여기서, 객체배열의 할당까지 이루어짐.
	private Electronic[] elec = new Electronic[3];
	
//	[생성자부]
	
	
//	[메소드부]
//	물건이 납품될때마다, 창고에 물건을 하나하나 차곡차곡 채워넣겠다.
//	매개변수에 객체를, index번째의 객체배열에 대입함. 
//	이때, Electronic의 매개변수생성자를 갖다 쓴 것임.
	public void insert(Electronic any, int index) {
			elec[index] = any;
	}
//	고객이 원하는 물건을 하나씩 꺼내주기(창고로부터) 
	public Electronic select(int index) {
		return elec[index];
	}
//	고객이 원하는 물건이 재고의 전부일때 => 창고째로 넘겨주기
	public Electronic[] select() {
		return elec;
	}
	
	/*
	 * 메소드
	 * 입력값(매개변수)이 있든 없든간에, 수행할 내용을 실행함.
	 * => 반환값이 있으면 내보내고, 없으면 메소드 내에서 연산만 함.
	 * 
	 * 입력값 : boolean,byte, short, int, long, float, double, char, String, 
	 * 			객체, 배열, 객체배열,... 
	 * 			ex) main메소드(String[] args) =>String[]배열의 args라는 객체명
	 * 출력값 : void, boolean,byte, short, int, long, float, double, char, String, 
	 * 			객체, 배열, 객체배열,...
	 */
}

2.2 ElectronicRun 클래스
package com.kh.chap01_poly.part02_electronic.run;

import com.kh.chap01_poly.part02_electronic.controller.ElectronicController2;
import com.kh.chap01_poly.part02_electronic.model.vo.Desktop;
import com.kh.chap01_poly.part02_electronic.model.vo.Electronic;
import com.kh.chap01_poly.part02_electronic.model.vo.NoteBook;
import com.kh.chap01_poly.part02_electronic.model.vo.Tablet;

public class ElectronicRun {

	public static void main(String[] args) {
//		2. 다형성을 적용한 후(ElectronicController2)
		ElectronicController2 ec2 = new ElectronicController2();
//		이 시점에서 객체생성으로, 
//		private Electronic[] elec = new Electronic[3];로 필드변수를 사용할 수 있음.
		
//		납품업체
//		Electronic any = (Electronic)new Desktop("삼성","데스크탑",1000000,"Gtx1070", index)
//		...
		ec2.insert(/*(Electronic)*/new Desktop("삼성","데스크탑",1000000,"Gtx1070"),0);
		ec2.insert(/*(Electronic)*/new NoteBook("LG","그렘",2000000,3), 1);
		ec2.insert(/*(Electronic)*/new Tablet("애플","아이패드",500000,false), 2);
// 		elec[0]= /*(Electronic)*/new Desktop("삼성","데스크탑",1000000,"Gtx1070")
//		elec[1]= /*(Electronic)*/new NoteBook("LG","그렘",2000000,3)
//		elec[2]= /*(Electronic)*/new Tablet("애플","아이패드",500000,false)
//		창고가 가득참
		
//		고객
		System.out.println("===== 다형성 적용 후 =====");
//		물건을 창고에서 하나만 뽑아서 보여줬을 때
		System.out.println(ec2.select(0)/*.toString*/);
		
//		창고째로 다 샀을때
		Electronic[] elec = ec2.select();
		System.out.println("elec : "+ elec); // 주소값이 출력됨.
		System.out.println();
		for(int i=0; i<elec.length;i++) {
			System.out.println(elec[i]);
		}
		/*
		 * 다형성을 사용하는 이유
		 * 1. 부모타입의 객체배열로, 다양한 자식들을 묶어서 한번에 받아줄 수 있음.
		 * => 부모타입 하나만으로 다양한 자식 객체들을 다룰 수 있음.
		 * 2. 메소드의 매개변수나 반환형에 다형성을 적용하면, 메소드의 개수가 확 줄어듬(코드가 간결해짐)
		 */
		
	}
}

0개의 댓글