Wanna be Brilliant Full-Stack Developer

자바 객체지향프로그래밍의 이해 오버라이딩 본문

Some Memos/Java

자바 객체지향프로그래밍의 이해 오버라이딩

Flashpacker 2023. 3. 2. 17:53


오버라이딩에 대해서 배워보자! 

오버라이딩을 통해서 오버로딩의 한계를 극복해보자!

package ch05;

class 질럿 {
	String name = "질럿";
}

class 드라군 {
	String name = "드라군";

}

class 다크템플러 {
	String name = "다크템플러";
}

public class OOPEx06 {

	public static void main(String[] args) {
		질럿 u1 = new 질럿();
		드라군 u2 = new 드라군();
		다크템플러 u3 = new 다크템플러();
	}

}

디폴트 생성자다. 안보여도 있는 상황이다. 

package ch05;

class 질럿 {
	String name = "질럿";
}

class 드라군 {
	String name = "드라군";

}

class 다크템플러 {
	String name = "다크템플러";
}

public class OOPEx06 {

	public static void main(String[] args) {
		질럿 u1 = new 질럿();
		드라군 u2 = new 드라군();
		다크템플러 u3 = new 다크템플러();
	}

}

질럿이 다크템플러를 공격할것인데 이떄 또 복사해서 사용하면서하면 오버로딩이다. 

package ch05;

class 질럿 {
	String name = "질럿";
	
	void 기본공격(드라군 e1) {
		System.out.println("질럿이 "+e1.name + "을 공격합니다.");
	}
	
	void 기본공격(다크템플러 e1) {
		System.out.println("다크템플러 "+e1.name + "을 공격합니다.");
	}
}

오버로딩이 단점이 있었다. 유닛이 많으면 많을 수록 메서드를 만들어야해서 힘들다.

 

그래서 저 기본공격 메서드를 통해서 

드라군 과 다크템플러도 받을 수 있도록 짜면 된다.

그럴때는 다형성을 이용하면 된다. 

 

질럿이 있고 드라군이 있고 다크템플러가 있는데!

질럿이면 질럿이니? 드라군에게 드라군이니? 다크템플러에게 다크템플러이니? 물어보면 다 네라고 하는데애네들을 추상화를 하려고한다. 추상화를 시켜서 프로토스 유닛이라고 추상화를 시켜준다.

상속(Extends)를 한다는것이다. 이 세개다 프로토스 유닛이다. 추상화를 시키면 

이 세개 타입이 프로토스 유닛 타입으로 바뀐다. 

그러면 new질럿을 하면 heap 메모리에 질럿 프로토스 유닛 이렇게 뜬다는것이다.

 

띄어보자! 

package ch05;

class 프로토스유닛{
	
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	void 기본공격(드라군 e1) {
		System.out.println("질럿이 "+e1.name + "을 공격합니다.");
	}
	
}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템플러";
}

public class OOPEx06 {

	public static void main(String[] args) {
		질럿 u1 = new 질럿(); // (질럿과 프로토스 유닛)
		드라군 u2 = new 드라군(); // ( 드라군과 프로토스유닛)
		다크템플러 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛)
		
		
		u1.기본공격(u2);
	}

}

근데 앞에 타입이 질럿 이라고 되어있으니까 타입을 바꿔보자 다형성이니까 프로토스 유닛으로 변경할 수 있다. 

그러면 주소가 바라보는것도 프로토스유닛을 바라보게 될것이다, 

package ch05;

class 프로토스유닛{
	
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	void 기본공격(드라군 e1) {
		System.out.println("질럿이 "+e1.name + "을 공격합니다.");
	}
	
}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템플러";
}

public class OOPEx06 {

	public static void main(String[] args) {
		프로토스유닛 u1 = new 질럿(); // (질럿과 프로토스 유닛V)
		프로토스유닛 u2 = new 드라군(); // ( 드라군과 프로토스유닛V)
		프로토스유닛 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛V)
		
		
	}

}

내가 new질럿을 하면 앞에 타입이 프로토스 유닛이면 프로토스 유닛을 바라보고 있는 것이다. 

이렇게 되니까 오류가 난다.

오류가 나는 이유는 프로토스유닛은 기본공격 함수를 들고 있지 않기 떄문이다.

애는 기본공격 메소드를 가지고 있지 않다. 

지금 문제가 타입을 다 일치를 시켰는데  u1은 

애를 가리키고 있는데 프로토스 유닛은 아무것도 안가지고 있다. 
프로토스 유닛이 기본공격이라는 메서드를 안가지고 있었는데 가지고 있다면 

package ch05;

