[java]상속과 다형성
in Java
상속
상속이란?
자바에는 상속(Inheritance)이라는 개념이 존재합니다.
쉽게 말해 부모 클래스(상위 클래스)와 자식 클래스(하위 클래스)가 있으며, 자식 클래스는 부모 클래스를 선택해서, 그 부모의 멤버를 상속받아 그대로 쓸 수 있게 됩니다.
상속을 하는 이유는 간단합니다. 이미 마련되어 있던 클래스를 재사용해서 만들 수 있기 때문에 효율적이고, 개발 시간을 줄여주게 됩니다.중복된 코드를 줄일 수 있고, 유지 보수가 편리하며, 통일성이 있고 다형성을 구현할 수 있습니다.
상속을 하더라도 자식 클래스가 부모의 모든 것들을 물려받는 것은 아닙니다.
- 부모 클래스의 private 접근 제한을 갖는 필드 및 메소드는 자식이 물려받을 수 없습니다.
- 부모와 자식 클래스가 서로 다른 패키지에 있다면, 부모의 default 접근 제한을 갖는 필드 및 메소드도 자식이 물려받을 수 없습니다. (default 접근 제한은 ‘같은 패키지에 있는 클래스’만 접근이 가능하게끔 하는 접근 제한자이기 때문입니다.)
- 그 이외의 경우는 모두 상속의 대상이 됩니다.
class 클래스명 extends 부모 클래스명 {
...
}
extends
키워드를 클래스 선언부에 명시하여 상속을 구현한다. extends
뒤에 오는 클래스가 부모 클래스다. 자식 클래스는 부모 클래스의 생성자와 클래스 초기화 블록을 제외한 모든 멤버, 즉 변수와 메서드를 상속받는다.
public class Test2 {
public static void main(String[] args) {
Parent parent = new Parent("hello");
System.out.println(parent.getA()); // hello
System.out.println(parent.getB()); // i'm not hello
Child child = new Child("hello"); // compile error: The constructor Child(String) is undefined
Child child2 = new Child();
System.out.println(child2.getA()); // null
System.out.println(child2.getB()); // i'm not hello
}
}
class Parent {
private String a;
private String b;
public Parent() {}
{
this.b = "i'm not hello";
}
static {
// 클래스 초기화 블록은 상속되지 않는다.
System.out.println("initializing");
}
public Parent(String a) {
// 생성자는 상속되지 않는다.
this.a = a;
}
public String getA() {
return this.a;
}
public String getB() {
return this.b;
}
}
class Child extends Parent {
// have nothing
}
class Parent {
String str = "부모멤버";
public void show() {
System.out.println("부모의 메서드");
}
}
class Child extends Parent {
String str = "자식멤버";
@Override
public void show() {
System.out.println(super.str);
System.out.println(this.str);
super.show(); // 부모의 메서드, 즉 재정의 하기 전의 메서드를 의미함.
// this.show(); // 무한재귀호출
}
}
-> new Child().show()
-> 부모멤버
-> 자식멤버
-> 부모의 메서드
자식 클래스의 인스턴스를 생성할 땐 자손의 멤버와 부모의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다. 이때 원칙상 자식 클래스의 생성자에 부모 클래스의 생성자를 호출하는 코드인 super()
가 호출되어야 하는데, 만약 부모 클래스에 기본 생성자가 있다면 super()
는 컴파일러가 자동으로 추가한다.
기본 생성자: 매개변수가 없는 생성자 메서드
부모 클래스에 선언된 인스턴스 변수와 같은 이름의 인스턴스 변수를 자손 클래스에 중복 정의했을 때, super
가 참조하는 멤버와 this
가 참조하는 멤버가 다르다.
다음을 보면 :
public class TestEverything {
public static void main(String[] args) {
Child child = new Child();
child.show();
}
}
class Parent {
String str = "부모에서 정의된 인스턴스 변수";
String txt = "부모 txt";
}
class Child extends Parent {
String txt = "자식 txt";
public void show() {
str = "자식이 재정의 한 인스턴스 변수";
System.out.println(super.str);
System.out.println(this.str); // (1) 중복 정의되지 않은 경우
System.out.println(super.txt);
System.out.println(this.txt); // (2) 중복 정의된 경우
String str = "지역변수";
System.out.println(str); // (3) 인스턴스 변수와 같은 이름의 지역변수를 생성할 경우
}
}
-> 자식이 재정의 한 인스턴스 변수
-> 자식이 재정의 한 인스턴스 변수
-> 부모 txt
-> 자식 txt
-> 지역변수
부모 쪽에서만 정의된 인스턴스 변수 str
은 super
, this
모두 자식의 인스턴스 변수를 참조하지만 부모와 자식 양 쪽에서 중복 정의된 인스턴스 변수 txt
는 super
, this
각각 자신의 인스턴스 변수를 참조한다. str
처럼 클래스 타입을 명시한 뒤 같은 이름으로 변수를 초기화하면 그 변수는 더 이상 인스턴스 변수가 아닌 지역변수를 참조한다. 따라서 위의 경우처럼 같은 이름의 지역변수가 선언될 경우엔 this
없이는 인스턴스 변수에 접근할 수 없다.
부모 클래스에 추상 클래스가 존재한다면 해당 메서드는 반드시 재정의 해야한다. 그렇지 않을경우 컴파일 에러가 발생할 것이다.
abstract class Parent {
abstract void print();
public void show() { }
}
class Child extends Parent {
@Override
void print() { }
}
스태틱 메서드는 재정의 할 수 없다. 하지만 접근은 가능:
class Parent {
public static void stM() {
System.out.println("hi");
}
}
class Child extends Parent {
public void show() {
stM();
}
}
부모타입의 참조변수에 자식의 인스턴스를 할당할 시 재정의override
된 멤버에 한해서 자식의 멤버가 실행우선권을 갖는다:
public class TestEverything {
public static void main(String[] args) {
Parent p = new Child();
p.show(); // 자식 메서드
}
}
class Parent {
public String show() {
return "부모 메서드";
}
}
class Child extends Parent {
@Override
public String show() {
return "자식 메서드";
}
}
다형성(polymorphism)이란?
다형성(polymorphism)이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미합니다.
자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있습니다.
다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나입니다.
참조 변수의 다형성
자바에서는 다형성을 위해 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하고 있습니다.
이때 참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 같거나 적어야 참조할 수 있습니다.
다음 예제는 참조 변수의 다형성을 보여주는 예제입니다.
class Parent { ... }
class Child extends Parent { ... }
...
Parent pa = new Parent(); // 허용
Child ch = new Child(); // 허용
Parent pc = new Child(); // 허용
Child cp = new Parent(); // 오류 발생.
특정 타입의 참조 변수로는 당연히 같은 타입의 인스턴스를 참조할 수 있습니다.
참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수와 같기 때문입니다.
그리고 부모 클래스 타입의 참조 변수로도 자식 클래스 타입의 인스턴스를 참조할 수 있습니다.
참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 적기 때문입니다.
하지만 반대의 경우인 자식 클래스 타입의 참조 변수로는 부모 클래스 타입의 인스턴스를 참조할 수 없습니다.
참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 많기 때문입니다.
클래스는 상속을 통해 확장될 수는 있어도 축소될 수는 없으므로, 자식 클래스에서 사용할 수 있는 멤버의 개수가 언제나 부모 클래스와 같거나 많게 됩니다.
참조 변수의 타입 변환
자바에서는 참조 변수도 다음과 같은 조건에 따라 타입 변환을 할 수 있습니다.
- 서로 상속 관계에 있는 클래스 사이에만 타입 변환을 할 수 있습니다.
- 자식 클래스 타입에서 부모 클래스 타입으로의 타입 변환은 생략할 수 있습니다.
- 하지만 부모 클래스 타입에서 자식 클래스 타입으로의 타입 변환은 반드시 명시해야 합니다.
참조 변수의 타입 변환도 기본 타입의 타입 변환과 마찬가지로 타입 캐스트 연산자(())를 사용합니다
다음 예제는 참조 변수의 타입 변환을 보여주는 예제입니다.
class Parent { ... }
class Child extends Parent { ... }
class Brother extends Parent { ... }
...
Parent pa01 = null;
Child ch = new Child();
Parent pa02 = new Parent();
Brother br = null;
pa01 = ch; // pa01 = (Parent)ch; 와 같으며, 타입 변환을 생략할 수 있음.
br = (Brother)pa02; // 타입 변환을 생략할 수 없음.
br = (Brother)ch; // 직접적인 상속 관계가 아니므로, 오류 발생.
instanceof 연산자
이러한 다형성으로 인해 런타임에 참조 변수가 실제로 참조하고 있는 인스턴스의 타입을 확인할 필요성이 생깁니다.
자바에서는 instanceof 연산자를 제공하여, 참조 변수가 참조하고 있는 인스턴스의 실제 타입을 확인할 수 있도록 해줍니다.
자바에서 instanceof 연산자는 다음과 같이 사용합니다.
참조변수 instanceof 클래스이름
왼쪽에 전달된 참조 변수가 실제로 참조하고 있는 인스턴스의 타입이 오른쪽에 전달된 클래스 타입이면 true를 반환하고, 아니면 false를 반환합니다.
만약에 참조 변수가 null을 가리키고 있으면 false를 반환합니다.
다음 예제는 참조 변수가 실제로 가리키고 있는 인스턴스의 타입을 instanceof 연산자로 확인하는 예제입니다.
class Parent { }
class Child extends Parent { }
class Brother extends Parent { }
public class Polymorphism01 {
public static void main(String[] args) {
Parent p = new Parent();
System.out.println(p instanceof Object); // true
System.out.println(p instanceof Parent); // true
System.out.println(p instanceof Child); // false
System.out.println();
Parent c = new Child();
System.out.println(c instanceof Object); // true
System.out.println(c instanceof Parent); // true
System.out.println(c instanceof Child); // true
}
}
[참조]
https://noritersand.github.io/java/java-%EC%83%81%EC%86%8D-extends/
http://www.tcpschool.com/java/java_polymorphism_concept