상속을 통해 클래스를 작성하면 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고, 코드를 공통적으로 관리할 수 있어 유지보수에 용이하다.
조상 클래스 : 상속을 해주는 클래스(=부모(parent) 클래스, 상위(super) 클래스, 기반(base) 클래스)
자손 클래스 : 상속을 받는 클래스(=자식(child) 클래스, 하위(sub) 클래스, 파생(derived) 클래스)
class Parent {
private String name;
private int age;
// Child 클래스의 address 미존재
}
class Child extends Parent {
// Parent 클래스의 name, age 필드를 상속받음
private String address;
}
class Child extends Parent1, Parent2 { // error
// ...
}
class Parent extends Object { // Object 클래스는 모든 클래스의 조상
// ...
}
자바에서는 관계 명확성 및 신뢰성을 위해 단일 상속만 허용한다.
모든 클래스는 java.lang.Object 클래스를 상속받는다.(생략 가능)
상속 이외에 클래스를 재사용하는 방법
포함(Composition)을 통해 클래스를 재사용할 수 있다.
class Circle {
private int x;
private int y;
private int radius;
}
class Circle {
private Point point = new Point();
private int radius;
}
class Rectangle {
private Point point = new Point();
private int width;
private int height;
}
class Point {
private int x;
private int y;
}
포함 vs 상속
원(Circle)은 점(Point)이다. (is-a 관계) -> 상속
원(Circle)은 점(Point)을 포함한다. (has-a 관계) -> 포함
이 경우 원(Circle)은 점(Point)을 포함하므로 상속보다는 포함이 적합하다.
오버라이딩(Overriding)
조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것
class Point {
int x;
int y;
String getLocation() {
return "x : " + x + ", y : " + y;
}
}
class Point3D extends Point {
int z;
String getLocation() { // Overriding
return "x : " + x + ", y : " + y + ", z : " + z;
}
}
오버라이딩의 조건
조상 클래스의 메서드와 이름이 같아야 한다.
매개변수의 수와 타입이 모두 같아야 한다.
반환 타입이 같아야 한다.
접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.(public > protected > default > private)
조상 클래스의 메서드보다 더 많은 수의 예외를 선언할 수 없다.(조상 클래스의 메서드가 예외를 선언하지 않은 경우, 자손 클래스에서 선언할 수 없다.)
인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할 수 없다.
오버로딩(Overloading) vs 오버라이딩(Overriding)
오버로딩(Overloading) : 같은 이름의 메서드를 정의하는 것
오버라이딩(Overriding) : 상속받은 메서드의 내용을 변경하는 것
class Point {
int x;
int y;
String getLocation() {
return "x : " + x + ", y : " + y;
}
}
class Point3D extends Point {
int z;
String getLocation() { // Overriding
return "x : " + x + ", y : " + y + ", z : " + z;
}
String getLocation(int x, int y, int z) { // Overloading
return "x : " + x + ", y : " + y + ", z : " + z;
}
}
super
조상 클래스의 멤버와 자손 클래스의 멤버의 이름이 같을 때 둘을 구별하기 위해 사용하는 참조변수이다.
class Point {
int x;
int y;
String getLocation() {
return "x : " + x + ", y : " + y;
}
}
class Point3D extends Point {
int z;
String getLocation() { // Overriding
return "x : " + x + ", y : " + y + ", z : " + z;
}
String getSuperLocation() {
return super.getLocation();
}
}
super()
자손 클래스의 생성자에서 조상 클래스의 생성자를 호출할 때 사용한다.
자손 클래스의 인스턴스를 생성하면, 자손 멤버와 조상의 멤버가 합쳐진 하나의 인스턴스가 생성된다.
자손 클래스의 생성자는 조상 클래스의 생성자를 호출하여 조상 클래스의 멤버들이 초기화되도록 해야 한다.
첫 줄에서 조상클래스 생성자를 호출해야하는 이유는 조상 클래스의 멤버들이 자손 클래스의 멤버보다 먼저 초기화되어야 하기 때문이다.
마찬가지로 조상 클래스의 생성자도 상속 관계를 거슬러 올라가면서 최종적으로 Object 클래스의 생성자까지 호출하게 된다.
첫 줄에서 조상클래스 생성자를 호출하지 않으면 컴파일러가 자동으로 super()를 호출한다.
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Point3D extends Point {
int z;
Point3D(int x, int y, int z) {
// super(); // 컴파일러가 자동으로 추가 -> 정의 된 생성자가 없어 오류 발생
this.x = x;
this.y = y;
this.z = z;
}
}
위와 같이 조상 클래스의 생성자를 호출하지 않으면 컴파일러가 super()를 호출하지만 정의 된 생성자가 없어 오류가 발생하므로 조상 클래스에 정의 된 생성자를 호출해야 한다.
class Point /* extends Object */ {
int x;
int y;
Point(int x, int y) {
// super(); // 컴파일러가 자동으로 추가 -> 조상인 Object 클래스 생성자 Object() 호출
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) {
super(x, y); // 조상 클래스의 생성자 Point(int x, int y) 호출
this.z = z;
}
String getLocation() { // Overriding
return "x : " + x + ", y : " + y + ", z : " + z;
}
}