4주차 : 상속
자바 상속의 특징
■ 상속이란?
- 기존의 클래스를 재사용하는 새로운 클래스를 생성
- 재활용의 개념이며, 더 적은양의 코드로 새로운 클래스의 생성이 가능
■ 사용법
// extends 키워드를 기억하자
class Child extends Parent {
// 내용
}
위의 사용법에서 Child가 생성하고자 하는 클래스이며, Parent는 기존에 있던 클래스이다.
Child와 Parent는 서로 상속 관계에 있으며, Child를 자손 클래스라고 하고 Parent를 조상 클래스라고 한다.
크게 보면 Child안에 Parent가 있다고 생각하면 된다.
자손 클래스는 조상 클래스의 모든 멤버를 상속받으므로, 조상 클래스에 멤버변수가 추가되면 자손 클래스에도 추가된다.
하지만, 자손 클래스에 멤버변수를 추가해도 조상 클래스는 아무런 영향이 없다.
■ 하나의 조상에 여러 자손을 생성 가능
// Parent하나에 Child1, 2의 자손 생성
class Parent { }
class Child1 extend Parent { }
class Child2 extend Parent { }
이때, 같은 조상을 상속받지만 Child1과 Child2는 서로 아무런 관계가 없다.
자손을 상속받는 또 다른 자손을 생성해보자
// Child1을 상속
class GrandChild extend Child1 { }
이 말인 즉슨, GrandChild클래스는 Parent와 Child1의 멤버들을 모두 상속받는다.
■ 추가
1) 다중 상속은 지원하지 않는다.
2) 모든 클래스는 Object 클래스의 자식 클래스이다.
3) 조상 클래스는 Super Class라고도 부른다.
4) 자손 클래스는 Sub Class라고도 부른다.
Super 키워드
■ Super란?
- 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수
- 앞전의 this와 같이 상속받은 멤버 이름과 본인의 클래스에 있는 멤버 이름이 같을 때 super를 붙여서 구분하기도 함
■ 사용법
// super키워드 예시
class Parent {
int age;
String name;
public Parent() { }
public Parent(int age, String name) {
this.age = age;
this.name = name;
}
}
class Child extend Parent {
public Child() { }
public setAge(int age) {
super.age = age; // 조상 클래스의 age에 매개변수로 받은 age삽입
}
public setName(String name) {
super.name = name; // 조상 클래스의 name에 매개변수로 받은 name삽입
}
// 객체를 출력할 때 사용되는 toString 오버라이딩
@Override
public String toString() {
return age + ", " + name;
}
}
class Main {
public static void main(String[] args) {
Child child = new Child();
child.setAge(26);
child.setName("JaeHee");
System.out.println(child);
}
}
// 결과 : 26, JaeHee
추가적으로, super 키워드와 super() 메서드는 다르다.
super() 메서드는 조상 클래스의 생성자 함수를 호출하는 역할을 한다.
자손 클래스는 이런 super() 메서드를 기본적으로 호출해야하지만, 누락하더라도 컴파일러가 자동으로 생성해준다.
※ 주의사항
- super()는 조상 클래스의 기본 생성자이며, 매개변수가 추가되면 그에 맞는 생성자가 호출
- 조상 클래스에 기본 생성자가 없다면, 자동으로 생성해주지 않기 때문에 자손 클래스에서 일일이 호출해야 한다.
// super() 메서드 예시
class Parent {
public Parent() {
System.out.println("조상 클래스입니다.");
}
}
class Child extends Parent {
public Child() {
//super(); 가 자동으로 생성
System.out.println("자손 클래스입니다.");
}
}
class Main {
public static void main(String[] args) {
Parent parent = new Parent();
System.out.println("-----");
Child child = new Child();
}
}
// 결과
조상 클래스입니다. // 조상 클래스 객체 생성
-----
조상 클래스입니다. // 자손 클래스 객체 생성을 할 때 조상 클래스의 super()부터 호출
자손 클래스입니다.
반대로 기본 생성자가 없는 경우를 예시로 들어보자
// 기본 생성자 X
class Parent {
String name;
public Parent(String name) {
this.name = name;
}
}
class Child extends Parent {
// 컴파일 에러 발생(기본 생성자x)
public Child() { }
// 정상 동작
public Child() {
super("JaeHee");
}
}
메소드 오버라이딩
■ 메소드 오버라이딩이란?
조상 클래스로부터 상속받은 메소드의 내용을 변경하는 것
■ 조건
조상 클래스의 메소드와 이름, 매개변수, 반환타입이 같아야 함
접근 제어자도 변경할 수 있지만, 조상 클래스의 메소드보다 좁은 범위로는 변경할 수 없다.
// 오버라이딩 예시
class Parent {
public void test() {
System.out.println("조상 클래스입니다.");
}
}
class Child extends Parent {
@Override
public void test() {
System.out.println("자손 클래스입니다.");
}
}
class Main {
public static void main(String[] args) {
Parent parent = new Parent();
Child child = new Child();
parent.test();
child.test();
}
}
// 결과
조상 클래스입니다.
자손 클래스입니다.
다이나믹 메소드 디스패치(Dynamic Method Dispatch)
상속과 다형성은 객체지향개념의 중요한 특징이며, 서로 관계가 깊다.
* 다형성 : 여러가지 형태를 가질 수 있는 능력
■ 메소드 디스패치란?
- 메소드를 어떻게 호출할 지 정해서 호출하는 것
- 정적과 동적의 방법이 존재
■ 정적(static)
정적 메소드 디스패치는 메소드가 어떻게 실행될지 컴파일 타임에 결정
// 정적 메소드 디스패스의 경우 위의 예시 그대로이다.
class Parent {
public void test() {
System.out.println("조상 클래스입니다.");
}
}
class Child extends Parent {
@Override
public void test() {
System.out.println("자손 클래스입니다.");
}
}
class Main {
public static void main(String[] args) {
Parent parent = new Parent();
Child child = new Child();
parent.test();
child.test();
}
}
// 결과
조상 클래스입니다.
자손 클래스입니다.
■ 동적(dynamic)
동적 메소드 디스패치는 어떤 메소드가 사용될지 런타임에 결정
// 동적 메소드 디스패치 예시
class Parent {
int x = 1;
public void test() {
System.out.println("조상 클래스입니다.");
}
}
class Child extends Parent {
int x = 10;
@Override
public void test() {
System.out.println("자손 클래스입니다.");
}
}
class Main {
public static void main(String[] args) {
Parent parent = new Parent();
Child child = new Child();
// Parent 클래스로 Child 객체 생성
Parent parent2 = new Child()
parent.test();
child.test();
parent2.test();
// 변수를 그냥 출력하면 어떻게 될까?
System.out.println(parent.x);
System.out.println(child.x);
System.out.println(parent2.x);
}
}
// 결과
조상 클래스입니다.
자손 클래스입니다.
자손 클래스입니다.
1
10
1
기본적으로 Child객체는 Child 클래스 내부의 test() 메소드를 실행시킨다.
추가적으로, Java에서는 멤버 변수의 다형성을 허용하지 않기 때문에 컴파일 시점에서 정해져서 변화가 없다.
그 결과로 Parent 클래스에 Child로 객체를 생성하면 메소드는 Child의 것을 호출하지만, 멤버변수는 Parent의 것을 호출한다.
추상 클래스
■ 추상 클래스란?
앞 전에 클래스는 설계도라고 하였었다.
그럼 추상 클래스는 무엇이라고 부를까?
그것은 미완성 설계도라고 표현할 수 있다.
이 말은, 미완성 메소드(추상 메소드)를 포함하고 있다는 의미이다.
미완성 설계도로 제품을 만들 수 없듯이, 추상 클래스로 인스턴스를 생성할 수 없다.
추상 클래스는 상속을 통해서 자손 클래스에 의해서만 완성될 수 있다.
■ 예시
추상 클래스는 abstract 키워드를 붙이면 된다.
// 추상 클래스 작성 예시
abstract class Abclass {
// 내용
}
추상 클래스는 추상 메소드를 포함하고 있다는 것을 제외하면 일반 클래스와 동일하므로 생성자와 멤버변수, 메소드를 가질 수 있다.
자손 클래스에서 오버라이딩하여 구현하는데, 이 과정이 구체화이다.
* 구체화 : 상속을 통해 클래스를 구현, 확장하는 작업
// 추상 클래스 예시
public abstract class Animal {
abstract void sound();
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍!");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("야옹!");
}
}
class Main {
public static void main(String[] args) {
Dog d = new Dog();
Cat c = new Cat();
d.sound();
c.sound();
}
}
// 결과
멍멍!
야옹!
final 키워드
현재 상속과 관련된 파트이므로 그에 해당하는 final의 의미만 살펴보자
■ final 키워드란?
메소드 정의 시 final 키워드를 사용하면, 오버라이딩할 수 없는 메소드를 의미하므로 상속받은 조상 클래스의 final 메소드는 자손 클래스에서 재정의가 불가능
// final 키워드 예시
class Parent {
public final void test() { }
}
class Child extends Parent {
@Override
public void test() { } // 컴파일 에러 발생
}
// 결과 : 'test()' cannot override 'test()' in 'Parent'; overridden method is final
Object 클래스
■ Object 클래스란?
- java.lang.Object
- 모든 클래스의 최상위 클래스
모든 클래스의 최상위 클래스이기 때문에, 모든 클래스는 Object 클래스를 상속받는다.
따라서, 모든 클래스는 Object 클래스의 메소드를 사용할 수 있고 일부 메소드를 Override해서 사용할 수 있다.
(final 메소드 제외)
java.lang 패키지안에 Object 클래스가 있는데, java.lang 패키지는 컴파일러가 자동으로 import해주므로 따로 명시를 하지 않아도 사용이 가능하다.