자바의 정석

상속

야생늑대 2022. 5. 23. 16:03
반응형

▶객체 지향 프로그래밍에서 부모클래스의 멤버를 자식클래스에게 물려줄 수 있다.

부모클래스 - 상위클래스

자식클래스 - 하위클래스 or 파생클래스

  • 상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여준다.
  • 상속을 이용하면 부모 클래스의 수정으로 모든 자식클래스들도 수정되는 효과를 가져오기 때문에 유지 보수 시간을 최소화할 수 있다.

클래스 상속

▶ 자식클래스가 부모클래스를 선택하여 상속받을 수 있다.

▶ 상속방법

class 자식클래스 extends 부모클래스 {
          	  //필드
                   //생성자
                    //메소드
}

1. 자바는 다중 상속을 허용하지 않아 여러개의 부모클래스를 상속할 수 없다. (단 하나의 부모클래스만 사용할 수 있다.)

2. 부모클래스에 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.

3. 부모클래스와 자식클래스가 서로 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외된다.

 

↓ 예제

더보기

▶ 부모클래스  CellPhone.java

package exam01;

public class CellPhone {
	
	//필드
	String model;
	String color;
	
	
	//생성자
	
	
	//메소드
	void powerOn() {System.out.println("전원을 켭니다.");}
	void powerOff() {System.out.println("전원을 끕니다.");}
	void bell() {System.out.println("벨이 울립니다.");}
	void sendVoice(String message) {System.out.println("자기 : "+ message);}
	void receiveVoice(String message) {System.out.println("상대방 : "+ message);}
	void hangUp() {System.out.println("전화를 끊습니다.");}
	
}

 자식클래스  DmbCellPhone.java

package exam01;

public class DmbCellPhone extends CellPhone {

	//필드
	int channel;
	
	
	//생성자
	
	DmbCellPhone(String model, String color, int channel){
		this.model = model;
		this.color = color;
		this.channel = channel;
	}
	
	//메소드
	void turnOnDmb() {
		System.out.println("채널" + channel + "번 DMB 방송 수신을 시작합니다.");
	}
	void changeChannelDmb(int channel) {
		this.channel = channel;
		System.out.println("채널" + channel + "번으로 바꿉니다.");
	}
	void turnOffDmb() {
		System.out.println("DMB 방송 수신을 멈춥니다.");
	}
	
	
}

▶ 자식클래스 사용 (객체생성)  DmbCellPhoneExample.java

package exam01;

public class DmbCellPhoneExample {

	public static void main(String[] args) {
		//DmbCellPhone 객체 생성
		DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰", "검정", 10);
		
		//CellPhone 클래스로부터 상속받은 필드
		System.out.println("모델 : " + dmbCellPhone.model);
		System.out.println("모델 : " + dmbCellPhone.color);
		
		//DmbCellPhone 클래스의 필드
		System.out.println("채널 : "+ dmbCellPhone.channel);
		
		//CellPhone 클래스로부터 상속받은 메소드 호출
		dmbCellPhone.powerOn();
		dmbCellPhone.bell();
		dmbCellPhone.sendVoice("여보세요");
		dmbCellPhone.receiveVoice("안녕하세요! 저는 홍길동인데요.");
		dmbCellPhone.sendVoice("아~예 반갑습니다.");
		dmbCellPhone.hangUp();
		
		//DmbCellPhone 클래스의 메소드 호출
		dmbCellPhone.turnOnDmb();
		dmbCellPhone.changeChannelDmb(12);
		dmbCellPhone.turnOffDmb();
	}

}
  • 자바에서 자식클래스의 객체를 생성하면, 상속하고 있는 부모클래스의 객체가 먼저 생성되고 그 다음에 자식객체가 생성된다. 
  • 다음 코드는 DmbCellPhone 객체만 생성하는 것처럼 보이지만. 내부적으로는 부모인 CellPhone 객체가 먼저 생성되고 DmbCellPhone 객체가 생성된다.
DmbCellPhone dmbCellPhone = new DmbCellPhone();

 


▶ 상속받지 않은 상태라서 필드를 찾지못하는 상태.

▶ CellPhone 클래스를 상속 받아서 CellPhone의 필드를 사용할 수 있다.


※ 모든 객체는 클래스의 생성자를 호출해야만 생성된다. 부모생성자는 자식생성자의 맨 첫줄에서 호출된다.

▶ 자식클래스에 생성자가 명시적으로 선언되지 않았다면 컴파일러는 기본생성자를 다음과 같이 자동 생성한다.

public DmbCellPhone(){
		super();
}
  • 첫줄에 작성된 super()는 부모의 기본생성자를 호출한다.
  • 위 쪽에 있는 CellPhone 클래스도 생성자가 선언되어 있지 않지만 컴파일러에 의해 자동 생성되므로 문제없이 실행된다.
  • 컴퍼일러 시 다음과 같이 자동생성된다.
public CellPhone(){
}

▶ 직접 생성자 를 선언하고 명시적으로 부모생성자 호출