class 프로토스유닛{
	void 기본공격(프로토스유닛 e1) {
		System.out.println("프로토스유닛 메서드");
	}
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	void 기본공격(프로토스유닛 e1) {
		System.out.println("질럿 메서드");
		// System.out.println("질럿이 "+e1.name + "을 공격합니다.");
	}
	
}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템플러";
}

public class OOPEx06 {

	public static void main(String[] args) {
		프로토스유닛 u1 = new 질럿(); // (질럿과 프로토스 유닛V)
		프로토스유닛 u2 = new 드라군(); // ( 드라군과 프로토스유닛V)
		프로토스유닛 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛V)
		
		
	}

}

그다음에 기본공격 메서드는 질럿도 가지고 있고 드라군도 가지고 있고 다크템플러도 가지고 있다.

동일한 메서드가 양쪽으로 다 있다.

부모도 들고 있고 자식도 들고 있다. 

 

u1.기본공격(u2); 이렇게 실행을 하면 이상한 일이 일어난다?!

머가 실행이 되는가? u1은 프로토스 유닛의 기본공격을 실행하지 않고 

class 질럿 extends 프로토스유닛{
String name = "질럿";

void 기본공격(프로토스유닛 e1) {
System.out.println("질럿 메서드");
// System.out.println("질럿이 "+e1.name + "을 공격합니다.");
}

 

애가 실행이 되었다. 이게 무엇인가? 잘기억해야한다. 

부모가 들고 있는 메소드를 동일하게 자식이 적게 되면 이것을 오버라이딩이라고 한다. 

오버라이드는 무효화다라는 뜻을 가지고 있는데 무엇을 무효화 하는가? 

부모의 메서드를 무효화하는것이다. 

오버라이드라는것은 무효화하다라는 뜻을 가지고 있다.

부모가 기본공격이라는 메서드를 가지고 있는데 자식이 똑같은 이름으로 기본공격을 가지고 있으면

부모의 메서드를 무효화 시켜버린다. 없는것 취급을 해버린다.

이것이 오버라이딩이라는 것이다.

둘다 들고 있으면 부모께 무효화 된다. 

그러면 이 내부는 이제 의미가 없다. 

어차피 내부가 실행될 일이 없기 떄문이다. 

오류가 뜨는데 e1이 name이라는 애를 안들고 있다. 그리하여

메서드는 동일한 이름을 만들게되면 오버라이딩이 되어서 부모의 메서드를 무효화 시키지만

변수는 그렇지 않다. 변수는 자식 과 부모도 name 변수를 가지고 있으면 부모의 이름을 써버린다!

 

그러면 원하는 프로그램이 아니다 e1.name을 쓸떄 질럿이 나와야한다. 

 

빈 껍데기 함수를 만들고 밑에 e1.name이 아니라 e1.이름확인

package ch05;

class 프로토스유닛{
	String name = "프로토스유닛";
	void 기본공격(프로토스유닛 e1) {}
		
		String 이름확인() {
			return "?";
		
	}
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	// 오버라이드 = 무효화 하다
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println("질럿이 "+e1.이름확인() + "을 공격합니다.");
	}
	
}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템플러";
}

public class OOPEx06 {

	public static void main(String[] args) {
		프로토스유닛 u1 = new 질럿(); // (질럿과 프로토스 유닛V)
		프로토스유닛 u2 = new 드라군(); // ( 드라군과 프로토스유닛V)
		프로토스유닛 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛V)
		
		u1.기본공격(u3);
	}

}

그러면 부모의 이름확인이라는 행위가 있는데 자식도 똑같은 메서드를 만들어보자

그러면 똑같은 함수가 있다면 부모의 메서드가 무효화된다.

 

package ch05;

class 프로토스유닛{
	String name = "프로토스유닛";
	void 기본공격(프로토스유닛 e1) {}
		
		String 이름확인() {
			return "?";
		
	}
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	// 오버라이드 = 무효화 하다
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println("질럿이 "+e1.이름확인() + "을 공격합니다.");
		
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}

}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}
}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템 플러";
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
		String 이름확인() {
			return name;
		}
}

public class OOPEx06 {

	public static void main(String[] args) {
		프로토스유닛 u1 = new 질럿(); // (질럿과 프로토스 유닛V)
		프로토스유닛 u2 = new 드라군(); // ( 드라군과 프로토스유닛V)
		프로토스유닛 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛V)
		
		u1.기본공격(u1);
	}

}

이렇게 프로그램을 짜니까 기본공격이라는 메서드 하나로 

이 사이에 추상화된 타입 프로토스 유닛을 넣기만 하면 된다. 

그러면 나머지도 만들어보자 

package ch05;

class 프로토스유닛{
	String name = "프로토스유닛";
	void 기본공격(프로토스유닛 e1) {}
		
