본문 바로가기
Back-End/Java

[Java] 상속(inheritance)

by 찐코딩 2021. 8. 17.

상속(Inheritance)

- 객체지향 프로그램 4대 특징 중 하나
- 실세계에서의 상속은 상위 개체의 속성이 하위 개체에 물려져서 하위 개체가 상위 개체의 속성을 모두 가지는 개념임
- 자바의 상속은 자식클래스가 부모클래스의 속성을 돌려 받고 기능을 추가하여 확장(extends) 하는 개념임
- 상속은 슈퍼클래스의 필드와 메소드를 흘려 받아서 코드를 재사용함으로써,

   코드 작성에 드는 시간과 비용을 줄일 수 있음
- 기존에 이미 잘 만들어진 클래스를 재사용하여 즉, 특정 클래스를 만들 때

  기존의 클래스의 데이터(속성)과 기능(메소드)를 그대로 돌려받아 중복적인 코드를 없애줌
 (클래스의 재사용, 코드의 중복성을 없애줌)
- 자식과 부모의 관계로 형성되어 있음
  ==> 부모클래스 : super, parent 클래스라고 하기도 함
  ==> 자식클래스 : sub, child 클래스라고 하기도 함
- 자식은 부모의 멤버보다 같거나 많다 

- 상속의 대상은 멤버(멤버변수, 멤버메소드)

   ==> 단, 생성자나 private 접근 제한을 갖는 멤버변수와 멤버메소드는 상속에서 제외

- 상속 시 사용되는 키워드 : extends

- 자바에서 상속은 단일 상속만 가능함

   ==> 즉, 상속을 받을 수 있는 클래스는 하나이다.

 

- 상속의 장점은 클래스의 수정을 최소화시킬 수 있는 장점이 있음

   또한, 유지보수의 시간을 최소화 시켜준다는 장점이 있음

 

 

상속관계에서 쓰이는 키워드 2가지 => super 키워드, this 키워드

super 키워드

- 부모의 멤버를 호출하는 명령어
- 형식)
super.부모클래스멤버(멤버변수, 멤버메소드)

 

this 키워드

- 현재 클래스에 소속된 멤버를 호출하는 명령어
- this 키워드는 현재 객체 자기 자신을 의미함
- 형식) this.자식클래스멤버(멤버변수, 멤버 메소드)

https://jinnnkcoding.tistory.com/48

 

[Java] 스코프와 this

https://jinnnkcoding.tistory.com/32 [Java] 접근지정자(접근제어자) 권한 접근지정자(접근제어자)권한 - 접근지정자는 클래스, 멤버변수, 멤버메서드 앞에 사용됨. - 외부로부터 접근을 제어한다는 의미를

jinnnkcoding.tistory.com

그렇다면 왜 사용하는가?

자식 클래스의 인스턴스를 생성하면, 자식의 멤버와 부모의 멤버가 모두 합쳐진 하나의 인스턴스가 생성됨.
그래서 자식 클래스의 인스턴스가 부모 클래스의 멤버들을 사용할 수있음.

자식 객체를 생성과 동시에 초기화 하려면, 부모 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에,

자식 클래스의 생성자의 첫줄에 부모 클래스의 생성자가 호출되어야 함. = super 키워드 사용

그 이유는 자식 클래스의 멤버가 부모 클래스의 멤버를 사용할 수도 있으므로 부모의 멤버들이 먼저 초기화되어 있어야 하기 때문.

만약, 부모클래스의 생성자를 호출 하지 않을 경우 컴파일러는 자동으로 super(); 을 삽입한다.

 

 

package inheritance;

public class Ex04_Point {

	public static void main(String[] args) {
		
		// 객체 생성 및 초기화
		Point3D point3d = new Point3D(2, 3, 5);
		
		}
}

//부모클래스
class Point {   
	//멤버변수
	int x;
	int y;
	
	Point(int x, int y) {	 // 인자 생성자
			this.x = x;
			this.y = y;
		}
	
	String getLocation() {
		return "X :" + x + ", y : " + y;
	}
}


// 자식 클래스
class Point3D extends Point {

	int z;
	
	Point3D(int x, int y, int z) { 	// 자식 클래스의 인자 생성자 생성
		this.x = x;
		this.y = y;// 
		this.z = z;	
	}
	
	@Override		// 오버라이딩
	String getLocation() {
		return "X :" + x + ", y : " + y + ", z : " + z ;
	}
	
}

