본문 바로가기

웹개발 수업/JAVA

[Day +15]상속, 다형성 / 오버라이딩, toString, equals, hashcode, instanceof, 캐스팅, 바인딩

210713 화

 

상속

*오버라이딩(14일에 이어서)

1) toString 오버라이딩

2) equals 오버라이딩
필드값 비교 가능 

3) hashcode 오버라이딩

package com.kh.chap02_override.model.vo;

public class Book {
	
	private String title;
	private String author;
	private int price;
	
	
	public Book() {}
	
	public Book(String title, String author, int price) {
		super();
		this.title = title;
		this.author = author;
		this.price = price;
	}

	public String getTitle() {
		return title;
	}

	public String getAuthor() {
		return author;
	}

	public int getPrice() {
		return price;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public void setPrice(int price) {
		this.price = price;
	}
	
	public String information() {
		return "title : " + title + ", author : " + author + 
				", price : " + price;
	}

	
	//1. toString 오버라이딩
	/*@Override
	public String toString() {
		return "title : " + title + ", author : " + author + 
				", price : " + price;
	}*/
	
	@Override
	public String toString() {
		return "Book [title=" + title + ", author=" 
        			+ author + ", price=" + price + "]";
	}
	//toString을 활용하면 bk1만 입력해도 출력가능
	


	//2. equals 오버라이딩
	//필드값 비교 가능 
	/*
	@Override
	public boolean equals(Object obj) { //이부분 다시 인강듣기**** 2번째 첫 시간
		//this (현 객체) vs obj (비교할 대상 객체)
		//Book 			 Objcet
		
		Book other = (Book)obj; //Object 타입의 비교 대상 객체를 Book으로 형변환
		
		if(title.equals(other.getTitle()) 
		   && author.equals(other.getAuthor())
		   && price == other.getPrice()) {
			return true;
		}
		return false;
			
	}*/
	// => 간단하게 구현 ver이었음

	
	
	@Override //뭔 소리져.. 
	public boolean equals(Object obj) {
		//1. 주소값 비교 - 주소가 같으면 당연히 같은 객체 참조이므로 true 반환
		if (this == obj)
			return true;
		//2. 주소값이 null이라면 참조하는게 없으므로 비교 가치가 없으므로 false 반환
		if (obj == null)
			return false;
		//3. 클래스 비교 - 참조 자료형이 같지 않으면 비교 가치가 없어서 false를 반환
		if (getClass() != obj.getClass())
			return false;
		//여기까지 코드 도달 시 1차 합격
		//각 필드 값들이 동일한지 비교하기 위해 Object => Book 형변환
		Book other = (Book) obj;
		
		//author 필드 값 비교 (String 타입)
		if (author == null) { // this.author가 null이고
			if (other.author != null) //other.author가 null이 아니면
				return false; 		  //필드 값이 다르므로 false리턴
		} else if (!author.equals(other.author)) //String 클래스의 equals를 비교해서
			return false;						 //필드 값이 다르므로 false 리턴
		
		//기본 자료형이므로 값 비교 후 같지 않으면 false 리턴
		if (price != other.price)
			return false;
		// title 필드 값 비교(String 타입) - author와 같은 구조
		if (title == null) {
			if (other.title != null)
				return false;
		} else if (!title.equals(other.title))
			return false;
		
		//위의 조건들을 모두 만족하지 못해서 코드가 여기까지 도달했다는 것은
		//모든 필드 값이 일치한다는 의미이므로 true리턴
		return true;
	}
	// 어려운 ver

	
	@Override
	public int hashCode() {
		//해쉬코드 값이 충돌(중첩)현상이 생기지 않도록 소수로 계산(충돌을 줄임)
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + price;
		result = prime * result + ((title == null) ? 0 : title.hashCode());
		return result;
	}
}

 

package com.kh.chap02_override.run;

import com.kh.chap02_override.model.vo.Book;

public class Run {

