Hayden's Archive

[자바/Java] 해징 / 상속 / 상속에서의 생성자 에러 / 오버라이딩 / 다형성 / 객체 형변환 본문

Study/Java & Kotlin

[자바/Java] 해징 / 상속 / 상속에서의 생성자 에러 / 오버라이딩 / 다형성 / 객체 형변환

_hayden 2020. 4. 16. 18:48

해징 관련 포스팅 : https://hayden-archive.tistory.com/64

참고 1 : https://pasudo123.tistory.com/24

참고 2 : https://micropilot.tistory.com/2416

 

 

해징(Hasing) - Has a Relation //수평관계
==> Service가 VO를 가지고 VO에 의존한다. --> 1. 필드에 선언 // 2. 주입 : 생성자, Setter 

상속(Inheritance) - Is a Relation //수직관계

 


 

 상속(Inheritance) 

- 프로그램에서의 상속은 부모가 가진 모든 것(필드, 메소드)을 자식에게 물려주는 것 + 자식은 자신만의 멤버(필드, 메소드)를 추가하는 것

- 부모한테 300억짜리 빌딩 물려받으면 재테크해서 재산을 불리듯이 자식은 부모보다 무조건 더 나아야 함.

- 따라서 자식부모로부터 drive되었고 부모로부터 기인했지만 부모보다 멤버수가 무조건 많고 부모보다 확장됨. 부모보다 자식의 기능이 powerful해짐.

- 개발자는 부모가 아닌 자식을 사용함. 개발자가 직접 사용하는 것은 맨 끝단에 있는 자식이다.

참고) 단말 노드 = Leaf Node(자료 구조 트리에 존재하는 모든 노드 가운데 자식을 가지지 않는 노드를 통틀어 이르는 말) // 머신러닝에서 결정처리, 앙상블 모델이 있는데 Tree에서 Leaf Node.

부모가 필요한 이유? -> 부모를 재사용하기 위함. 자식은 확장된 기능을 씀.

Caption TV(자막 TV) 평면 TV를 만들려고 하는데 부모 없이 만들려고 하면 너무 오래 걸림. 기존의 classic한 TV가 있으면 물려받아서 새롭게 만듦. 기존의 것을 본떠서 새롭게 추가해서 만드는 것.

 


 

 상속의 단계, 추상화와 구체화 

Manager와 Engineer는 아무런 관계가 없음(unrelated). 하지만 name, birthdate, salary라는 공통적인 성질(필드)을 공유함. 이런 게 발견되면...

상속 1단계 : Generalization(일반화), Abstraction(추상화) 과정. 공통적인 성질을 일반적인 성질로 가지는 모듈을 윗단에 하나 설정.
- Person은 너무 광범위. Employee라고 하고 이 Employee가 name, birthdate, salary를 가지면 됨. => 공통점을 뽑아서 아래에서 위로 올라감.

근데 추상화 과정만으로 상속을 하면 안 됨. 공통적인 성질을 가지는 실제 세계의 객체는 엄청 많음...

상속 2단계 : is a Relation이 맞는지 검증하고 확인. Manager는 Employee인가? OK! -> 상속! 
- 상속은 실선으로 표시. 화살표 안은 새카맣게 칠해지는 게 맞지만 툴에 따라 약간의 차이가 있음. 
- 좌우로 상속 표현X. 직관적으로 이해하기 위해 상하로 만듦.

Employee ==> 부모 클래스. Parent Class. Super Class. 
Manager, Engineer ==> 자식 클래스. Child Class. Sub Class.