결과

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	Implicit super constructor Point() is undefined. Must explicitly invoke another constructor

	at inheritance.Point3D.<init>(Ex04_Point.java:35)
	at inheritance.Ex04_Point.main(Ex04_Point.java:8)

자식 클래스 생성자에서 부모클래스의 생성자인 Point를 찾을 수 없다는 오류가 발생했다.

Point3D클래스의 생성자의 첫 줄이 생성자(부모의 것이던 자신의 것이던) 호출하는 문장이 아니기 때문에

컴파일러는 자동으로! super();를 Point3D클래스의 생성자 첫 줄에 넣는다

Point3D(int x, int y, int z) {
		super();
		this.x = x;
		this.y = y;// 부모클래스의 인자 생성자를 호출하는 명령어
		this.z = z;		// 자식 클래스의 인자 생성자 생성
	}

이렇게 말이다.

 

그래서 Point3D클래스의 인스턴스를 생성하면 셍성자 Point3D(int x, int y, int z)가 호출되면서

첫 문장인 super();를 수행하게 된다.

super()는 Point3D클래스의 부모인 Point클래스의 기본 생성자인 Point()를 뜻하므로, Point()가 호출된다.

그러나 Point클래스에 생성자 Point()가 정의되어있지 않기 때문에, 위와 같은 컴파일 에러가 발생한 것이다.

이 에러를 수정하려면, Point클래스에 생성자 Point()를 추가해주던가,

셍성자 Point3D(int x, int y, int z)의 첫 줄에서 Point(int x, int y)를 호출 할 수 있도록 변경해야 한다.

 

// 자식 클래스
class Point3D extends Point {

	int z;
	
	Point3D(int x, int y, int z) {
		super(x, y);	// 부모클래스의 인자 생성자 Point(int x, int y)를 호출
		this.z = z;		// 자식 클래스의 인자 생성자 생성
	}
	
	@Override		// 오버라이딩
	String getLocation() {
		return "X :" + x + ", y : " + y + ", z : " + z ;
	}
	
}

자식 클래스를 위와 같이 수정했더니 아래와 같이 문제없이 컴파일 된 것을 볼 수 있다.

x 좌표 >>> 2
y 좌표 >>> 3
z 좌표 >>> 5

 


상속 예제01)

car라는 부모 클래스를 생성하고, 이를 상속받아 소나타 라는 자식 클래스를 생성해보자

package inheritance;

public class Ex01_Car {

	public static void main(String[] args) {
		
		// Sonata 클래스의 객체 생성
		
		/*
		 * Sonata 클래스의 객체 생성 과정
		 * Sonata 클래스의 객체 생성 시 JVM이 우선적으로 Car 부모 클래스를 객체 생성한 후에
		 * Sonata 클래스의 객체를 생성한다.
		 */
		Sonata sonata = new Sonata();
        
        // 객체 초기화
		sonata.model = "소나타";
		sonata.cc = 2000;
		sonata.door = 4;
		
        // 정보 출력
		sonata.output();
	}
}


// 부모클래스
class Car {
	
	// 멤버변수
		int cc;  // 차량 배기량
		int door; // 차량 문짝 수
	}

// 자식클래스
class Sonata extends Car {

	//멤버변수
	//int cc;  
	//int door; ==> 상속을 받으면 부모클래스의 멤버는 생략
	String model; //차량 모델명 변수 추가

	// 멤버 메소드 추가
	void output() {
		System.out.println("모델명 : " + model);
		System.out.println("배기량 : " + cc);
		System.out.println("문짝수 : " + door);
	}

}

결과

모델명 : 소나타
배기량 : 2000
문짝수 : 4

 

상속 예제02)

만일 부모 클래스의 멤버변수 값을 자식 클래스에서 바꾼다면 결과는 어떻게 출력될까?

package inheritance;


class Car2 {
	// 멤버변수
	int cc;  // 차량 배기량
	int door; // 차량 문짝 수
	String color = "검정색";
}


class Avante extends Car2 {
	
	//멤버변수
	String color="흰색"; // 부모의 멤버변수값 변경
	
	
	//멤버 메소드
	void output() {
		System.out.println
		("엔진 : "+ cc + ", 문짝수 : " + door + ", 색상 : " + color);
		System.out.println
		("엔진 : "+ cc + ", 문짝수 : " + door + ", 색상 : " + super.color);  // super:부모의 멤버를 호출하는 명령어
		System.out.println
		("엔진 : "+ cc + ", 문짝수 : " + door + ", 색상 : " + this.color); // this:현재 클래스에 소속된 멤버를 호출하는 명령어
	}		
}