		String 이름확인() {
			return "?";
		
	}
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	// 오버라이드 = 무효화 하다
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}

}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}
}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템 플러";
	
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
		String 이름확인() {
			return name;
		}
}

public class OOPEx06 {

	public static void main(String[] args) {
		프로토스유닛 u1 = new 질럿(); // (질럿과 프로토스 유닛V)
		프로토스유닛 u2 = new 드라군(); // ( 드라군과 프로토스유닛V)
		프로토스유닛 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛V)
		
		u1.기본공격(u1);
	}

}

이제 오버라이드가 되었다. 오버라이드가 되었다는 것은? 

부모가 들고 있는 메서드를 자식이 똑같은것을 가지고 있다면 부모가 들고 있는 메서드를 무효화 한다.

 

그다음에 이상태에서 신입한테 리버라는 유닛을 하나 만들어봐(오버라이드 해서 만들어!!)

그래서 리버라는 클래스를 만들고 무엇을 오버라이드 하라는거지 하고 팀장에게 물어본다.

무엇을 오버라이드 해야하는거에요? 물어보면 리버를 프로토스 유닛으로 상속을해봐 라고하면

상속을 하여 어떤 메서드를 만들어야하나요? 라고 물어보면 

공격하는 메서드와 이름을 확인하는 메서드가 필요해! 라고 이야기를 하면서

name이라는 변수를 하나 만들어서 리버를 넣으라고 하면

 

class 리버 extends 프로토스유닛{
String name = "리버";

void 공격 () {

}

void 이름체크() {

}
}

 

이렇게 만들고 

이름을 확인하는거니까 String으로 해서 return name

공격은 누구를 공격하라는거지? 팀장님에게 누구를 공격하는거냐고 물어보면 

전체를 공격하고 프로토스 유닛 타입을 받으라고 이야기를 들으면?

class 리버 extends 프로토스유닛{
	String name = "리버";
	
	void 공격 (프로토스유닛 e1) {
		System.out.println(this.name +"이" + e1.이름확인()+"을 공격합니다.");
	}
	
	String 이름체크() {
		return name;
	}
}

이렇게 만들고 팀장님에게 보고를 하면?! 

\

package ch05;

class 프로토스유닛{
	String name = "프로토스유닛";
	void 기본공격(프로토스유닛 e1) {} // 무효화됨
		
		String 이름확인() { // 무효화됨
			return "?";
		
	}
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	// 오버라이드 = 무효화 하다
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}

}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}
}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템 플러";
	
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
		String 이름확인() {
			return name;
		}
}


// 신입 -> 리버라는 유닛을 하나 만들어봐(오버라이드 해서 만들어!!)
// 팀장 -> 프로토스 유닛으로 상속 ( 공격메서드 , 이름을 확인하는 메서드 필요해)
// -> name이라는 변수를 하나 만들어!! 거기에 이름을 리버라고 적어
// 누구를 공격하라는거죠? -> 프로토스 유닛 타입으로 받으면되
// 테스트해봐 -> 질럿으로 리버를 공격해봐
class 리버 extends 프로토스유닛{
	String name = "리버";
	
	void 공격 (프로토스유닛 e1) {
		System.out.println(this.name +"이" + e1.이름확인()+"을 공격합니다.");
	}
	
	String 이름체크() {
		return name;
	}
}

public class OOPEx06 {

	public static void main(String[] args) {
		프로토스유닛 u1 = new 질럿(); // (질럿과 프로토스 유닛V)
		프로토스유닛 u2 = new 드라군(); // ( 드라군과 프로토스유닛V)
		프로토스유닛 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛V)
		프로토스유닛 u4 = new 리버();
		
		u1.기본공격(u1);
		u2.기본공격(u1);
		u2.기본공격(u3);
		u1.기본공격(u4);
		
	}

}

그리고 결과도 질럿이 ?를 공격한다고 잘못나온다! 

 

그래서 팀장님에게 ?가 뜬다고 말을하면? 

그거 메서드 이름 머라고 적었어? 

메서드 이름을 자식쪽에서 잘못 적어서 지금 오버라이드가 안된것이다.

그리하여 메서드 이름을 통일 하면 

package ch05;

class 프로토스유닛{
	String name = "프로토스유닛";
	void 기본공격(프로토스유닛 e1) {} // 무효화됨
		
		String 이름확인() { // 무효화됨
			return "?";
		
	}
}

class 질럿 extends 프로토스유닛{
	String name = "질럿";
	
	// 오버라이드 = 무효화 하다
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}

}

class 드라군 extends 프로토스유닛 {
	String name = "드라군";

	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
	String 이름확인() {
		return name;
	}
}