C++과의 차이점으로 자바는 프로그램의 안정성을 위해 가장 상단에 있는 root class를 뒀다. 모든 클래스들의 root class는 Object 클래스이다. ( 관련 포스팅 : https://hayden-archive.tistory.com/79 )

큰 조직일수록 하위에서 상위로 올라가는 경우는 잘 없다.(추상화)
이미 설계되어 있는 데에 상위에서 하위로 내려가는 경우가 많다.(구체화)

 


 

 상속은 부모와 자식이 한몸 / 다중상속 불가 

- 기본형은 Heap에 값이 바로 들어감. 하지만 클래스 데이터 타입은 Heap에 값이 바로 들어가지 않고 값을 참조하는 주소값이 들어감. ( 관련 포스팅 : https://hayden-archive.tistory.com/67?category=775409 )

- 상속을 하면 부모랑 자식이 따로 만들어지는 게 아니라 자식 객체가 부모 객체를 쓰는 거임.
상속은 부모와 자식이 한몸이고 자식은 부모 밑에 붙어서 만들어짐

- 위 그림에서처럼 모든 클래스들의 부모인 Object 클래스가 객체로 있고 그 밑에 부모(Super)인 Employee 객체가 달라붙고 그 아래에 자식(Sub)인 Manager 객체가 달라붙음.

 

 

부모가 두개면 안 됨. 상속은 단일 상속만 가능하다. 부모가 2개인 기술도 있지만 자바는 코드의 안정성을 위해 부모가 1개밖에 안됨.


 상속과 생성자 에러 문제 

public class Manager extends Employee

부모로부터 물려받아 확장되었으므로 extends라는 키워드를 쓴다. 부모로부터 물려받았으므로 자식은 자기 자신만의 필드를 추가하면 된다.

 

- 클래스 자기 자신을 가리킬 때는 this를 쓰고, 부모를 가리킬 때는 super를 쓴다.

- 생성자는 클래스의 멤버가 아니므로 물려지는 요소가 아님.(생성자는 메소드와 같은 일을 하지만 메소드가 아님) 객체가 생성될 때 값을 집어넣으려고 만든 함수 같은 알고리즘임. 생성자는 상속되지 않음. 따라서 오버라이딩 되지도 않음. (오버라이딩 아래 파트 설명 참고)

- 자식 객체를 생성할 때 무조건 부모가 먼저 존재해야 함. (프로그램 코드에서 정말 중요함!)

- 따라서, 자식 생성자 첫 라인에서 무조건 부모 생성자 호출이 무조건 일어남.

 

// 부모 클래스
class Employee { 
	private String name;
	private double salary;
	private MyDate birthDate;
	
	public Employee(String name, double salary, MyDate birthDate) { 
		super(); 
		this.name = name;
		this.salary = salary;
		this.birthDate = birthDate;
	} 
	
}

// 자식 클래스
public class Manager extends Employee {
	private String dept;
	
	public Manager(String dept) {
		super(); 
		this.dept = dept;
	}
}

 

- 클래스의 생성자에서 super();가 있든 없든 무조건 부모 생성자가 먼저 호출된다. 부모 클래스에서는 최상위 클래스인 Object 클래스가 호출될 것이고, 자식 클래스에서는 부모 클래스가 호출될 것이다.

- 그런데 위 코드에서는 에러가 난다. 자식 객체를 생성할 때 생성자 호출시 문제가 생기기 때문이다.

- 자식이 만들어지려면 메모리상에 부모가 먼저 올라가야 한다. 이 말은 자식 객체가 생성되려면 무조건 부모 객체가 생성되어야 한다는 말이고, 부모 생성자 호출이 일어나야 한다는 말이다.

- 그런데 부모 생성자의 매개변수(name, salary, birthDate)가 상속받은 자식 생성자에게 전달되지 않고 있다!

 

해결 방법은 2가지가 있다.

1. 부모클래스에 전달하는 인자값이 없는 기본 생성자 만들어주기

// 부모 클래스
class Employee { 
	private String name;
	private double salary;
	private MyDate birthDate;
	
	public Employee(){} // 부모의 기본 생성자 추가!!!!!!!
    
	public Employee(String name, double salary, MyDate birthDate) { 
		super(); 
		this.name = name;
		this.salary = salary;
		this.birthDate = birthDate;
	} 
	
}

// 자식 클래스
public class Manager extends Employee {
	private String dept;
	
	public Manager(String dept) {
		super(); 
		this.dept = dept;
	}
}

 

2. 자식클래스의 생성자 안에 부모 클래스의 매개변수 생성자인 super(매개변수);를 선언해주기

// 부모 클래스
class Employee { 
	private String name;
	private double salary;
	private MyDate birthDate;
    
	public Employee(String name, double salary, MyDate birthDate) { 
		super(); 
		this.name = name;
		this.salary = salary;
		this.birthDate = birthDate;
	} 
	
}

// 자식 클래스
public class Manager extends Employee {
	private String dept;
	
	public Manager(String name, double salary, MyDate birthDate, String dept) {
	// 자식 클래스의 생성자에 부모 필드를 함께 매개변수로 넣고 
		super(name, salary, birthDate); // 부모 클래스 생성자 선언
		this.dept = dept;
	}
}

 


 

 오버라이딩(Overriding) 

메소드는 부모가 가진 게 그대로 물려진다. 그런데 이 메소드는 부모한테 맞는 기능이므로 나한테 맞게 고치려면 오버라이딩을 한다.

Method Overriding

Step 1. 부모의 기능을 그대로 받아서
Step 2. 그 기능을 자식 본인에게 맞도록 고쳐 쓴다.

==> 부모로부터 상속받은 기존의 메서드를 자식한테 맞게 재정의한다!
==> 오버라이딩을 하면, 부모 메소드를 무시하고 자식 메소드를 우선한다.

 

오버라이딩 코드 및 주석(Annotation)-@Override 참고 : http://blog.naver.com/beaqon/221057292808

 

자바 오버라이딩 (@Override)

부모 클래스의 메서드를 상속받으면 자식 클래스 역시 그 메서드를 사용할 수 있게 되지만, 필요에 따라서 ...

blog.naver.com

 

보통 오버라이딩을 하면, 자동으로 부모 메소드는 무시되고 자식 메소드만 사용된다.
하지만 오버라이딩을 해도 super를 사용해서 부모 메소드를 호출할 수 있다. ( 참고 : https://gosmcom.tistory.com/18 )

@Override 
public String toString() {
	return super.toString(); 
}

만약 부모가 없다면 모든 클래스의 부모인 Object로 가게 됨. 따라서 Object 객체에 있는 toString()을 호출함. 

 

// 부모 메소드
public String getDetails() {
	return name+","+salary+","+birthDate;
}
// 자식 메소드
public String getDetails() {
	return super.getDetails() + ", " +dept;
}

이렇게 부모 메소드를 호출해와서 name, salary, birthDate를 가져오고 여기에 자식 필드를 덧붙여서 반환시킬 수도 있다.

 


 

 다형성(Polymorphism)  - 가장 중요!

// 부모 Employee 클래스 데이터 타입으로 Manager, Engineer, Secretary 자식 객체 생성 
// 이게 바로 다형성(Polymorphism)
Employee e1 = new Employee("김00", 12000.0, new MyDate(1975, 1, 1));
Employee e2 = new Manager("이00", 23000.0, new MyDate(1972, 11, 1), "체육부");
Employee e3 = new Engineer("최00", 56000.0, new MyDate(1987, 3, 1), "R", 200.0);
Employee e4 = new Secretary("박00", 34000.0, new MyDate(1990, 3, 1), "강00");

 

- 다형성(폴리모피즘 poly + morphism) : 하나의 객체변수가 여러 가지 모양과 모습을 가지는 능력. 즉, 부모 타입으로 자식 객체를 생성하는 것

- 상속에서 재사용성이 가능한 것은 부모 타입으로 자식 객체를 폴미몰피즘 했기 때문.

- 상속을 하는 이유는 Polymorphism을 하기 위해서 하는 것.

 

다형성(Polymorphism)의 장점 

1. Heterogeneous Collections(이종 객체들의 모임)

배열은 같은 타입으로만 만들 수 있는데(Homogeneous Collection) 다른 타입들을 모아서 배열로 만들 수 있게 됨. 다형성을 통해 Employee 객체, Manager 객체,  Engineer 객체, Secretary 객체를 한 배열에 담을 수 있게 된다!

Employee e1 = new Employee("김00", 12000.0, new MyDate(1975, 1, 1));
Employee e2 = new Manager("이00", 23000.0, new MyDate(1972, 11, 1), "체육부");
Employee e3 = new Engineer("최00", 56000.0, new MyDate(1987, 3, 1), "R", 200.0);
Employee e4 = new Secretary("박00", 34000.0, new MyDate(1990, 3, 1), "강00");
Employee[ ] emps = {e1, e2, e3, e4};

 

2. Polymorphic Argument(다형성 인자)

다형성을 매개변수에 적용할 수 있음. 매개변수를 부모 타입으로 정의하면 그 하위의 모든 객체를 다 받을 수 있음. 매개변수를 Employee 타입으로 정의하면 Manager, Engineer, Secretary 타입의 객체를 모두 다 받을 수 있음.

예를 들어 Manager, Engineer, Secretary 객체를 핸들링하는 메소드를 짤 때...

public void handleManager(Manager m) {}
public void handleEngineer(Engineer m) {}
public void handleSecretary(Secretary m) {}

상속을 안 쓴 최악의 절차적 프로그래밍 코드. Polymorphism 안 쓰면 이걸 각각 핸들링해야 함. 위 코드에서는 3개 밖에 안 되지만 현업 가면 이런 VO가 수십개 넘게 있음. 

 

public void handleManager(Manager m) {}
public void manageEngineer(Engineer m) {}
public void getSecretary(Secretary m) {}

상속을 안 했어도 현실 세계와 어울리게 직관적으로 짓는답시고 메소드 이름을 이렇게 정하면 안 됨. 어차피 메소드 기능 같음.

 

public void handleEmployee(Manager m) {}
public void handleEmployee(Engineer m) {}
public void handleEmployee(Secretary m) {}

상속을 안 하더라도 메소드 오버로딩 해야 함.

 

여기서 상속을 해서 다형성이 생기면 이렇게 코드를 짤 수 있음.

public void handleEmployee(Employee e) {
	// 그런데 이 객체가 어떤 타입인지 알아야 할 때가 있다. 그 때는...
	if(e instanceof Manager) {} //e라는 객체가 Manager라면
	if(e instanceof Engineer) {} //e라는 객체가 Engineer라면
	if(e instanceof Secretary) {} //e라는 객체가 Secretary라면
}

Manager, Engineer, Secretary 타입으로 오버로딩할 필요도 없어지고 확장성이 커짐. 최소한의 변경으로 새롭게 추가되는 것들을 추가 가능!

 

3. Virtual Method Invocation(가상 메소드 호출)  - 이 때, 가상 메소드는 자식 메소드를 뜻함

: 상속 + 오버라이딩된 메소드에서만 나타나는 성질. 

1) 컴파일 시점 메소드 : 컴파일 시점에서는 부모 타입 메소드를 호출. Employee의 getDetails()를 호출 
2) 실행 시점 메소드 : 실행 시점에서는 자식의 메소드가 호출. 자식(Manager, Engineer, Secretary)의 getDetails()를 호출.

 


 

 객체 형변환(Object Casting) 

자료형 변환(Type Casting) 관련 포스팅 : https://hayden-archive.tistory.com/55

 

[자바/Java] 변수와 자료형

참고 도서 : Do it! 자바 프로그래밍 입문 ( https://book.naver.com/bookdb/book_detail.nhn?bid=13797129 ) 참고 강의 : Do it! 자바 프로그래밍 입문 인프런 강의 ( https://www.inflearn.com/course/%EC%9E%90..

hayden-archive.tistory.com

 

Employee e1 = new Employee("김00", 12000.0, new MyDate(1975, 1, 1));
Employee e2 = new Manager("이00", 23000.0, new MyDate(1972, 11, 1), "체육부");
Employee e3 = new Engineer("최00", 56000.0, new MyDate(1987, 3, 1), "R", 200.0);
Employee e4 = new Secretary("박00", 34000.0, new MyDate(1990, 3, 1), "강00");

 

- e3.changeTech("자바"); 라는 메소드 = Employee에 있는 changeTech()를 호출해라! 그런데 changeTech()는 Engineer에만 있는 메소드라서 찾을 수 없음. → 부모가 가지고 있지 않은, 자식만의 멤버를 호출하거나 변경하려고 할 수 없음.

- 그래서 Engineer eg3 = (Engineer)e3; 이렇게 e3를 Engineer 타입으로 캐스팅해준다.(객체 형변환 Object Casting)

- 그러면 이제 eg3.changeTech("Java"); 이렇게 Engineer의 멤버를 변경할 수 있다.

 

=> 결론 : 부모 타입으로 자식 객체를 만들었을 때(=Polymorphism 방식으로 객체를 만들었을 때) 자식 만의 멤버를 만들려고 하면 캐스팅해서 써야 함.