✔ 상속의 기본 문법 이해
상속이란?
- 기존에 정의된 클래스에 메소드와 변수를 추가하여 새로운 클래스를 정의하는 것
- 클래스 간의 공통 규약을 적용하기 위해 정의하게 됨
- 키워드 extends는 상속을 의미하는 키워드
- 상속의 대상이 되는 클래스는 상위 클래스, 기초 클래스, 부모 클래스라고 불림
- 상속을 하는 클래스는 하위 클래스, 유도 클래스, 자식 클래스라고 불림
- 상속을 하는 클래스의 인스턴스에는 상속의 대상이 되는 클래스의 변수와 메소드가 존재하게 됨
- 자바는 상속할 수 있는 클래스가 최대 하나인 단일 상속만을 지원
- 그러나 상속의 깊이를 더하는 것을 얼마든지 가능 (AAA → MMM → ZZZ)
class Man {
String name;
public void tellYourName() {
System.out.println("My name is " + name);
}
}
class BusinessMan extends Man {
String company;
String position;
public void tellYourInfo() {
System.out.println("My company is " + company);
System.out.println("My position is " + position);
tellYourName();
}
}
상속과 생성자
- 하위 클래스의 인스턴스 생성 시 상위 클래스, 하위 클래스 순으로 생성자가 모두가 호출됨
- 이때 하위 클래스의 생성자에서 상위 클래스의 생성자를 명시적으로 호출하지 않으면, 인자를 받지 않는 생성자를 자동 호출
- 상위 클래스의 생성자를 명시적으로 호출하기 위해서는 키워드 super를 사용
- 상위 클래스의 생성자는 하위 클래스의 생성자의 몸체 부분에 앞서 실행되어야 하므로 생성자의 첫 문장으로 등장해야 함
class SuperCLS {
public SuperCLS() {
System.out.println("Con: SuperCLS()");
}
public SuperCLS(int i) {
System.out.println("Con: SuperCLS(int i)");
}
public SuperCLS(int i, int j) {
System.out.println("Con: SuperCLS(int i, int j)");
}
}
class SubCLS extends SuperCLS {
public SubCLS() {
System.out.println("Con: SubCLS()");
}
public SubCLS(int i) {
super(i);
System.out.println("Con: SubCLS(int i)");
}
public SubCLS(int i, int j) {
super(i, j);
System.out.println("Con: SubCLS(int i, int j)");
}
}
class SuperSubCon {
public static void main(String[] args) {
new SubCLS();
}
}
- 하지만 자바는 상속 관계에 있을지라도, 상위 클래스의 멤버는 상위 클래스의 생성자를 통해서 초기화하도록 유도하고 있음
- 그렇기 때문에 하위 클래스의 인스턴스 생성 과정에서 상위 클래스의 생성자가 호출되는 것
- 그러므로 상속 관계에 있을지라도 인스턴스 변수는 각 클래스의 생성자를 통해서 초기화해야 함
class Man {
String name;
public Man(String name) {
this.name = name;
}
public void tellYourName() {
System.out.println("My name is " + name);
}
}
class BusinessMan extends Man {
String company;
String position;
public BusinessMan(String name, String company, String position) {
super(name);
this.company = company;
this.position = position;
}
public void tellYourInfo() {
System.out.println("My company is " + company);
System.out.println("My position is " + position);
tellYourName();
}
}
class MyBusinessMan2 {
public static void main(String[] args) {
BusinessMan man = new BusinessMan("YOON", "Hybrid ELD", "Staff Eng.");
man.tellYourInfo();
}
}
✔ 클래스 변수, 클래스 메소드와 상속
static 선언이 붙은 클래스 변수와 클래스 메소드의 상속
- 클래스 변수와 클래스 메소드는 인스턴스의 생성과 상관 없이 접근이 가능하며 클래스 내부와 외부에서 접근이 가능함
- 즉 클래스 변수와 클래스 메소드는 인스턴스에 속하지 않는, 딱 하나만 존재하는 변수와 메소드
- 그러므로 인스턴스 내의 인스턴스 멤버로 존재하지 않아 상속의 대상이 아님
- 대신 상위 클래스에 위치한 클래스 변수와 메소드에 대하여 접근 수준 지시자가 접근을 허용할 경우
이를 상속하는 하위 클래스에서는 클래스 내부가 아니지만, 이름만으로 클래스 변수와 메소드에 직접 접근이 가능함
class SuperCLS {
protected static int count = 0;
public SuperCLS() {
count++;
}
}
class SubCLS extends SuperCLS {
public void showCount() {
// 변수의 이름을 통해 직접 접근
System.out.println(count);
}
}
✔ 상속을 위한 두 클래스의 관계
언제 두 클래스를 상속의 관계로 맺어야 할까?
- 기본적으로 IS-A 관계라는 것이 성립해야 상속의 후보로 고려할 수 있음
- IS-A 관계는 '~은 ~이다.'로 표현되는 관계
- 하위 클래스는 상위 클래스의 모든 특성을 가져야 함
- 거기에 더하여 하위 클래스는 자신만의 추가적인 특징을 더하게 됨
- 예) 스마트폰은 모바일폰의 일종이다.
✔ 메소드 오버라이딩
상위 클래스의 참조변수가 참조할 수 있는 대상의 범위
- 하위 클래스는 상위 클래스로 표현될 수 있으므로, 상위 클래스의 참조변수는 하위 클래스의 인스턴스를 참조할 수 있음
- 그러므로 상속 관계가 형성되면 하위 클래스는 하위 클래스의 인스턴스인 동시에 상위 클래스의 인스턴스가 됨
- 이러한 참조 관계는 인스턴스 배열을 생성하는 것에서도 동일하게 이어짐
SmartPhone phone = new SmartPhone("010-555-777", "Nougat");
MobilePhone phone = new SmartPhone("010-555-777", "Nougat");
- 자바는 메소드 호출 시 참조변수의 형을 참조하여 그 메소드의 호출이 옳은 것인지 판단함
- 이러한 형태의 판단은 속도가 빠름
- 그러나 실제 참조하는 인스턴스를 대상으로 메소드의 호출 가능성을 판단하는 일을 간단하지 않아 시간을 늦추게 됨
- 그러므로 상위 클래스에서 접근 가능한 멤버는 상위 클래스에 정의된 멤버나
상위 클래스가 상속하는 클래스의 멤버로 제한되어
상위 클래스로 정의된 메소드는 호출할 수 있지만, 하위 클래스에 정의된 메소드의 호출은 불가능함
- 즉 참조변수의 형에 해당하는 클래스와 그 클래스가 상속하는 상위 클래스에 정의된 메소드들만 호출이 가능함
class MobilePhone {
protected String number;
public MobilePhone(String num) {
number = num;
}
public void answer() {
System.out.println("Hi~ from " + number);
}
}
class SmartPhone extends MobilePhone {
private String androidVer;
public SmartPhone(String num, String ver) {
super(num);
androidVer = ver;
}
public void playApp() {
System.out.println("App is running in " + androidVer_;
}
}
class MobileSmartPhoneRef {
public static void main(String[] args) {
SmartPhone ph1 = new SmartPhone("010-555-777", "Nougat");
MobilePhone ph2 = new SmartPhone("010-555-777", "Nougat");
ph1.answer();
ph1.playApp();
ph2.answer();
// 컴파일 오류 발생
ph2.playApp();
}
}
참조변수 간 대입과 형 변환
- 컴파일러는 참조변수의 형만을 가지고 대입의 가능성을 판단함
- 하위 클래스의 인스턴스는 상위 클래스의 인스턴스이기도 하니, 상위 클래스의 인스턴스에 대입할 수 있음
- 하지만 인스턴스가 상위 클래스의 인스턴스를 상속하는지 확신할 수 없는 상황에서는 대입을 허용하지 않음
- 이 경우 명시적으로 형 변환을 하여 상속하는 인스턴스임을 프로그래머가 보장할 경우 허용이 됨
class Cake {
public void sweet() { ... }
}
class CheeseCake extends Cake {
public void milky() { ... }
}
// 가능
CheeseCake ca1 = new CheeseCake();
Cake ca2 = ca1;
// 불가능
Cake ca3 = new CheeseCake();
CheeseCake ca4 = ca3;
// 가능
Cake ca3 = new CheeseCake();
CheeseCake ca4 = (CheeseCake)ca3;
메소드 오버라이딩이란?
- 상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 것
- 메소드 오버라이딩이 성립되기 위해서는 메소드의 이름, 메소드의 반환형, 메소드의 매개변수 선언이 같아야 함
- 오버라이딩 한 메소드는 오버라이딩 된 메소드를 무효화하고 대신하게 됨
- 이때 클래스 내부에서 상위 클래스에 정의된, 오버라이딩 된 메소드를 호출하고 싶다면 키워드 super를 사용
class Cake {
public void yummy() {
System.out.println("Yummy Cake");
}
}
class CheeseCake extends Cake {
public void yummy() {
super.yummy();
System.out.println("Yummy Cheese Cake");
}
}
인스턴스 변수와 클래스 변수도 오버라이딩의 대상일까?
- 인스턴스 변수가 상위 클래스와 하위 클래스에 모두 선언되었을 경우, 참조변수의 형에 따라서 접근하는 변수가 결정됨
- 변수는 오버라이딩이 되지 않기 때문
- 이러한 특성은 클래스 변수와 클래스 메소드의 경우에도 마찬가지로 적용되어
참조변수의 형에 따라서 접근하는 클래스 변수와 클래스 메소드가 결정됨
✔ instanceof 연산자
instanceof 연산자란?
- 참조변수가 참조하는 인스턴스의 클래스나 참조하는 인스턴스가 상속하는 클래스를 묻는 연산자
- 참조변수가 참조하는 인스턴스나 상속하는 클래스의 인스턴스가 맞다면 true 그렇지 않다면 false를 반환
class Cake {
}
class CheeseCake extends Cake {
}
Cake cake = new CheeseCake();
if (cake instanceof Cake) {
...
}
if (cake instanceof CheeseCake) {
...
}
instanceof 연산자의 활용
- instanceof 연산자는 명시적 형 변환의 가능성을 판단해주는 연산자로 사용될 수도 있음
class Cake {
}
class CheeseCake extends Cake {
}
class StrawberryCheeseCake extend CheeseCake {
}
StrawberryCheeseCake cake = new StrawberryCheeseCake();
// cake가 참조하는 인스턴스가 CheeseCake 인스턴스이거나 CheeseCake를 상속하는 클래스의 인스턴스
if (cake instanceof CheeseCake) {
(CheeseCake)cake;
}
✔ Object 클래스와 final 선언 그리고 @Override
Object 클래스를 상속하는 모든 클래스
- 클래스를 정의할 때 어떤 클래스도 상속하지 않으면 해당 클래스는 java.lang 패키지에 묶여 있는 Object 클래스를 상속하게 됨
- 상속하는 클래스가 있는 경우에는 Object 클래스를 상속하지 않음
- 그러나 이 경우에도 상속하는 클래스가 Object 클래스를 상속하게 됨
- 결국 자바의 모든 클래스는 Object 클래스를 직접 혹은 간접적으로 상속하게 됨
- 이는 자바의 모든 인스턴스에 공통된 기준 및 규약을 적용하기 위함
class MyClass { ... }
class MyClass extends Object { ... }
- 그러므로 자바의 모든 인스턴스는 System.out.println 메소드의 인자가 될 수 있는 것
public void println(Object x)
클래스와 메소드의 final 선언
- 클래스를 정의하는데 있어서 해당 클래스를 다른 클래스가 상속하는 것을 원치 않는다면 final 선언을 추가함
- 또한 메소드의 정의에 final 선언을 추가하면 해당 메소드의 오버라이딩을 허용하지 않을 수 있음
public final class MyLastCLS { ... }
class Simple {
public final void func(int n) { ... }
}
@Override
- @Override 어노테이션을 사용하면 상위 클래스의 메소드를 오버라이딩 할 목적으로 정의하였다는 것을 컴파일에게 전달 가능
- 이를 통해 컴파일러는 오버라이딩이 제대로 되었는지를 확인하여 오류를 확인할 수 있음
- 메소드의 매개변수 형과 반환형 등이 달라 오버라이딩이 되지 않을 경우 발견하기 쉽지 않아 치명적인 실수가 될 수 있음
class Cake {
public void yummy() {
System.out.println("Yummy Cake");
}
}
class CheeseCake extends Cake {
@Override
public void yummy() {
super.yummy();
System.out.println("Yummy Cheese Cake");
}
}