BackEnd/Java

[Java]상속(Inheritance)

Hojung7 2024. 7. 31. 06:20

1. 상속의 정의

 

다른 클래스가 가지고 있는 멤버(필드, 메서드)들을 새로 작성할 클래스에서 직접 만들지 않고
상속을 받음으로써 새 클래스가 자신의 멤버처럼 사용할 수 있는 기능

 

1) 목적

 

클래스의 재사용, 연관된 일련의 클래스들에 대한 공통적인 규약 정의

 

2) 방법

 

클래스 간의 상속 시에는 extends 키워드 사용

 

[표현식]

public class Academy extends Company {}
              자식             부모

 

3) 장점

 

- 코드 길이 감소
    → 부모 필드/메서드를 자식이 안써도 사용 가능
   
 - 재사용성 증가
    →  super(), super 참조변수를 이용해
      자식이 부모코드를 호출해서 자식 코드 길이 감소
     
  - 공통적인 코드 관리, 유지 보수성 증가
     →  부모의 코드만 수정해도
       부모 코드를 활용하던 자식이 코드도 일괄 수정됨
      
   - 자식에게 상속하는 기능의 이름을 강제할 수 있음
→  오버라이딩 시 부모의 메서드명과 동일해야 한다!

 

 

오버라이딩( Overrriding) 오버로딩(Overloading)
하위 클래스에서 메서드 정의 같은 클래스에서 메서드 정의
메서드 이름 동일

매개변수 동일(개수, 타입, 순서)

리턴 타입 동일
메서드 이름 동일
매개변수 다름(개수, 타입, 순서)
리턴 타입 상관없음
자식 메서드의 접근 범위가 
부모 메서드의 접근 범위보다 넓거나 같아야 함
접근 제어자와 상관 없음
자식 메서드의 예외 수가
부모 메서드의 예외 수보다 적거나 
범위가 좁아야 함
예외처리와 상관없음

 

2. Object

- 모든 클래스(객체)의 최상위 부모

 

 - 모든 클래스(객체)가 공통적으로 가져야 하는 기능이 모여있음

 

 - 클래스 선언부에 아무런 상속 구문(extends)이 작성되어 있지 않으면

  컴파일러가 extends Object 구문을 추가해준다!!

 

3. final 메서드

 

 - 오버라이딩 불가

 

 → 부모의 메서드가 완벽해서 또는 재정의하면 문제가 되서

 더이상 오버라이딩을 못하게 하겠다

 (마지막으로 정의된 메서드)

 

 public final 반환형 메서드명(){}

 

 

#예제

[부모 역할 클래스]

package dto;

/*Object 클래스
 * - 모든 클래스(객체)의 최상위 부모
 * - 모든 클래스(객체)가 공통적으로 가져야 하는 기능이 모여있음
 * 
 * - 클래스 선언부에 아무런 상속 구문(extends)이 
 * 	  작성되어 있지 않으면
 *   컴파일러가 extends Object 구문을 추가해준다!!
 * 
 */



/**
 * 부모 역할 클래스
 */
public class Parent extends Object {
	
	// 부모 필드
	// protected 접근 제한자 
	// - 같은 패키지  : 객체 생성 후 사용
	// ex ) 같은 패키지 클래스에서
	//			Parent p = new Parent();  p.lastName;
	// - 상속 관계 자식 클래스 : 자식 클래스에서 직접 사용
	// ex) 자식 클래스에서 lastName; 작성
	protected String lastName = "홍"; // 명시적 초기화
	
	 private String address = "서울시 중구 남대문로 120";
	 
	  private int money = 100_000_000; // _ : 자릿수 구분(해석 X)
	  
	  private String car = "그랜져";
	  
	  // 기본 생성자
	  public Parent() {
		  System.out.println("부모 - 기본 생성자로 객체 생성");
	  }

