백엔드/JAVA

객체 지향 프로그래밍 이론(4)

두개의 문 2023. 5. 12. 21:54
상속

 

- 상속

· 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것 → 코드의 추가 및 변경 용이함

  ∴ 코드의 재사용성↑, 중복 제거 → 유지보수 쉬워짐

 

☞ 첫번째 상속 예시

- 직접 2개의 클래스 ( ChildClass, ParentClass ) 생성해서 상속에 대해 알아보자

 

< ①  ParentClass >

package inherit;

public class ParentClass {
	// 멤버변수 선언
	String name;
	
	// 기본생성자
	ParentClass(){
		this.name = "자바";
	}

	// 속성함수
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

❶ SuperClass의 멤버 변수 선언

❷ 기본생성자를 통해 변수 초기화

❸ 속성함수를 통해 외부로부터 데이터 보호

 

 

< ② ChildClass >

package inherit;

public class ChildClass extends ParentClass {
	// print() 메서드 추가	
	void print() {
		System.out.println("자식클래스의 이름 : " + this.name);
	}
}

❶ SuperClass를 상속받아보자

- 상속받는 방법

public class ChildClass extends ParentClass { }​

 새로 작성할 클래스이름  extends 상속받고자 하는 클래스이름
   ∴ 두 클래스는 서로 상속관계에 있음
      → 부모 클래스의 모든 멤버를 상속받기 때문에, ChildClass는 ParentClass의 멤버들을 포함함

  

❷ Print( ) 메서드 추가

  → SuperClass의 멤버 이외에 Print( ) 메서드가 추가로 존재

 

왼쪽 : 상속계층도

 

 

< ③ InheritClass ( 실행클래스 ) >

package inherit;

public class InheritApp {

	public static void main(String[] args) {
		// 객체 생성
		ParentClass parentClass = new ParentClass();
		ChildClass childClass = new ChildClass();
		
		// 부모 클래스 이름 출력
		System.out.println("부모클래스의 이름 : " + parentClass.name );
		
		// 부모 클래스 이름 변경 후, 출력
		parentClass.setName("화이팅");
		System.out.println("변경된 부모클래스의 이름 : " + parentClass.getName());
		
     		// 자식 클래스의 print() 메서드 호출
		childClass.print();
        
		// 자식 클래스 이름 변경 후, 출력
		childClass.setName("아자");
		childClass.print();
		
	}
}

❶ 객체 생성

❷ ParentClass의 이름 출력 : 기본생성자를 통해 초기화된 이름이 출력됨

❸ 속성함수를 통해 ParentClass의 이름 변경 후, 다시 출력

❹ ChildClass의 print( ) 메소드 호출 

    → 변수 name이 "자바" ( ParentClass에서 기본생성자를 통해 초기화된 이름 )으로 출력됨

    ∴ 상속을 통해 동일한 내용의 코드를 메모리에 따로 로드시키는 것이 아니라 한번만 로드시킨 후, 공유함

       즉, 자식클래스의 경우 상속받은 멤버를 다시 메모리에 로드시키는 것이 아니라 부모클래스의 시작주소만 가지고 있는 상태

❺ 속성함수를 통해 ChildClass의 이름 변경 후 출력

 

 

< 출력 결과 >

부모클래스의 이름 : 자바
변경된 부모클래스의 이름 : 화이팅
자식클래스의 이름 : 자바
자식클래스의 이름 : 아자

 

▷ 이번 예시를 통해 알 수 있었던 내용

 : 자식클래스에 새로운 코드가 추가되어도 부모클래스에는 아무런 영향을 미치지 않음

 하지만, 부모클래스에서 변경될 경우 자식클래스에서 자동적으로 영향을 받게 됨

 

 ① 자손 클래스는 부모 클래스의 모든 멤버를 상속받음 

     ( 단, 생성자와 초기화 블럭은 상속되지 않음 )

