▶객체 지향 프로그래밍에서 부모클래스의 멤버를 자식클래스에게 물려줄 수 있다.
부모클래스 - 상위클래스
자식클래스 - 하위클래스 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
▶ 자식 객체 이용
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));//재정의된 메소드 호출
}
}
※이클립스 재정의메소드 자동생성
- 이클립스는 부모 메소드 중 하나를 선택해서 재정의 메소드를 자동 생성해주는 기능이 있다.
- 이 기능은 부모 메소드의 시그니처를 정확히 모를 경우 매우 유용하게 사용할 수 있다.
- 자식클래스에서 재정의 메소드를 작성할 위치로 커서를 옮긴다.
- [Source] - [Override/Implement Methods] 메뉴를 선택한다.
- 부모 클래스에서 재정의될 메소드를 선택하고 [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 |