package Extends;
class Alphabet {
protected void practiceMethod() {
System.out.println("practice");
}
}
class A extends Alphabet {
void aMethod() {
System.out.println("TEST");
}
}
=========================================================
package Extends;
public class AlphabetTest {
public static void main(String[] args) {
A prac = new A();
prac.aMethod();
prac.practiceMethod(); // 상위 클래스의 메소드
}
}
부모를 부르는 예약어, super()
package Extends;
class Alphabet {
Alphabet() {
System.out.println("Alphabet 클래스의 생성자 호출");
}
protected void practiceMethod() {
System.out.println("practice");
}
}
class A extends Alphabet {
A() {
System.out.println("A 클래스의 생성자 호출");
}
void aMethod() {
System.out.println("TEST");
}
}
===============================================================
package Extends;
public class AlphabetTest {
public static void main(String[] args) {
A prac = new A();
}
}
하위 클래스의 객체를 생성하기 위해 생성자를 호출하였을 때, 부모 클래스의 생성자가 먼저 호출이 되는 이유는 다음과 같습니다.
부모를 부르는 예약어인 super() 함수가 컴파일시 하위 클래스의 생성자에 자동으로 추가가 되기 때문인데, super() 키워드는 this 가 자기 자신의 참조 값을 가지고 있는 것과 동일하게 부모 클래스의 참조 값을 가지고 있는 예약어입니다. 이 키워드는 상위 클래스의 생성자를 호출하는 데에도 사용이 됩니다.
만일, 상위 클래스에 디폴트 생성자가 없고 매개변수가 있는 생성자만 있을 경우엔 super() 에 매개변수를 추가하여, 매개변수가 있는 상위 클래스의 생성자를 직접 호출합니다.
class A extends Alphabet {
A() {
// super(); -> 생성자 내부 상위에 호출
System.out.println("A 클래스의 생성자 호출");
}
기능을 재정의하자, 메서드 오버라이딩
package Extends;
class Alphabet {
Alphabet() {
System.out.println("Alphabet 클래스의 생성자 호출");
}
protected void practiceMethod() {
System.out.println("practice");
}
}
class A extends Alphabet {
A() {
System.out.println("A 클래스의 생성자 호출");
}
void aMethod() {
System.out.println("TEST");
}
@Override
protected void practiceMethod() {
System.out.println("Modify the function");
}
}
해당 코드에 Override 라는 애노테이션을 추가하였는데, 애노테이션은 주석이라는 의미로 @ 기호와 함께 사용합니다. 자바에서 제공하는 애노테이션은 컴파일러에게 특정한 정보를 제공해 주는 역할을 합니다. 즉, Override 를 추가하여 해당 메서드가 재정의된 메서드라는 것을 컴파일러에게 알립니다.
메서드를 재정의 하기 전, 메모리 참조 값은 부모 클래스의 메서드 영역 주소를 참조하여 명령이 실행되는데 메서드를 재정의하면 메모리 참조 값은 실제 인스턴스에 해당하는 메서드가 호출되게 됩니다. (실제 인스턴스 = 부모 클래스가 아닌 자식 클래스의 실제 인스턴스의 메서드 호출) 아래는 상기 작성했던 코드의 가상 메서드를 표현한 그림입니다.
메서드 재정의 전
메서드 재정의 후
묵시적 형 변환
byte bNum = 10;
int iNum = bNum; // byte형 변수인 bNum 값을 int형 변수 iNum에 대입한다.
상기와 같이 클래스 또한 상속받은 상위 클래스로 형 변환이 가능합니다. 테스트를 했던 코드에서는 A 클래스가 Alphabet 클래스를 상속을 받았었습니다. 즉 A 클래스는 A 자료형이면서 동시에 Alphabet 자료형이기도 하므로 A 클래스 인스턴스를 생성할 때 인스턴스의 자료형을 부모 클래스로 클래스 형 변환하여 선언이 가능합니다. → 부모 클래스가 자식 클래스로 형 변환하는 것은 성립되지 않습니다. 자식 클래스는 부모 클래스의 기능을 모두 가졌지만, 부모 클래스는 자식 클래스의 기능을 사용할 수가 없습니다.
Alphabet prac = new A();
동일한 이름을 가지고 여러 형태의 액션을 취하는 다형성
package Extends;
class Alphabet {
void practiceMethod() {
System.out.println("practice");
}
}
class A extends Alphabet {
void practiceMethod() {
System.out.println("practice A");
}
}
class B extends Alphabet {
void practiceMethod() {
System.out.println("practice B");
}
}
class C extends Alphabet {
void practiceMethod() {
System.out.println("practice C");
}
}
하기 코드의 practiceMethodTest 메서드는 어떠한 인스턴스가 매개변수로 넘어와도 모두 Alphabet 형으로 변환합니다. Alphabet 에서 상속받은 클래스가 매개변수로 넘어오면 모두 상위 클래스 형으로 변환되므로 해당 클래스의 메서드를 호출할 수 있습니다. alphabet.pracriceMethod() 코드는 변함이 없지만, 어떠한 매개변수가 넘어왔느냐에 따라서 출력되는 결과가 달라지는데 이것을 다형성이라고 합니다.
package Extends;
public class AlphabetTest {
public static void main(String[] args) {
AlphabetTest prac = new AlphabetTest();
prac.practiceMethodTest(new A());
prac.practiceMethodTest(new B());
prac.practiceMethodTest(new C());
}
// 매개변수의 자료형이 상위 클래스이다.
public void practiceMethodTest(Alphabet alphabet) {
alphabet.practiceMethod();
}
}
실행되는 결과를 그림으로 표현하면 다음과 같습니다.
만일 practiceMethod 의 동일한 기능을 가진 D 라는 하위 클래스가 추가가 된다면, Alphabet 클래스를 상속받아 구현하면 모든 클래스를 Alphabet 자료형 하나로 쉽게 관리가 가능합니다. 이처럼 상위 클래스에서 공통된 부분의 메서드를 구현하고, 하위 클래스에서는 추가 요소만 덧붙여 구현하면 코드의 길이가 줄어들고 유지보수가 편리합니다. → 만일 오버라이딩한 메서드에서 부모 클래스의 기능이 필요하다면 super() 키워드 혹은 this() 키워드를 이용할 수 있습니다.
장점
단점
템플릿 메서드와 final 키워드!
AbstractParentsClass.java
package AbstractPractice;
abstract class AbstractParentsClass {
// This is an abstract method
abstract void A();
abstract void B();
void AB() {
System.out.println("Practice Abstract Classes");
}
// This is a template method
final void Alphabet() {
A();
B();
AB();
}
}
AbstractChildClassOne.java
package AbstractPractice;
class AbstractChildClassOne extends AbstractParentsClass {
@Override
void A() {
System.out.println("A");
}
@Override
void B() {
System.out.println("B");
}
void ChildOne() {
System.out.println("Abstract Child Class - AbstractChildClassOne");
}
}
AbstractChildClassTwo.java
package AbstractPractice;
class AbstractChildClassTwo extends AbstractParentsClass {
@Override
void A() {
System.out.println("AA");
}
@Override
void B() {
System.out.println("BB");
}
@Override
void AB() {
System.out.println("Method Overriding");
}
void ChildTwo() {
System.out.println("Abstract Child Class - AbstractChildClassTwo");
}
}
테스트
package AbstractPractice;
public class AbstractClassTest {
public static void main(String[] args) {
AbstractParentsClass practiceTestOne = new AbstractChildClassOne();
practiceTestOne.A();
practiceTestOne.B();
practiceTestOne.AB();
practiceTestOne.Alphabet();
AbstractParentsClass practiceTestTwo = new AbstractChildClassTwo();
practiceTestTwo.A();
practiceTestTwo.B();
practiceTestTwo.AB(); // Call the overriding method
practiceTestTwo.Alphabet();
}
}