Java/Java 기초

7. 상속과 다형성

펭킹 2022. 3. 24. 10:43

지금부터 다루는 내용은 2~6단원의 충분한 이해를 바탕으로 접근해야  쉽게 이해가 된다. 

 

상속의 정의

상속은 새 클래스를 작성할 때, 기존에 존재하는 클래스에서 멤버를 물려받아 생성하는 행위이다.

이때, 물려주는 쪽을 부모 클래스, 물려받는 쪽을 자식 클래스라고 하고, 자식은 부모에게 상속받는다고 한다.

이렇게 작성하게 되면 사용할 코드의 양이 줄어드는 효과가 있다.

 

상속의 형태는 다음과 같다

 

Class A{}

Class B extends A {}

 

이때 부모 클래스는 A가 되고, 자식 클래스는 B가 됩니다. 

자식 클래스는 부모 클래스의 함수나 변수를 그대로 가져와서 사용할 수 있지만, 부모는 자식에서 만들어진 변수나 함수를 사용할 수 없다.

또한, 자식이 상속을 받을 때, 부모의 생성자는 상속되지 않는다. 

자바는 단일 상속만 허용합니다. 다중 상속 발생 시 에러가 발생한다.

final로 선언된 클래스는 상속이 불가능하다.

 

오버라이딩(Overriding)

오버라이딩은 자식이 부모 클래스의 함수를 사용할 때 그 함수를 다시 작성하는 작업이다. 이때, 부모의 함수와 선언부가 일치하는 함수를 작성해야 한다.

만약 자식 클래스 오버라이딩 함수에서 아무것도 작성하지 않으면 부모의 함수를 그대로 사용하는 것으로 간주한다. 

 

상속과 생성자

자식 클래스의 생성자를 작업할 때, 부모의 생성자도  반드시 호출해서 사용해야 된다. 

자식의 생성자 내부에 부모 클래스의 생성자가 따로 없다면, 컴파일러는 자동으로 자식 클래스 생성자 맨 위에 super();를 추가한다.

Object는 클래스 가장 상위의 부모의 개념이라 다른 클래스로부터 상속받지 않고 모든 클래스를 상속한다. 따라서 모든 클래스는 Object를 자동으로 상속한다.

 

제어자

접근 제어자에는 다음과 같은 항목이 존재한다.

1. public : 접근 제한이 없다

2. protected : 같은 패키지와 자식 클래스

3. default : 같은 패키지

4. private : 같은 클래스

접근 제어자가 있는 경우 getter와 setter를 통해 반환과 설정을 할 수 있다.

 

static제어자는 변수나 함수 앞에 붙여서 사용하고, 해당 멤버가 클래스 멤버라는것을 표시한다.

인스턴스를 따로 생성하지 않아도 바로 접근할 수 있다.

 

final이 붙은 변수나 함수, 클래스는 더 이상 값을 변경하지 않는다는 것을 뜻합니다.

1.final 변수 : 상수

2.final 함수 : 오버라이딩 불가

3.final 클래스 : 상속 불가

 

다형성

다형성은 말 그대로 여러 형태를 가지는 성질인데, 이것을 객체지향 언어인 자바에서 사용하면 한 가지 타입에 여러 가지 형태의 인스턴스를 가지는 것을 뜻한다.

여기부터 조금 어렵게 느껴질 수 있는 이유가 상속 개념까지는 어느 정도는 구체적인 범위로 생각할 수 있었지만, 다형성은 더 추상적으로 표현하기 때문이다.

예를 들어 상속이 고양잇과로 나눈 항목에는 사자나, 먼치킨, 러시안 같은 품종이 들어간 것이라면, 다형성은 고양잇과, 개과, 파충류 목을 한 번에 묶는 동물로

사자를 지칭하는 것이다.

사자를 동물로 부를 수 있고, 동물이 사자, 말티즈, 이구아나 등을 모두 지칭하는 말인 것처럼 다형성은 모든 상속받는 모든 클래스의 간판 역할을 한다.

그럼 상속이 있는데 굳이 다형성을 쓸 필요가 없지 않냐고 할 수 있다. 다형성이 필요한 이유를 예로 들어 설명해보겠다.

package test;

class Animal_all//부모 클래스
{
	void sniff()
	{
		System.out.println("냄새맡기");
	}
	
	void eat()
	{		
		System.out.println("음식먹기");
	}
}

class Lion extends Animal_all//Animal_all을 상속받는 자식 클래스
{
	public String toString()//toString은 내용 즉시반환
	{
		return "사자";
	}
}

class Husky extends Animal_all//Animal_all을 상속받는 자식 클래스
{
	public String toString()
	{
		return "허스키";
	}
}

class Master//위 두마리 동물의 주인 클래스
{
	void feed1(Lion l1)//사자 먹이주기 함수
	{
		System.out.println(l1+"에게 먹이주기");
	}
	
	void feed2(Husky h1)//허스키 먹이주기 함수
	{
		System.out.println(h1+"에게 먹이주기");
	}
}


public class muti_test {

	public static void main(String[] args) {
		Lion l1 = new Lion();//Lion class를 통해 직접 접근
		Husky h1 = new Husky();
		Master ms = new Master();
		
		ms.feed1(l1);
		ms.feed2(h1);
	}

}

실행 결과는 다음과 같다.

 

사자에게 먹이주기
허스키에게 먹이주기

 

코드를 보게 되면 사자와 허스키를 생성하고, 먹이를 주려면 주인 클래스에서 각각 함수를 만들어 줘야 된다. (오버로딩을 사용하여 사자와 허스키 각각 밥 주는

함수를 생성)

지금은 2마리지만 관리해야 되는 동물이 100마리, 1000마리가 된다면, 주인 클래스에선 함수를 그에 맞춰 100개, 1000개를  함수 생성해줘야 된다.

하지만 다형성을 이용하여 main에서 선언할 때 동물로 묶게 된다면 다음과 같이 표현할 수 있다.

 

package test;

class Animal_all//부모 클래스
{
	void sniff()
	{
		System.out.println("냄새맡기");
	}
	
	void eat()
	{		
		System.out.println("음식먹기");
	}
}

class Lion extends Animal_all//Animal_all을 상속받는 자식 클래스
{
	public String toString()//toString은 내용 즉시반환
	{
		return "사자";
	}
}

class Husky extends Animal_all//Animal_all을 상속받는 자식 클래스
{
	public String toString()
	{
		return "허스키";
	}
}

class Master//위 두마리 동물의 주인 클래스
{
	void feed(Animal_all ani)
	{
		System.out.println(ani+"에게 먹이주기");
	}
}


public class muti_test {

	public static void main(String[] args) {
		Animal_all l1 = new Lion();//Animal_all을 통해 부모클래스를 거쳐서 접근
		Animal_all h1 = new Husky();
		Master ms = new Master();
		
		ms.feed(l1);
		ms.feed(h1);
	}

}

실행 결과는 다음과 같다.

 

사자에게 먹이주기
허스키에게 먹이주기

 

실행결과는 같게 나오나, 주인 클래스에선 더 이상 동물이 생성될 때마다 그에 맞는 함수를 생성할 필요가 없어졌다.

main에서 사자와 허스키 생성 시 Animal_all로 데이터 타입을 설정하여 부모를 통해 자식에 접근해서 동물로 묶어버려서 가능하다.

재사용성과 코드수를 줄이기 위해선 다형성의 활용이 필수적이고, 자바의 존재 이유기도 하다.