자식클래스(매개변수선언, ...) {
	super (매개값, ... ) ;
}
  • super(매개값, ... )는 매개값의 타입과 일치하는 부모생성자를 호출한다. 이 떄 매개값의 타입과 일치하는 부모생성자가 없을 경우 컴파일 에러가 발생한다.
  • super(매개값, ... )가 생략되면 super() <-(기본생성자) 가 자동적으로 추가되기 때문에 부모클래스에 기본생성자가 존재해야 한다.
  • 부모클래스에 기본생성자가 없고 매개변수가 있는 생성자만 존재한다면 자식생성자에서 반드시 부모생성자 호출을 위해 super(매개값, ...)를 명시적으로 호출해야 한다. 
  • super(매가값, ...)는 반드시 생성자 블록의 첫줄에 위치해야 한다. 그렇지 않으면 컴파일에러가 발생한다.

↓ 예제

더보기

People.java

package exam01;

public class People {
	
		public String name;
		public String ssn;
		
		public People(String name, String ssn) {
			this.name = name;
			this.ssn = ssn;
		}	
}

Student.java

package exam01;

public class Student extends People{
	public int studentNo;
	
	public Student(String name, String ssn, int studentNo) {
		super(name, ssn);
		this.studentNo = studentNo;		
	}					
}
  • People클래스는 기본생성자가 없고 name, ssn을 매개값으로 받아 객체를 생성시키는 생성자만 있다.
  • 그렇기 때문에 상속받고 있는 Student 클래스는 생성자에서 super(name, ssn)으로 People 클래스의 생성자를 호출해야 한다.
  • 기본생성자가 없기 때문에 super()을 생략하거나 기본생성자를 사용하면  컴파일 에러가 발생한다.

※ Implicit super constructor People() is undefined. Must explicitly invoke another constructor

Implicit super constructor People() is undefined. Must explicitly invoke another constructor

▶ 자식 객체 이용   

 StudentExample.java

package exam01;

public class StudentExample {

	public static void main(String[] args) {

		Student student = new Student("홍길동", "123456-1234567", 1);
		System.out.println("name : " + student.name);
		System.out.println("ssn : " + student.ssn);
		System.out.println("studentNo : " + student.studentNo);		
	}
}

 


 

 

메소드 재정의란? ( 오버라이딩 : Overriding )

  • 자식클래스에서 부모클래스의 메소드를 다시 정의하는 것.
  • 부모클래스를 상속할 때 모든 메소드가 자식클래스에 맞게 설정되어 있을 수 는 없다. 그럴 때 일부메소드를 자식 클래스에 맞게 다시 수정해서 사용해야하는데 자바는 이런경우를 위해 메소드 재정의 기능을 제공한다.

메소드 재정의 방법

  • 부모의 메소드와 동일한 시그니처( 리턴타입, 메소드 이름 , 매개 변수 목록)를 가져야 한다.
  • 새로운 예외(Exception)를 throws할 수 없다.
  • 접근제한을 더 강하게 재정의 할수 없다.
public   -->  default, private    (X) 불가능
 부모               자식
default  -->  default, public     (O) 가능
 부모               자식

자식클래스에서 메소드가 재정의되면 부모객체의 재정의가 된 메소드는 숨겨지기 때문에 메소드를 호출하면 재정의가 된 자식메소드가 호출된다.

 

↓ 예제 

더보기

Calculator.java

package exam01;

public class Calculator {

	double areaCircle(double r) {
		System.out.println("Calcurator 객체의 areaCircle() 실행");
		return 3.14159 *r * r;
		}
}

Computer.java

package exam01;

public class Computer extends Calculator {
	
	@Override
	double areaCircle(double r ) {
		System.out.println("Computer 객체의 areaCicle() 실행");
		return Math.PI * r * r;              //  <---재정의  3.14159 --> Math.PI
	}	
}

 

  • Calcurator의 areaCircle() 메소드는 파이의 값을 3.14159로 계산하였지만, 좀 더 정밀 한 계산을 위해 Computer의 areaCicle()메소드는 Math.PI 상수를 시용했다.
  • Math는 수학 계산과 관련된 필드와 메소드들을 가지고있는 클래스로, 자바 표준 API이다.
  • Computer에서 @Override 어노테이션은 생략해도 좋으나, 어노테이션을 붙여줘야 컴파일러가 이 메소드가 재정의 된것인지 확인하기때문에 개발자의 실수를 줄일 수 있다.
  • 예를 들어, @Override 어느테이션을 작성하고 areaCircl() 처럼 e를 빼먹으면 재정의할 메소드와 명칭이 다르기 때문에 컴파일에러가 발생한다. 

ComputerExample

package exam01;

public class ComputerExample {
	public static void main(String[] args) {

		int r = 10;
		
		Calculator calculator = new Calculator();
		System.out.println("원면적 : " + calculator.areaCircle(r)); 
		System.out.println();
		Computer computer = new Computer();
		System.out.println("원면적 : " + computer.areaCircle(r));//재정의된 메소드 호출
		
	}
}


