기록공간

클래스 고급 - 상속과 다형성 본문

Java

클래스 고급 - 상속과 다형성

입코딩 2020. 8. 25. 00:16
반응형

상속(Inheritance) 이란?

새로 설계(생성)하고자 하는 클래스가 이미 설계되어 있는 다른 클래스의 기능과 중복되는 경우

이미 설계된 클래스의 일부분이나 전체 구조를 공유할 수 있도록 하는 기능을 의미한다.

 

즉, 상속은 객체를 좀 더 쉽게 만들 수 있는 고수준의 재사용성(reusability)을 확보하고 객체 간의 관계를 구성함으로써 객체 지향의 또 다른 큰 특징인 다형성의 문법적 토대가 된다.

 

상속은 기존 프로그램의 클래스 내용을 공유함으로써 중복된 코드들을 재작성할 필요 없이 반복적이고 세부적인 작업을 처리하지 않기 때문에 프로그램을 작성하는 시간을 절약할 수 있고 유지보수를 편리하게 할 수 있으며, 프로그램의 길이도 짧아지게 해 준다. 또한, 이미 작성된 프로그램들은 앞서 테스트되었기 때문에 오류를 줄일 수 있어 현재 작성 중인 프로그램에만 전념할 수 있다.

 

물려주는 클래스 -> 상속하는 클래스 -> 상위 클래스 -> 부모(조상) 클래스 -> Super Class
물려받는 클래스 -> 상속받는 클래스 -> 하위 클래스 -> 자식(자손) 클래스 -> Sub Class

주의할 점

자바는 다중상속을 지원하지 않기 때문에 두 개 이상의 클래스로부터 상속을 받을 수 없다. 

자바는 단일상속만 허용한다.

 

예제)

// 부모 클래스
class SuperTest116 
{
	// [protected] : 상속받는 클래스, 동일한 패키지, 클래스 내부에서 접근 가능
	protected double area;
	
	// [default] : 동일한 패키지, 클래스 내부에서 접근 가능
	// 부모 클래스의 생성자 
	SuperTest116()
	{
		System.out.println("Super Class...");
	}

	// [default] : 동일한 패키지, 클래스 내부에서 접근 가능
	void write(String title)
	{
		System.out.println(title + " - " + area);
	}
}


// 자식 클래스(동일한 패키지에 존재하는 자식 클래스
public class Test116 extends SuperTest116 //, SuperTest117, SuperTest118	// 우리 부모님이 SuperTest116 이다~!!!
{
	// 부모 클래스로부터 상속받은 내용
	/*
	protected double area;

	(생성자는 상속에서 제외된다.)

	// write는 default 접근 제어 지시자이므로
	// 만약 SuperTest116이 자식 클래스인 Test116과
	// 동일한 패키지에 존재하지 않는다면
	// Test116은 write를 호출할 수 없다.
	void write(String title)
	{
		System.out.println(title + " - " + area);
	}
	*/

	//double area = 10.1234;

	// 자식 클래스의 생성자
	Test116()
	{
		// 부모 클래스의 생성자 호출
		//SuperTest116();	// -> super();
		
		//Test116();		// -> this();
		
		System.out.println("Sub Class...");

		//Super Class가 항상 먼저 출력되어야 한다.
		//super();
		// --==>> 에러 발생(컴파일 에러)
	}

	public void circle()
	{
		int r = 10;
		area = r * r * 3.141592;
		write("원");
	}

	public void rect()
	{
		int w = 20, h = 5;
		// super를 사용하면 자식 클래스 변수가 아닌 부모 클래스 변수가 온다.
		super.area = w * h;	
		super.write("사각형");
	}

	public static void main(String[] args)
	{
		// Test116 클래스(자식 클래스) 기반 인스턴스 생성
		Test116 ob = new Test116();
		// --==>> Super Class...
		//		  Sub Class...

		ob.circle();
		// --==>> 원 - 314.1592;

		ob.rect();
		// --==>> 사각형 - 100.0


	}
}

 

super

static으로 선언되지 않은 메소드에서 사용되며, 현재 클래스가 상속받은 상위 클래스의 객체를 가리킨다.

super는 상위 클래스의 생성자를 호출하거나 상위 클래스의 멤버 변수 또는 메소드를 호출할 때 사용할 수 있다. 

 

하위 클래스의 생성자에서 상위 클래스의 생성자를 호출할 때에는 하위 클래스의 생성자 정의 구문에서 맨 처음에만 위치할 수 있다.

 

생성자와 클래스 상속간의 관계