	public static void main(String[] args) {
		
		Book bk1 = new Book("수학의 정석", "나수학", 100);
		Book bk2 = new Book("칭찬은 고래도 춤추게 한다", "고래", 300);
		
		System.out.println(bk1.information());
		System.out.println(bk2.information());
		
		/*title : 수학의 정석, author : 나수학, price : 100
		title : 칭찬은 고래도 춤추게 한다, author : 고래, price : 300*/
		
		//모든 클래스는 Object 클래스의 후손이다.
		//즉, 최상위 클래스는 항상 Object
		//Object 클래스에 있는 메소드를 오버라이딩해서 사용할 수 있음

		//1. toString
		//오버라이딩 전 : Object 클래스의 toString 실행
		//=> 패키지명을 포함한 클래스 풀네임 + @ + 객체의 해쉬코드 16진수 값
		//오버라이딩 후 : Book 클래스의 toString 실행
		//=> 해당 객체가 가지고 있는 멤버 필드 값에 대한 정보
		System.out.println(bk1.toString());
		System.out.println(bk2.toString());
		
		//앞으로 information 메소드 대신 to String 메소드를 오버라이딩 해서 사용
		//출력문에서 어떤 레퍼런스를 출력하고자 할 때 JVM이 자동으로 레퍼런스.toString()을 호출
		//따라서 레퍼런스 변수만 작성해도 알아서 toString이 호출 됨
		System.out.println(bk1);
		System.out.println(bk2);
		
		
		/*
		 * Book [title=수학의 정석, author=나수학, price=100]
		Book [title=칭찬은 고래도 춤추게 한다, author=고래, price=300]
		Book [title=수학의 정석, author=나수학, price=100]
		Book [title=칭찬은 고래도 춤추게 한다, author=고래, price=300]
		 */
		
		
		
		//2. equals
		Book bk3 = new Book("수학의 정석", "나수학", 100);
		//bk1이 가지고 있는 필드 값과 완전하게 같은 필드 값을 가진 bk3 생성
		
		System.out.println("bk1과 bk3가 같은 책입니까?" + (bk1 == bk3));
		//딱 봐도 false가 나올 것임을 예상해야 한다. 
		//why?new 생성자로 다른 객체를 만들어줬기 때문에
		
		System.out.println("bk1과 bk3가 같은 책입니까?" + (bk1.equals(bk3)));
		//equals는 object가 가지고 있는 메소드이다. 
		//equals는 값 비교이다 
		//오버라이딩 전 : Object 클래스의 equals 호출 시 주소값을 비교하므로
		//필드 값이 모두 같아도 false를 리턴함
		//오버라이딩 후 : 실제 멤버 값을 비교하여 멤버 값이 같을 경우 true를 리턴함
		
		/*
		 * bk1과 bk3가 같은 책입니까?false
		   bk1과 bk3가 같은 책입니까?true
		 */
		
		
		//3. hashCode
		System.out.println(bk1.hashCode());
		System.out.println(bk2.hashCode());
		System.out.println(bk3.hashCode());
		//오버라이딩 전 : Object 클래스의 hashCode => 객체의 실제 주소값을 10진수로 계산한 결과 값
		//오버라이딩 후 : 멤버 값이 같은 경우 같은 해쉬 코드 값이 나오도록 처리
		
		//동일 객체 : 실제 값도 같고 해쉬 코드도 같은 경우
		//동등 개체 : 실제 값은 같지만 해쉬 코드가 다른 경우(다른 객체이지만 필드 값만 같은 경우)
		//동등 객체 중복 제거 등의 로직에서 hash코드 판별 후 equals 판별
		
		
		/*오버라이딩 전
		 * 705927765
			366712642
			1829164700
		 */
		
		
		//--------------------------------
		String str1 = new String("hello");
		String str2 = new String("hello");
		
		System.out.println(str1 == str2);//주소값 비교 => false
		System.out.println(str1.equals(str2));//값 비교 => true
		System.out.println(str1.hashCode());
		System.out.println(str2.hashCode());
		
		
	}

}

 

 


다형성

: 부모타입 레퍼런스로 자식 객체를 다루는 기술.

: 객체지향 프로그래밍의 3대 특징 중 하나로 ‘여러 개의 형태를 갖는다’는 의미. 부모타입 레퍼런스로 자식타입 레퍼런스를 다루는 기술

Parent p = new Child1();
Parent p2 = new Child2();

 

 

1) 부모, 자식 접근 클래스 예시

(1) Parent P = new Parent(); (O)

(2) Child c = new Child(); (O)

(3) Parent p = new Child(); (O)

(4) Child c = new Parent(); (X)

부모가 자식으로 들어가는 것은 불가능하다. 

 

 

2) 캐스팅

(1) 업 캐스팅

: 자식 타입이 부모타입으로 형변환이 되는 것.

-업 캐스팅은 자동 형변환이 된다

chr c = new Sonata();

 

 

(2) 다운 캐스팅

: 부모 타입이 자식 타입이 될 때

예) 

Car c = new Sonata();

((Sonata)c).moveSonata();

 

 

*instanceof 연산자

: 현재 참조형 변수가 어떤 클래스 형의 객체 주소를 참조하고 있는지 확인 할 때 사용. 