	  // 매개 변수 생성자(Alt + shift + s -> o)
	public Parent(String address, int money, String car) {

		this.address = address;
		this.money = money;
		this.car = car;
		
		System.out.println("부모 - 매개 변수 생성자로 객체 생성");
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public int getMoney() {
		return money;
	}

	public void setMoney(int money) {
		this.money = money;
	}

	public String getCar() {
		return car;
	}

	public void setCar(String car) {
		this.car = car;
	}
	  
	/**
	 * 현재 객체의 필드 값을 하나의 문자열로 반환하는 메서드
	 * @return
	 */
	public  String inform() {
		return String.format("%s / %s / %d / %s", 
							   lastName, address, money, car);
	}

 

[자식 클래스]

package dto;

/* 상속 : 부모의 코드(필드/메서드)를 물려 받아
 *        자식이 자신의 코드인 것 처럼 사용
 * 
 * 상속 키워드 : extends (확장하다)
 *   -> 부모 코드를 물려 받은 자식의 크기가 커지기 때문에!
 *   
 * ** 주의 사항 **
 * - 부모의 코드 중 생성자는 물려받지 못함!
 * 
 * - 부모의 private 접근 제한자 필드/메서드는
 *   상속 받긴 하지만 직접 접근 불가!!!
 *   -> 상속 관계여도 다른 클래스(객체)로 인식 되기 때문에 
 *      접근 불가
 */

public class Child1 extends Parent{
	
	// 필드 
	private String notebook;
	
	/* super() 생성자
	 * - super 뜻 : 상위의, 위쪽의
	 * 
	 *  - 현재 클래스의 상위클래스(부모) 생성자를 호출하는 구문 
	 * 
	 * - super()는 자식 생성에만 작성할 수 있으며
	 *   반드시 첫 번째 줄에 작성해야 한다!!
	 * 
	 */
	
	// 기본 생성자
	public Child1() {
		super();
		System.out.println("자식1 - 기본 생성자로 객체 생성");
	} 
	
	
	// 매개 변수 생성자
	public Child1(String notebook) {
		
		// 부모 매개 변수 생성자 호출하기
		super("부산 해운대구", 123456789, "모하비");
		this.notebook = notebook;
		System.out.println("자식1 - 매개 변수 생성자로 객체 생성");
	}
	
	
	// getter/setter
	public String getNotebook() {
		return notebook;
	}
	
	public void setNotebook(String notebook) {
		this.notebook = notebook;
	}
	
	 /* 
	 * 자식 객체 + 부모 객체에 있는 모든 필드 값 출력
	 */
	public void print() {
		
		// 부모 필드
		// protected 필드는 상속 받은 클래스에서 직접 접근 가능!!
		System.out.println("lastName : " + lastName);
		
		// private 필드간접 접근
		System.out.println("address : " + getAddress());
		System.out.println("money : " + getMoney() );
		System.out.println("car : " + getCar() ); //  private 필드를 간접 접근
		
		// 자식 필드
		System.out.println("notebook : " + notebook);
		
	}
	
	/* super 참조 변수
	 * -  자식 객체 내부의 부모 객체를 참조하는 변수
	 * 
	 * - 언제 사용해??
	 * 	-> 부모의 필드/메서드임을 확실하게 명시할 때 주로 사용
	 * 
	 */
	
	/** 자식 + 부모 필드 정보를 하나의 문자열로 반환
	 * -> 성 / 주소 / 돈 / 차 / 노트북
	 * 
	 */
	public String childInform() {
		
	//	 return String.format("%s / %s / %d / %s / %s", 
	//			   lastName, getAddress(), getMoney(), getCar(), notebook);
		
		return super. inform() + "/" + notebook;
		
	}
	
	/* 오버라이딩(Overriding) : 덮어 쓰기 / 재정의
	 * Over   : 위에 있다, 넘어서다
	 * Riding : 타는 것
	 * 
	 * - 부모로 부터 물려받은 메서드를 
	 *   자식이 같은 이름의 메서드를 만들어서 덮어 쓰기
	 *   -> 물려 받은 자식에 맞게
	 *   	부모의 메서드를 다시 정의해아 되는 경우 사용
	 *   
	 *   ** 중요 **
	 *   
	 *   @Override 어노테이션(Annotation)
	 *   1) 해당 메서드는 오버라이딩 되었음을 명시
	 *   
	 *   2) 오버라이딩된 코드가 알맞게 작성되었는지 
	 *   	컴파일러에게 검사하라고 지시하는
	 *    	컴파일러 주석
	 *    
	 * [오버라이딩 성립 조건] 
	 * 1. 접근 제한자는 같거나 더 넓은 범위 
	 * 2. 반환형, 매개 변수, 메서드명은 모두 동일
	 * 3. 예외처리 구문(throws)은 같거나 좁은 범위
	 * 
	 * (쉽게 말해서 부모랑 똑같이 쓰는 게 제일 쉬움)
	 */
	
	// 컴파일러야 이거 잘 쓴건지 검사해줘
	@Override
	public void introduce() {
		System.out.println("저는 Parent의 자식 Child1 입니다");
	}
	
	// Parent에게 상속 받은 toString() 오버라이딩
	@Override
	public String toString() {
		
		// Child1 클래스 내부에서 toString() 호출하면
		// Child1.toString()이 호출됨
		
		// super.toString() : 부모 클래스에 작성된 toString() 호출
		// -> 오버라이딩 했다고 해서
		// 	부모의 메서드가 덮어쓰기 되어 사라지는 것이 아니다!!
		
		return super.toString() + "/" + notebook;
	}
	
	// final 메서드 확인하기 -> 오버라이딩 시도
    //	@Override
    //	public final void onlyParent() {
	// Cannot override the final method from Parent
	// -> 오버라이딩 불가 에러
	}

 

[ Run]

package run;

import dto.Child1;
import dto.Parent;

public class TestRun1 {
	public static void main(String[] args) {
	
		// 부모 객체 생성
		Parent p1 = new Parent(); // 기본 생성자
		Parent p2 = new Parent("서울시 강남구 역삼동", 300, "페라리");

 

#결과

 

[Run]

	// 자식 객체 생성
		Child1 c1 = new Child1();
		
		/* 자식이 부모 코드를 물려 받았는지 확인1 */
		
		System.out.println(c1.inform());

 

#결과

 

[Run]

	c1.setCar("테슬라 모델 X");
		System.out.println(c1.getCar());
		
		c1.setAddress("경기도 부천시");
		c1.setMoney(Integer.MAX_VALUE);
		
		System.out.println(c1.inform());

#결과

 

[Run]

System.out.println("[자식 매개 변수 생성자로 객체 생성]");
		
		Child1 c2 = new Child1("맥북 m3 프로");
	
		System.out.println(c2.inform() + "/" + c2.getNotebook());

 

#결과

 

[Run]

		System.out.println("[protected] 확인");
		c2.print();

 

#결과

 

[Run]

	System.out.println("[super 참조 변수 확인]");
		
		System.out.println(c2.childInform());
	}

 

#결과

 

[Run] 오버라이딩 결과

package run;

import dto.Child1;
import dto.Parent;

public class TestRun2 {
	public static void main(String[] args) {
		
		// 부모 객체 생성
		Parent p1 = new Parent();
		
		p1.introduce();
		
		System.out.println("-------------------------------------------");
		// 자식 객체 생성
		Child1 c1 = new Child1(); // 내부에 Parent 객체 존재
		c1.introduce();

 

#결과

 

[Run] Object 오버라이딩 확인

System.out.println("[Object.toString() 오버라이딩 확인]");
		System.out.println(p1.toString());
		System.out.println(p1);  // 참조 변수명만 작성해도
                                       // -> toString() 자동 호출

 

#결과

 

[Run] Parent  오버라이딩 확인

System.out.println("[Parent.toString() 오버라이딩 확인]");
		
		c1.setNotebook("LG Gram");
		System.out.println(c1);

 

#결과