※이클립스 재정의메소드 자동생성

  • 이클립스는 부모 메소드 중 하나를 선택해서 재정의 메소드를 자동 생성해주는 기능이 있다.
  • 이 기능은 부모 메소드의 시그니처를 정확히 모를 경우 매우 유용하게 사용할 수 있다.
  1.  자식클래스에서 재정의 메소드를 작성할 위치로 커서를 옮긴다.
  2.  [Source] - [Override/Implement Methods] 메뉴를 선택한다.
  3.  부모 클래스에서 재정의될 메소드를 선택하고 [OK] 버튼을 클릭한다


▶ 자식클래스에서 부모클래스의 메소드를 재정의하게 되면, 부모 클래스의 메소드는 숨겨지고 재정의 된 자식 메소드만 사용된다. 그러나 자식 클래스 내부에서 재정의 된 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면 명시적으로 super 키워드를 붙여서 부모 메소드를 호출할 수 있다.

 

super.부모메소드();
  //부모클래스
class Parent{ 
	void method1() { }
    void method2() { }
}

//자식클래스
class Child extends parent {
	void method2() { }     // 재정의
    void method3() { 
    method2();             // 재정의 된 메소드 호출
    super.method2();       // 숨겨진 부모클래스의 메소드호출
    }
    }

 

↓ 예제

더보기

super 변수    Airplane.java

package exam01;

public class Airplane {

	public void land() {
		System.out.println("착륙합니다.");		
	}
	public void fly() {
		System.out.println("일반비행합니다.");		
	}
	public void takeOff() {
		System.out.println("이륙합니다.");
	}
}

super 변수    SupersonicAirplane.java

package exam01;

public class SupersonicAirplane extends Airplane{
	
	public static final int NORMAL = 1;                   --상수를 선언
	public static final int SUPERSONIC = 2;				  --상수를 선언
	
	public int flyMode = NORMAL;
	
	@Override
	public void fly() {
		if(flyMode == SUPERSONIC) {
			System.out.println("초음속비행합니다.");
		}else {
			super.fly();    //<--- Airplane 객체의 fly() 메소드 호출
		}
	}	
}

※ 자주 사용되는 고정값들은 상수를 사용함으로써 가독성을 높여준다.

※ 메소드를 재정의 하여 flyMode 가 SUPERSONIC 상수 값을 가질 경우에는 "초음속 비행합니다."

를 출력하고  그렇지 않을 경우는 부모클래스인 Airplane의 fly() 메소드를 호출하기 위해 super.fly()를 사용하였다.

 super 변수  SupersonicAirplaneExample.java

package exam01;

public class SupersonicAirplaneExample {

	public static void main(String[] args) {
		SupersonicAirplane sa = new SupersonicAirplane();
		sa.takeOff();
		sa.fly();
		sa.flyMode = SupersonicAirplane.SUPERSONIC;
		sa.fly();
		sa.flyMode = SupersonicAirplane.NORMAL;
		sa.fly();
		sa.land();	
	}

}

 

 

 


final 클래스와 final 메소드

▶ final 키워드는 클래스, 필드, 메소드를 선언할 때 사용할 수 있는데, 해당 선언이 최종 상태이고 결코 수정될 수 없음을 뜻한다. 

클래스, 필드, 메소드 선언 시 각각 해석이 조금씩 다르다.

 

1. 필드에 선언

필드에 선언할 시 final이 지정되면 초기값 설정 후 더 이상 값을 변경할 수 없다.

private 으로 접근자를 했을 때도 getter로 값을 가져오는건 가능하지만 setter로 값을 저장하는건 불가능.

2. 상속할 수 없는 final 클래스

클래스를 선언할 때 final 키워드를 class 앞에 붙이면 이 클래스는 최종적인 클래스이므로 상속할 수 없는 클래스가 된다. 즉, final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다는 것이다.

public final class 클래스 { .. }

final 클래스의 대표적인 예는 자바 표준 API에서 제공하는 String 클래스이다. 

String클래스는 다음과 같이 선언되어있다.

public final class String { ... }

그래서 String 클래스는 다음과 같이 자식클래스를 만 수 없다. 

public class NewString extends String { ... }   ( X )  불가능

 

3. 재정의할 수 없는 final 메소드

메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 재정의할 수 없는 메소드가 된다. 즉 부모 클래스를 상속해서 자식 클래스를 선언할 떄 부모 클래스에서 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다.

public final 리턴타입 메소드( 매개변수, ...) { ... }

stop() 메소드를 final로 선언했다.

final 메소드라서 오버라이드가 안된다는 에러가 발생한다.

Cannot override the final method from Car

 


▶ protected 접근 제한자

  • public 와 default 접근 제한의 중간 쯤에 해당한다.
  • 같은 패키지에서는 default롸 같이 접근 제한이 없지만 다른패키지에서는 자식클래스만 접근을 허용한다.
  • 단 new연산자를 사용해서 생성자를 직접 호출할 수는 없고, 자식 생성자에서 super()로 부모클래스의 생성자를 호출해야한다.
반응형

'자바의 정석' 카테고리의 다른 글

매개변수의 다형성  (0) 2022.08.22
인스턴스 멤버와 정적 멤버  (0) 2022.04.15
메소드 Method  (0) 2022.04.12
생성자 Constructor  (0) 2022.04.12
필드 Field  (0) 2022.04.11