-클래스 타입이 맞으면 true, 맞지 않으면 false 반환.

 

3) 다형성을 이용할 때 좋은 점

-다형성을 이용하여 상속 관계에 있는 하나의 부모 클래스 타입의 배열 공간에 여러 종류의 자식 클래스 객체 저장 가능

Car[] carArr = new Car[5];
carArr[0] = new Sonata();
carArr[1] = new Avante();
carArr[2] = new Grandure();
carArr[3] = new Spark();
carArr[4] = new Morning();

 

4) 매개변수와 다형성

public void execute() {

driveCar(new Sonata());

driveCar(new Avante());

driveCar(new Grandure());

}

 

public void driveCar(Car c) {}

 

 

*바인딩

1) 동적 바인딩

: 컴파일 시 정적 바인딩 된 메소드를 실행할 당시의 객체 타입을 기준으로 바인딩 되는 것

 

(1) 동적 바인딩 성립 요건

: 상속 관계로 이뤄져 다형성이 적용된 경우, 메소드 오버라이딩이 되어 있으면 정적으로 바인딩 된 메소드 코드보다 오버라이딩 된 메소드 코드를 우선적으로 수행한다

 

2) 정적 바인딩

 

package com.kh.chap1_poly.part01_basic.model.vo;

public class Parent {
	
	private int x;
	private int y;
	
	public Parent() {}

	public Parent(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return x;
	}

	public int getY() {
		return y;
	}

	public void setX(int x) {
		this.x = x;
	}

	public void setY(int y) {
		this.y = y;
	}
	
	public void printParent() {
		System.out.println("나 부모야");
	}
	
	public void print() {
		System.out.println("나 부모야");
	}
	
}
package com.kh.chap1_poly.part01_basic.model.vo;

public class Child1 extends Parent{
	private int z;
	
	public Child1() {}

	public Child1(int x, int y, int z) {
		super(x, y);
		this.z = z;
	}

	public int getZ() {
		return z;
	}

	public void setZ(int z) {
		this.z = z;
	}
	
	public void printChild1() {
		System.out.println("나 첫번째 자식이야");
	}
	@Override
	public void print() {
		System.out.println("나 첫번째 자식이야");
	}

}
package com.kh.chap1_poly.part01_basic.model.vo;

public class Child2 extends Parent{
	
	private int n;
	
	public Child2() {}
	
	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;
	}
	
	public void printChild2() {
		System.out.println("나 두번째 자식이야");
	}
	
	@Override
	public void print() {
		System.out.println("나 두번째 자식이야");
	}

}

 

<실행>

package com.kh.chap1_poly.part01_basic.run;

import com.kh.chap1_poly.part01_basic.model.vo.Child1;
import com.kh.chap1_poly.part01_basic.model.vo.Child2;
import com.kh.chap1_poly.part01_basic.model.vo.Parent;

public class Run {