 ② 자손 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많음

( 질문 )
상속의 경우 생성자는 상속되지 않음 → 근데 자식 클래스에서 새로 추가한 print( ) 메서드 호출 시, name은 "자바"로 출력
⇒ 그럼 생성자가 상속되어서 자식클래스 객체 생성 시, "자바"로 초기화된 거 아닌가? 이럴경우, 디버그를 이용해보자.

 

< 디버그 이용 >

 

❶ Resume : 다음 Breakpoint로 넘어감

❷ Step Into : 한 줄씩 지나가며 메소드를 만날 경우에는 그 메소드 안까지 들어감

❸ Step Over : 한 줄씩 지나가며 메소드를 만날 경우에는 따라 들어가지 않고 진행

 

① InheritApp 클래스에서 객체 생성되는 부분

에 Breakpoint 지정 후, Debug 실행

 

② Step Into 클릭 → ParentClass에 있는 생성자 호출됨

 

③ 생성자에 의해 변수 name을 "자바"로 초기화됨

 

④ ChildClass의 객체 생성 → ParentClass의 name 변수를 공유함을 알 수 있음 ( 위치 동일 )

  ∴ 상속 시, 생성자는 상속되지 않음 -> 만약 생성자가 상속이 된다면, 두 클래스의 변수 name의 위치가 같을 수 없음.

 

⑤ ParentClass의 속성함수를 통해 데이터의 값을 변경 후 출력

 

⑥ ChildClass의 속성함수를 통해 데이터의 값 변경 후 출력

 

 

 

☞ 두번째 상속 예시

< ① Book Class >

package inherit;

public class Book {
	// 멤버 변수
	String name;
	String author;
	int price;
	
	// 속성함수
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	
	// 출력 메서드 
	public void print() {
		System.out.println(
				"ParentBook의 이름 : " + this.name +
				"\nParentBook의 저자 : " + this.author +
				"\nParentBook의 가격 : " + this.price);
	}
}

 

< ② ITBook Class >

package inherit;

public class ITBook extends Book {
	// 멤버 변수 선언
	String genre;
	String publisher;
	
	// 속성함수
	public String getGenre() {
		return genre;
	}
	public void setGenre(String genre) {
		this.genre = genre;
	}
	public String getPublisher() {
		return publisher;
	}
	public void setPublisher(String publisher) {
		this.publisher = publisher;
	}
	
	// toString() 메소드 재정의
	@Override
	public String toString() {
		String msg = 
				"ITBook [genre=" + genre + 
				", publisher=" + publisher + 
				", Name=" + this.getName() +
				", Author=" + this.getAuthor() +
				", Price=" + this.getPrice() +
				"]";
		return msg;
	}
}

 

< ③ BookApp Class >

package inherit;

public class BookApp {

	public static void main(String[] args) {
		// 객체 생성
       		Book book = new Book();
		ITBook itBook = new ITBook();
		
        	// 속성함수 이용해 부모클래스 변수의 데이터값 변경
		book.setName("혼자 공부하는 자바");
		book.setAuthor("신용권");
		book.setPrice(20000);
		
       		// 부모클래스 출력
		book.print();
        
       		// 속성함수를 이용해 자식클래스 변수의 데이터값 변경
		itBook.setName("이것이 자바다");
		itBook.setAuthor("신용권");
		itBook.setPrice(30000);
		itBook.setGenre("JAVA");
		itBook.setPublisher("한빛출판사");
		
        	// 자식클래스 출력
		System.out.println(itBook.toString());
	}
}

 

< 출력 결과 >

ParentBook의 이름 : 혼자 공부하는 자바
ParentBook의 저자 : 신용권
ParentBook의 가격 : 20000
ITBook [genre=JAVA, publisher=한빛출판사, Name=이것이 자바다, Author=신용권, Price=30000]

 

 

+ 접근제어자로 접근 제한범위 설정해보기

- 접근 제어자의 목적 : 멤버 또는 클래스에 사용 → 해당 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할
- 접근 범위가 넓은 쪽에서 좁은 쪽의 순
  : public > protected > (default) > private
 ① public : 접근 제한 없음
 ② (default) : 같은 패키지 내에서만 접근 가능 → 접근 제어자가 지정되어 있지 않은 경우에 해당
 ③ protected : 같은 패키지 내 또는 다른 패키지의 자손 클래스에서 접근 가능
 ④ private : 같은 클래스 내에서만 접근 가능