JAVA/정리한 것

[ JAVA ] 객체지향언어의 특징 - 상속성

따갓 2022. 5. 14. 21:22

 객체지향언어의 3대 특징 

1. 상속성 
2. 캡슐화
3. 다형성

 

상속성 - 클래스를 상속받아 클래스를 만든다.

 

class A {
	int data = 0;
	void print(){
		System.out.println( "print" + this.data );	
	}
}

class B extends A{ // extends A 가 있을 때와 없을때 차이 .
	/* A 상속받은 부분
	int data = 0;
	void print(){
		System.out.println( "print" + this.data );
    	}
	>> 이러한 코드가 보이진 않지만 B 내에 존재하는 상황.
	*/	
	
	void print2(){
		System.out.println("print2");
	// >> 조상에서 선언된 함수.변수, B 자체에서 선언된 함수 변수 둘다 이용가능.
		
	}
}
  • class B extends A : "class B 는 class A로부터 상속받았다. A는 B의 조상클래스, B는 A의 자손클래스가 된다."
  • A 클래스에서 선언된 멤버함수와 멤버변수를 옮겨오는 효과이다. 이것을 상속 이라고 한다.

 

만약 A에서 상속받은 함수 or 변수와 똑같은 이름의 함수, 변수가 이미 B에 존재한다면 어떻게 될까?

class A{
	int data = 100;
	void print(){
		System.out.println("A print");
	}
}

class B extends A{
	/*  A상속
	int data = 100;
	void print(){
		System.out.println("A print");
	}
	*/	
	int data = 200;
	void print(){
		System.out.println("B print");
	}
}
public class Main{
	public static void main(String[] args){
	B t = new B();
	t.print();	// 결과 : B print 
	}
}
  • 원래라면 같은 이름의 변수, 함수( 매개변수도 같을 경우 )를 동시에 '선언'이 불가능하다. 근데 위 구조라면 B 클래스 내부에 int data가 두번, void print가 두번 선언된다.
  • 클래스로부터 물려받으면 함수포인터를 물려받게 되는데, 자손에서 똑같이 선언되는 경우에는 새로운 포인터를 생성하지 않고 기존의 물려받은 포인터가 자손에서 선언된 실체( B class의 print() 함수 )를 가리키게 된다
  • 조상에서 선언된 멤버함수를 자손에서 그대로 재정의하면서 갈아엎는 형태method overriding 이라고 한다. ( 멤버변수는 오버라이딩 개념이 없음 ) 
  • 이러한 상속을 통해서 개발자는 비효율적인 코드 중복을 피할 수 있다. 각각의 자식 클래스에 부모 클래스의 자원을 일일히 적어주어야 하는 수고를 덜어주기 때문이다.
  • 또한, 부모 클래스를 한번만 수정하여 자식 클래스의 전체가 수정이 되기 때문에 유지 보수의 편리성도 얻을 수 있다.

 

[ CASE ]

class A{
	int data = 100;
	void print(){ System.out.println(" print "); }
}
class B extends A{
	int data = 200;
	void print(){ System.out.println("printXX"); }
	void print2(){ System.out.println(" print2 "); }
}
public class Main{
	public static void main(String[] args){		
		// case (1)
        B t = new B();
		t.print();
		t.print2();
		System.out.println( t.data );		
		// case(2)
		A t1 = new B();
		t1.print();
		System.out.println( t1.data );
       		// t1.print2();	>>	에러 발생
		
        	//case(3)
		B t2 = (B)t1; 		
		t2.print2();	
		System.out.println( t2.data );
	}
}
  • case(1)의 경우 :  print() . print2() 모두 호출 가능하다. 이 때 print()의 경우 오버라이딩되서 "print" 가 아닌 "printXX"가 출력된다. t.data를 출력하면 class B 에 있는 data 값인 200이 출력된다.
  • case(2)의 경우 : 조상(A)에서 선언된 함수( print )만 호출가능하고 자손(B) 에서 선언된 함수 ( print2 )는 호출 불가능하다. t1.data를 출력하면 class A 에 있는 data값인 100 이 출력된다.
  • case(3)의 경우 : B t2 = (B)t1;  >> A클래스형 t를 B클래스형으로 강제형변환(캐스팅) 시킨다.  이건 아무때나 다 되는게 아니라 t1이 가리키고 있는 B의 인스턴스가 존재해야 성립된다. 즉 처음에 A t1 == new A(); 이렇게 선언 되었다면 이 코드는 실행불가하다.
  • 조상형 변수로 자손 클래스의 인스턴스를 가리킬 수 있다.(처럼 보인다)>> 실제로는 여전히 자손안의 '조상'을 가리키는 것이다.
  • 왜 이런 형태로 선언하는가? 그 이유는 자손B 의 것으로 오버라이딩 하면서 A 의 멤버변수를 이용해야할 때 이다.
  • 메서드 오버라이딩은 객체지향언어 3대 특징 중 하나인 다형성에 속하기도 한다.

[ CASE ]

class A{
	A(){     // A의 생성자함수
		System.out.println("A Constructor");
	}	
}
class B extends A{
	B(){     // B의 생성자함수
		System.out.println("B Constructor");
	}	
}
public class Main{
	public static void main(String[] args){
		new B();
		// t.B();  >> 생성자는 멤버함수 아니므로 이렇게 호출할 수 없다.
	}
}

위 코드를 실행하면 다음과 같은 결과가 나온다.

더보기

A Constructor
B Constructor

  • 자손의 인스턴스를 생성하면 "조상의 생성자함수"부터 "자손의 생성자함수"가 차례로 호출된다.
  • 그럼 생성자도 '상속'되는 개념인가? >> 생성자는 일단 멤버함수가 아니므로 상속되지 않는다. 포인터를 통해 접근도 안된다. 다만 인스턴스 생성시 호출될 뿐이다.