하위 클래스는 상위 클래스의 멤버를 상속받지만, 생성자는 상속 대상에서 제외된다. 그리고 하위 클래스의 생성자가 호출될 때 자동으로 상위 클래스의 생성자가 호출된다. 이 때, 상위 클래스의 생성자는 인수가 없는 생성자(default 생성자 형태)가 호출된다.

 

상위 클래스 및 하위 클래스를 설계하는 과정에서 상위 클래스의 생성자를 정의하지(작성하지) 않거나 인수가 없는 생성자만을 정의한(작성한) 경우 명시적으로 하위 클래스에서 상위 클래스의 생성자를 호출하지 않아도 아무런 문제가 발생하지 않지만 상위 클래스에 인자가 있는 생성자만 존재하는 경우에는 주의해야 한다.

 

예를 들어 다음 코드에서

class A_class
{
	A_class(int n)
	{
	}
}

class B_class extends A_class
{
	B_class()
	{
		super();
	}
}

하위 클래스인 B_class의 생성자에서 명시적으로 A_class의 생성자를 호출하지 않으면 자동으로 인자 없는 생성자를 호출한다. 

 

하지만, A_class에는 인자가 있는 생성자만 존재하고 인자가 없는 생성자는 존재하지 않기 때문에 에러가 발생한다. 따라서 B_class 생성자의 선두에 다음처럼 명시적으로 상위 클래스의 생성자 호출 구문을 작성해야 한다.

class A_class
{
	A_class(int n)
	{
	}
}

class B_class extends A_class
{
	B_class()
	{
		super(10);
		...
	}
}

 

종합적으로 모든 상황에서 정리하면 다음과 같은 규칙이 있다.

 

 

상속 시 주의할 사항

상위 클래스에서 선언된 멤버 변수의 이름과 하위 클래스에서 선언된 멤버 변수의 이름이 같으면 상위 클래스의 멤버 변수는 일반적인 사용 과정에서 무시된다. 이때, 상위 클래스의 멤버 변수를 사용하기 위해서는 super 키워드를 이용할 수 있다. 즉, super 키워드를 활용하여 상위 클래스의 멤버 변수를 사용할 수 있다는 것이다.

 

동일한 이름의 멤버 변수나 동일한 이름의 메소드가 한 클래스 안에 선언되거나 정의되는 경우 기본적으로 에러가 발생한다. 단, 메소드의 경우에는 매개변수의 개수나 타입이 다른 경우 에러가 발생하지 않고 이들을 서로 다른 메소드로 취급하게 된다. ==> Method Overloading(메서드 오버로딩)

 

예제)

// Rect117 클래스와, Circle117 클래스의 부모 클래스
class SuperTest117
{
	protected double area;
	private String title;

	public SuperTest117()
	{
		System.out.println("SuperTest117... 인자 없는 생성자");
	}

	public SuperTest117(String title)
	{
		this.title = title;
		System.out.println("SuperTest117... 문자열을 인자로 받는 생성자");
	}

	public void write()
	{
		System.out.println(title + " - " + area);
	}
}

// SuperTest117 클래스를 상속받는 자식 클래스
class Rect117 extends SuperTest117
{
	/*
	protected double area;

	public void write()
	{
		System.out.println(title + " - " + area);
	}
	*/

	private int w, h;

	// 자식 클래스의 사용자 정의 생성자(default 생성자 형태)
	public Rect117()
	{
		// 자동으로 삽입
		//super();  -> SuperTest117();
	}
	
	public void calc(int w, int h)
	{
		this.w = w;
		this.h = h;
		area = (double)this.w * this.h;
		write();
	}
	
	@Override				// -- 어노테이션(annotation) -> metadata(무언가를 설명하기 위한 데이타) -> JDK 1.5
	public void write()
	{
		System.out.println("w : " + w + ", h : " + h);
		System.out.println("사각형 - " + area);
	}

	// ※	메소드 오버라이딩(Method Overriding)
	//		
	//		상위 클래스를 상속받은 하위 클래스에서
	//		상위 클래스에 정의된 메소드를 다시 정의하는 것으로(재정의)
	//		객체 지향 프로그래밍의 특징인 다향성을 나타낸다.
	//		재정의(Overriding)는 반드시 상속 관계에 있어야 하며,
	//		메소드 이름, 리턴 타입, 매개변수의 갯수나 타입이
	//		완전히 일치해야 한다.


}

// SuperTest117 클래스를 상속받는 자식 클래스
class Circle117 extends SuperTest117
{
	/*
	protected double area;

	public void write()
	{
		System.out.println(title + " - " + area);
	}
	*/