	public static void main(String[] args) {
		
		System.out.println("1. 부모 타입 레퍼런스로 부모 객체 다루는 경우");
		Parent p1 = new Parent();
		p1.printParent();
		// => p1 레퍼런스로 Parent에만 접근 가능
		
		System.out.println("2. 자식 타입 레퍼런스로 자식 객체 다루는 경우");
		Child1 c1 = new Child1();
		c1.printParent(); //자식은 부모에 접근 가능
		c1.printChild1(); //자식은 본인에도 접근 가능
		
		
		// *******지금부터 다형성 적용 되는 내용********
		System.out.println("3. 부모 타입 레퍼런스로 자식 개체 다루는 경우");
		Parent p2 = new Child1(); 
		//대입 연산자를 기준으로 좌항, 우항의 타입이 다르지만 오류가 나지 않고 있다
		//why? 자동으로 Parent타입으로 형변환이 이뤄졌다고 생각해야 함 => 자동 형변환, 묵시적 형변환
		p2.printParent();
		//=> p2레퍼런스로 Parent만 접근할 수 있다(현재 Parent 타입이므로)
		
		// Q. Child1에 접근하고 싶다면? A. 강제 형변환, 명시적 형변환을 해줘야 한다
		((Child1)p2).printChild1();
		
		/* 상속 구조의 클래스들 간에는 형변환이 가능하다
		 * 
		 * 1. UpCasting(자식 타입을 부모타입으로 변환시키는 것) : 형변환 생략 가능, 자동 형변환
		 * ex) Parent = new Child();
		 *
		 * 
		 * 2. DownCasting(부모타입을 자식 타입으로 변환시키는 것) : 형변환 생략 불가, 강제 형변환
		 * ex) ((Child1)p2).printChild1();
		 * 
		 */
		
		System.out.println("4. 자식 타입 레퍼런스로 부모 객체를 다루는 경우");
		//Child1 c2 = new Parent();
		//빨간줄 = 컴파일 에러라고 한다
		//컴파일 에러(소스 상의 문법 오류) => 타입 불일치
		
		//Child1 c2 = (Child1)new Parent();
		//명시적으로 다운 캐스팅을 해주고 프로그램을 실행한다면?
		//런타임 에러(프로그램 실행 시 발생하는 오류) 발생 => ClassCastException
		//Cast 연산자 사용시 타입 오류
		
		
		 /*
		  * 1. 부모 타입 레퍼런스로 부모 객체 다루는 경우
			나 부모야
			2. 자식 타입 레퍼런스로 자식 객체 다루는 경우
			나 부모야
			나 첫번째 자식이야
			3. 부모 타입 레퍼런스로 자식 개체 다루는 경우
			나 부모야
			나 첫번째 자식이야
			4. 자식 타입 레퍼런스로 부모 객체를 다루는 경우
	
		  */
		
		
		//다형성을 사용하는 이유는?
		//다형성 적용 전
		Child1[] arr1 = new Child1[2];
		arr1[0] = new Child1(1, 2, 4);
		arr1[1] = new Child1(2, 3, 5);
		
		Child2[] arr2 = new Child2[2];
		arr2[0] = new Child2(2, 1, 5);
		arr2[1] = new Child2(5, 7, 2);
		
		System.out.println("=====다형성 적용 후 객체 배열로=====");
		Parent[] arr = new Parent[4];
		arr[0] = new Child1(1, 2, 4);
		arr[1] = new Child1(2, 3, 5);
		arr[2] = new Child2(2, 1, 5);
		arr[3] = new Child2(5, 7, 2);
		// => 하나의 부모 타입으로 다양한 자식 객체를 참조할 수 있음
		// => 자식에게 접근할 때 하나의 부모 타입으로 접근할 수 있음
		
		
		//각각의 메소드를 실행해보자
		((Child1)arr[0]).printChild1();
		((Child1)arr[1]).printChild1();
		((Child2)arr[2]).printChild2();
		((Child2)arr[3]).printChild2();
		
		/*
		 * =====다형성 적용 후 객체 배열로=====
			나 첫번째 자식이야
			나 첫번째 자식이야
			나 두번째 자식이야
			나 두번째 자식이야
		 */
		
		System.out.println("=====반복문 이용해서 출력=====");
		for(int i = 0 ; i < arr.length ; i ++) {
			//Q. 각 인덱스 별로 어떤 자식 객체를 참조하고 있는지 어떻게 판별하지..?
			// A. <instanceof 연산자>
			//현재 레퍼런스가 어떤 클래스형의 주소를 참조하고 있는지 확인할 때 사용한다
			//클래스 타입이 일치하면 true, 아니면 false를 리턴
			if(arr[i] instanceof Child1) {
				((Child1)arr[i]).printChild1();
			}else if(arr[i] instanceof Child2) {
				((Child2)arr[i]).printChild2();
			}
		}
		/*
		 * =====반복문 이용해서 출력=====
			나 첫번째 자식이야
			나 첫번째 자식이야
			나 두번째 자식이야
			나 두번째 자식이야
		 */
		
		System.out.println("===== 향상 된 for문 이용해서 출력 =====");
		//실제 참조하고 있는 타입의 메소드를 적절하게 실행시켜주기 위해
		//instanceof연산자를 사용해서..(이 뒤에 못 씀)
		for(Parent p : arr) {
			if(p instanceof Child1) {
				((Child1)p).printChild1();
			}else if(p instanceof Child2) {
				((Child2)p).printChild2();
			}
		}
		/*
		 * ===== 향상 된 for문 이용해서 출력 =====
			나 첫번째 자식이야
			나 첫번째 자식이야
			나 두번째 자식이야
			나 두번째 자식이야
		 */
		
		System.out.println("=====오버라이딩 적용해서 출력");
		for(int i = 0 ; i < arr.length ; i++) {
			arr[i].print();
			
		//=> 자동으로 실제 참조하고 있는 객체의 오버라이딩 된 메소드를 실횅함
		//=> '동적 바이딩'이 일어났다
			
			/*
			 * =====오버라이딩 적용해서 출력
				나 첫번째 자식이야
				나 첫번째 자식이야
				나 두번째 자식이야
				나 두번째 자식이야

			 */
		}
		}
	
	

}