class 다크템플러 extends 프로토스유닛{
	String name = "다크템 플러";
	
	void 기본공격(프로토스유닛 e1) {
		//System.out.println("질럿 메서드");
		 System.out.println(this.name + "이 "+e1.이름확인() + "을 공격합니다.");	
	}
	
	// 오버라이드 = 부모의 메서드를 무효화하다.
		String 이름확인() {
			return name;
		}
}


// 신입 -> 리버라는 유닛을 하나 만들어봐(오버라이드 해서 만들어!!)
// 팀장 -> 프로토스 유닛으로 상속 ( 공격메서드 , 이름을 확인하는 메서드 필요해)
// -> name이라는 변수를 하나 만들어!! 거기에 이름을 리버라고 적어
// 누구를 공격하라는거죠? -> 프로토스 유닛 타입으로 받으면되
// 테스트해봐 -> 질럿으로 리버를 공격해봐
// 팀장님 ? 가 뜨는데요?
// 아 그거 너 메서드 이름 머라고 적었어? -> 이름체크 
class 리버 extends 프로토스유닛{
	String name = "리버";
	
	void 공격 (프로토스유닛 e1) {
		System.out.println(this.name +"이" + e1.이름확인()+"을 공격합니다.");
	}
	
	// 오버라이딩이 안됐네 -> 무효화가 안됨
	String 이름확인() {
		return name;
	}
}

public class OOPEx06 {

	public static void main(String[] args) {
		프로토스유닛 u1 = new 질럿(); // (질럿과 프로토스 유닛V)
		프로토스유닛 u2 = new 드라군(); // ( 드라군과 프로토스유닛V)
		프로토스유닛 u3 = new 다크템플러(); // (다크템플러와 프로토스유닛V)
		프로토스유닛 u4 = new 리버();
		
		u1.기본공격(u1);
		u2.기본공격(u1);
		u2.기본공격(u3);
		u1.기본공격(u4);
		
	}

}

신입이 잘못한것은 함수도 잘못 만들었다. 공격이라고 만들지 않았기 떄문에

공격을 못한다. 

이 메서드를 보면 프로토스 유닛을 보면 기본공격과 이름확인을 들고 있다.

리버는 공격과 이름확인을 들고 있다.

그러면 어떻게 되는가? 이름 확인은 이름이 똑같으니까 오버라이드가 되니까 부모께 무효화가 되는데

공겨과 기본공격은 다르니까 이름이 다르니까 오버라이드가 안되어서 부모께 실행이 된것이다. 

 

이런 문제가 생기지 않도록 깔끔하게 하기 위해서는 단순한 방법을 다음에 배워보자!

이번에 한 다형성을 이용해서 해본것은 조금 어렵다.

 

부모라는 애가 있고 자식이 있는데 상속의 관계는 

이렇게 된다. 부모가 Speak라는 메소드를 들고 있으면 그러면 내가 new 부모라고 하면 

메모리에 부모만 뜬다.

u1.Speak라고 하면 무엇이 실행이 되는가 하면 Speak()가 실행이 된다. 

근데 내가 이상태에서 자식에 Speak를 만들고 나서 자식 u2 = new 자식()라고 하면 메모리에

자식과 메모리가 다 뜬다.

new를 하면 자식을 했지만 자식이 부모를 상속하고 있어서 메모리에 두개를 뜨지만

바라 보는건 자식 타입을 바라본다.

그러면 u2.speak()라고 하면 자식이 실행이 된다.

 

근데 이렇게 실행하면 말이 달라진다.

부모 u3 = new 자식() 이라고 하면 메모리에 자식도 뜨고 부모도 뜨는데 바라보는것은 부모를 바라본다

둘다 speak라는 메서드를 들고 있는데 둘다 들고 있으면 내가 u3.speak()를 호출하면 부모 Speak를 찾아간다.

애가 실행이 안되고 무효화가 되고 자식 speak가 실행이 된다.

 

부모도 자식도 speak를 들고 있는데 아까는 자식 u2 = new 자식() 에서 u2.speak() 라고 했으니까

u2가 바라보는 자식이니까 자식의 speak를 실행하는것이니까 실행이 되는데

 

밑에 부모 u3 = new 자식()  는 부모쪽으로 가서 speak를 실행하려고 하니까

자식이 가지고 있는것을 보고 무효화를 하고 자식꺼를 실행한다.

다형성을 이용하면 다형성은 자식 부모 관계가 있을때 부모의 메서드를 떄릴려고 했더니

자식의 메서드가 호출이 되는것을 동적 바인딩이라고 한다.

동적 바인딩을 이용하면 메서드를 하나만 만들 수 있다.

오버라이딩을 하지 않더라도 말이다!