	// 자식 클래스의 사용자 정의 생성자
	public Circle117(String title)
	{
		super(title);
	}

	public void calc(int r)
	{
		area = r * r * 3.141592;
		write();
	}
}

// main() 메소드를 포함하고 있는 외부의 다른 클래스(동일 패키지)
public class Test117
{
	public static void main(String[] args)
	{
		Rect117 ob1 = new Rect117();
		// --==>> SuperTest117... 인자 없는 생성자
		
		//Circle117 ob2 = new Circle117();
		// --==>> 에러 발생(컴파일 에러)
		// -- 현재 Circle117 클래스에는 
		//    매개변수를 필요로하는 사용자 정의 생성자가 만들어져 있으며
		//    이로 인해 default 생성자가 자동으로 삽입되지 않는 상태.

		Circle117 ob2 = new Circle117("원");
		// --==>> SuperTest117... 문자열을 인자로 받는 생성자

		ob1.calc(10, 20);
		// --==>> w : 10, h : 20
		//		  사각형 - 200.0

		ob2.calc(10);
		// --==>> 원 - 314.1592
	}
}

 

메소드 오버라이딩(Method Overriding)의 특징

  • 메소드 이름, 리턴 타입, 파라미터 수나 타입이 완전히 일치해야 한다.

  • 반드시 상속 관계가 있어야 한다.

  • 재정의된 하위 클래스의 메소드 접근제어지시자는 상위 클래스의 메소드 접근제어지시자보다 범위가 크거나 같아야 한다. 예를 들어, 상위 클래스 메소드의 접근제어지시자가 protected인 경우 하위 클래스가 이 메소드를 오버라이딩(Overriding)하는 경우 접근제어지시자는 protected 또는 public 이어야 한다.

  • static, final, private 메소드는 오버라이딩(Overriding) 할 수 없다.

  • Exception의 추가가 불가능하다. 즉, 상위 클래스의 메소드가 가지고 있는 기존 예외 사항에 새로운 Exception을 추가하는 것은 불가능하다는 것이다.

예제)

// 부모 클래스
class SuperTest119
{
	private int a = 5;
	protected int b = 10;
	public int c = 20;

	public void write()
	{
		System.out.println("Super write() 메소드 : " + a + " : " + b + " : " + c);
	}
}

// 자식 클래스
class SubTest119 extends SuperTest119
{
	protected int b = 100;

	public void print()
	{
		//System.out.println("Sub print() 메소드 : " + a + " : " + b + " : " + c);
		// -- 슈퍼클래스에서 선언된 변수 a에는 접근할 수 없다.
		//	  -> private 변수이기 때문에...

		System.out.println("Sub print() 메소드 : " + b + " : " + c);

		//System.out.println("Sub print() 메소드 : " + b);
		//System.out.println("Sub print() 메소드 : " + this.b);
		//System.out.println("Sub print() 메소드 : " + super.b);
		// -- 변수 b 는 접근 방법에 따라
		//    다른 b 로 접근 및 출력이 이루어진다.
		//	  슈퍼클래스에서 선언된 b, 서브클래스에서 선언된 b


		//System.out.println("Sub print() 메소드 : " + c);
		//System.out.println("Sub print() 메소드 : " + this.c);
		//System.out.println("Sub print() 메소드 : " + super.c);
		// -- 변수 c는 접근하는데 아무런 제약과 제한이 없다.
		//    슈퍼클래스에서 선언된 c
	}

	@Override
	public void write()
	{
		//System.out.println("Super write() 메소드 : " + a + " : " + b + " : " + c);
		// -- 슈퍼클래스에서 선언된 변수 a에는 접근할 수 없다.
		//    -> private 변수이기 때문에
		System.out.println("Super write() 메소드 : " + b + " : " + c);
	}
	
}

// main() 메소드를 포함하고 있는 외부의 다른 클래스(동일 패키지)
public class Test119
{
	public static void main(String[] args)
	{
		// 하위 클래스(SubTest119) 인스턴스 생성
		SubTest119 ob = new SubTest119();

		ob.print();
		// --==>> Sub print() 메소드 : 100 : 20

		ob.write();
		// write() 메소드를 Override 하기 이전 상황
		// --==>> Super write() 메소드 : 5 : 10 : 20

		// write() 메소드를 Override 한 이후 상황
		// --==>> Super write() 메소드 : 100 : 20

		System.out.println("------------------------------------- 구분선");

		System.out.println(ob.b);
		// --==>> 100
		
		// System.out.println(ob.super.b);
		System.out.println(((SuperTest119)ob).b);
		// --==>> 10
		// ※ 슈퍼 부름

		((SuperTest119)ob).write();
		// --==>> Super write() 메소드 : 100 : 20
		// ※ 이미 덮어씌워졌기 때문에 이는 원본으로 복원할 수 없음
		//    즉 메서드를 슈퍼 부름은 불가능하다.

		//    메소드와 변수를 꼭~!! 구분하여 정리할 것~!!! check~!!!

	}
}

 