public class Ex02_Car {

	public static void main(String[] args) {
		
		// 객체 생성
		Avante avante = new Avante();
		
		// 객체 초기화
		avante.cc = 1600;
		avante.door = 4;
		
		// 정보 출력
		avante.output();

	}

}

결과

엔진 : 1600, 문짝수 : 4, 색상 : 흰색
엔진 : 1600, 문짝수 : 4, 색상 : 검정색
엔진 : 1600, 문짝수 : 4, 색상 : 흰색

결과창의 첫번째 출력문을 보면

부모의 멤버 변수값이 아닌 자식의 멤버값이 출력된 것을 볼 수 있다.

그렇다고 부모의 멤버값도 바뀌었는가? 두번째 문장을 보면 그렇지 않음을 볼 수 있다.

(super.color로 부모의 멤버변수 값을 호출함)

마지막 this.color를 호출하여 자식 멤버값이 성공적으로 변경되었음을 볼 수 있다.

즉, 자식의 멤버가 부모의 멤버보다 우선순위가 높다. 라는 것을 알 수 있는 예문이다.

 

상속 예제03)

하나의 부모클래스에서 자식 클래스를 여러개 생성

package inheritance;

import java.util.Scanner;


// 부모 클래스
class Volume {
	
	// 멤버변수
	int vol = 1;
	
	public void setVolume(int vol) {
		this.vol = vol;
	}
	
	public int getVolume() {
		return vol;
	}
	
	// 볼륨을  올리는 메서드
	void volumeUp() {
		vol++;
		if(vol > 15) {
			vol = 15;
		}
	}
	
	// 볼륨을 내리는 메서드
	void volumeDown() {
		vol--;
		if(vol < 1) {
			vol = 1;
		}
	}
}


class TV extends Volume {} //TV 자식 클래스

class Computer extends Volume {} //Computer 자식 클래스

class Radio extends Volume {} //Radio 자식 클래스


public class Ex05_Remote {

	public static void main(String[] args) {
		
		Scanner sc = new Scanner(System.in);
		
		TV tv = new TV();
		Computer computer = new Computer();
		Radio radio = new Radio();
		
		while(true) {   // 무한 반복
			System.out.print
				("1. TV / 2. Radio / 3. Computer / 4. 종료 : ");
			int menu = sc.nextInt();
			
			if(menu == 4) {	// 종료 선택 시 반복문 탈출
				break;	}	
			
			System.out.print("1. volumeUp / 2. volumeDown : ");
			int volume = sc.nextInt();
			
			switch(menu) {
				case 1 :  // TV를 선택한 경우
					if(volume == 1) {  // 볼륨Up을 선택한 경우
						tv.volumeUp(); }
					else {            // 볼륨Down을 선택한 경우
						tv.volumeDown(); }
					break;
					
				case 2 : // Radio를 선택한 경우
					if(volume == 1) {  // 볼륨Up을 선택한 경우
						radio.volumeUp(); }
					else {            // 볼륨Down을 선택한 경우
						radio.volumeDown();	}
					break;
					
				case 3 : // Computer를 선택한 경우
					if(volume == 1) {  // 볼륨Up을 선택한 경우
						computer.volumeUp(); }
					else {            // 볼륨Down을 선택한 경우
						computer.volumeDown(); }
					break;
			}  // switch ~ case 문 end
			
			System.out.println(":::::::::::::::::::::::::::::::::::");
			
			System.out.println
				("TV volume : "+tv.getVolume()+" / Radio volume : "+radio.getVolume()
				+" / Computer volume : "+computer.getVolume());
			
			System.out.println();
		}  // while문 end
		
		System.out.println();
		
		// 반복문 탈출 시 수행
		System.out.println("프로그램이 종료되었습니다.");
		
		sc.close();

	}

}

결과

1. TV / 2. Radio / 3. Computer / 4. 종료 : 2
1. volumeUp / 2. volumeDown : 1
:::::::::::::::::::::::::::::::::::
TV volume : 1 / Radio volume : 2 / Computer volume : 1

1. TV / 2. Radio / 3. Computer / 4. 종료 : 1
1. volumeUp / 2. volumeDown : 1
:::::::::::::::::::::::::::::::::::
TV volume : 2 / Radio volume : 2 / Computer volume : 1

1. TV / 2. Radio / 3. Computer / 4. 종료 : 4

프로그램이 종료되었습니다.

 

댓글