추상 클래스(Abstract Class)

추상 클래스는 미완성된 클래스라고 생각하면 편하다. 선언만 있고 정의가 없는 하나 이상의 메소드(추상 메소드)를 갖는 클래스로 하위 클래스에서 오버라이딩(Overriding)할 것으로 예상되는 메소드에 대해 메모리 낭비 없이 미리 호출 계획을 세워두기 위해 만든다.

 

*형식 및 구조

[접근제어지시자] abstract class 클래스명
{
    [접근제어지시자] abstract 자료형 메소드명([매개변수], ...);
}

특징

클래스가 적어도 하나 이상의 추상 메소드를 포함할 때 그 클래스는 클래스 앞에 abstract 키워드를 붙여 추상 클래스로 명시해야 하며, 추상 클래스로 선언한 경우에는 불완전한 형태의 클래스이므로 객체를 생성할 수 없다. 추상 메소드가 존재하지 않는 추상 클래스마저도 객체를 생성할 수 없는 것이다. 

 

즉, 추상 클래스는 독립적으로 존재할 수 없기 때문에 상속을 위해서만 존재하며 추상 클래스에서 상속받은 하위 클래스에서는 반드시 추상 메소드를 오버라이딩(Overriding)해야 한다. (abstract 키워드는 클래스와 메소드에서만 사용할 수 있다.)

 

예제)

// 추상 클래스
abstract class SortInt120
{
	private int[] value;

	protected void sort(int[] value)
	{
		this.value = value;
		sorting();
	}

	// 추상 메소드
	protected abstract void sorting();
	// 네가 선호하는 걸로
	// 재정의 해서 써라~

	protected int dataLength()
	{
		return value.length;
	}

	// [final] 키워드로 인해
	// 이 클래스 SortInt120 을 상속받는 클래스에서
	// 이 메소드를 재정의(Method Overriding) 할 수 없다.
	protected final int compare(int i, int j)
	{
		int x = value[i];
		int y = value[j];
		if(x == y)
			return 0;
		else if(x > y)
			return 1;
		else 
			return -1;
	}

	// [final] 키워드로 인해
	// 이 클래스 SortInt120 을 상속받는 클래스에서
	// 이 메소드를 재정의(Method Overriding) 할 수 없다.
	protected final void swap(int i, int j)
	{
		int temp = value[i];
		value[i] = value[j];
		value[j] = temp;
	}
}


// SortInt120 클래스(추상 클래스)를 상속받은 클래스 -> 일단은 추상 클래스
// -> 추상 메소드 [sorting()] 을 Overriding -> 일반 정상 클래스
public class Test120 extends SortInt120
{
	/*
	protected void sort(int[] value)
	{
		this.value = value;
		sorting();
	}

	// 추상 메소드
	protected abstract void sorting();

	protected int dataLength();
	{
		return value.length;
	}

	protected final int compare(int i, int j)
	{
		int x = value[i];
		int y = value[j];
		if(x == y)
			return 0;
		else if(x > y)
			return 1;
		else 
			return -1;
	}

	protected final void swap(int i, int j)
	{
		int temp = value[i];
		value[i] = value[j];
		value[j] = temp;
	}
	*/

	int i, j;
	static int[] data = {7, 10, 3, 8, 7};

	@Override
	protected void sorting()
	{
		// 정의 -> 완성

		for(i = 0; i < dataLength() - 1; ++i)
		{
			for(j = i + 1; j < dataLength(); ++j)
			{
				if(compare(i, j) == 1)
					swap(i, j);
			}
		}
	}

	public static void main(String[] args)
	{
		System.out.print("Source Data : ");
		for(int n : data)
			System.out.print(n + " ");
		System.out.println();

		Test120 ob = new Test120();
		ob.sort(data);

		System.out.print("Sorted Data : ");
		for(int n : data)
			System.out.print(n + " ");
		System.out.println();
	}
}

// 실행결과
/*
Source Data : 7 10 3 8 7
Sorted Data : 3 7 7 8 10
계속하려면 아무 키나 누르십시오 . . .
*/
반응형
Comments