- 서브 클래스 실행시, 수퍼 클래스부터 로딩되고 그 다음 서브 클래스가 로딩된다.
- 인스턴스 필드 생성 전에 스태틱 필드와 블럭부터 실행됨을 명심하자.
- 인스턴스 필드 생성: 수퍼 클래스의 인스턴스 변수부터 생성한다.
B obj = new B();
스태틱 필드와 블럭이 생성자보다 먼저 실행된다.
즉, 서브 클래스가 로딩되기 전에 수퍼 클래스부터 로딩되는데, 클래스가 로딩될 때 스태틱도 함께 로딩되기 때문에 수퍼 클래스의 스태틱 부터 로딩되게 된다.
- 모든 클래스 생성자의 첫 문장은 반드시 수퍼클래스의 생성자 호출 문장이어야 한다.
또는, 같은 클래스의 다른 생성자를 호출하는 문장이어야 한다.- 즉, super()나 this()로 생성자의 첫문장이 시작되어야 한다.
⇒ 그러나, 밑에와 같이 둘 다 동시에 호출할 수는 없다.
그럼 둘 중 하나는 첫 문장이 아니게 되기 때문이다!
super()
this()
this()
super()
- 수퍼 클래스가 현재 클래스에 extends형태로 나타나 있더라도, 수퍼 클래스의 생성자 호출을 생략하면,
컴파일러가 자동으로 수퍼 클래스의 기본 생성자를 호출하라는 명령을 생성자의 첫 줄에 추가한다.
⇒ super()가 생략되어 있다면, 컴파일러가 현재 클래스 생성자에 super()를 자동으로 붙여버린다.
하위 클래스부터 생성자가 실행된다. 수퍼 클래스부터 생성자가 실행되는 것이 아니다! 단지, 서브 클래스의 생성자의 첫 문장이 super()라 타고 올라가다보니, 최상위 클래스의 생성자부터 먼저 출력되고 그 뒤, 상위부터 리턴되며 차근차근 하위 클래스의 생성자가 실행되는 것이다!
예시
A.java
public class A /*extends Object*/ {
int v1;
A() {
// 수퍼 클래스의 어떤 생성자를 호출할지 지정하지 않으면 컴파일러는
// 다음과 같이 수퍼 클래스의 기본 생성자를 호출하라는 명령을
// 생성자의 첫 줄에 추가한다.
super(); // 즉 개발자가 붙이지 않으면 자동으로 붙인다.
// 헐.. 강사님, A 클래스의 수퍼 클래스는 없는데요?
// => 클래스를 정의할 때 수퍼 클래스를 지정하지 않으면,
// 컴파일러는 자동으로 수퍼 클래스를 java.lang.Object 클래스로 지정한다.
// => 그래서 자바의 모든 클래스는 반드시 수퍼 클래스가 있으며,
// 자바의 모든 클래스는 java.lang.Object의 자손 클래스가 된다.
System.out.println("A() 생성자!");
this.v1 = 100;
}
static {
System.out.println("static A");
}
}
public class B extends A {
int v2;
B() {
// 수퍼 클래스의 어떤 생성자를 호출할지 지정하지 않으면 컴파일러는
// 다음과 같이 수퍼 클래스의 기본 생성자를 호출하라는 명령을
// 생성자의 첫 줄에 추가한다.
super(); // 즉 개발자가 붙이지 않으면 자동으로 붙인다.
System.out.println("B() 생성자!");
this.v2 = 200;
}
static {
System.out.println("static B");
}
}
public class C extends B {
int v3;
C() {
// 수퍼 클래스의 어떤 생성자를 호출할지 지정하지 않으면 컴파일러는
// 다음과 같이 수퍼 클래스의 기본 생성자를 호출하라는 명령을
// 생성자의 첫 줄에 추가한다.
// super(); // 즉 개발자가 붙이지 않으면 자동으로 붙인다.
System.out.println("C() 생성자!");
this.v3 = 300;
// 만약 개발자가 수퍼 클래스의 생성자를 호출하는 명령을 작성할 때
// 그 전에 다른 코드가 있다면 컴파일러는 오류를 발생시킨다.
// super(); // 따라서 수퍼 클래스 생성자를 호출하는 명령은 반드시 첫 문장으로 와야 한다.
}
static {
System.out.println("static C");
}
}
public class Exam01 {
public static void main(String[] args) {
C obj = new C();
System.out.printf("v1=%d, v2=%d, v3=%d\n", obj.v1, obj.v2, obj.v3);
// 생성자 호출 순서
// 1) C 클래스의 생성자를 호출하면,
// 그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
// 그래서 수퍼 클래스인 B 클래스의 생성자를 호출한다.
// 2) B 클래스의 생성자를 호출하면,
// 그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
// 그래서 수퍼 클래스 A의 생성자를 호출한다.
// 3) A 클래스의 생성자를 호출하면,
// 그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
// 그래서 수퍼 클래스 Object의 생성자를 호출한다.
// 4) Object 클래스의 생성자를 호출하면,
// 더이상 수퍼 클래스가 없기 때문에 Object() 생성자를 실행한다.
// 그리고 이 생성자를 호출한 A 클래스의 생성자로 리턴한다.
// 5) A 클래스의 생성자를 실행한 후
// 이 생성자를 호출한 B 클래스의 생성자로 리턴한다.
// 6) B 클래스의 생성자를 실행한 후
// 이 생성자를 호출한 C 클래스의 생성자로 리턴한다.
// 7) C 클래스의 생성자를 실행한다.
}
}
위의 코드 모식도
다음 코드를 메인에서 실행할 때 순서도
C obj = new C();
상위 클래스 순서로 클래스 로딩 + 스태틱 실행
C 클래스의 생성자부터 super()를 통해 상위 생성자를 타고 올라가며 실행
다시 하위 클래스로 내려가며 생성자 코드 실행하기
C클래스의 인스턴스 생성
⇒ 즉, A클래스의 생성자의 코드가 먼저 출력된다고 A 클래스의 생성자가 먼저 실행되는 것이 아니라, C클래스의 생성자부터 실행이 되지만, super()를 통해 타고 올라가서 결국 A클래스의 생성자가 실행이 되어 A 생성자의 코드가 먼저 출력되는 것이다!
최하위 클래스 인스턴스를 new를 통해 생성 명령어 실행 ->수퍼 클래스 로딩 -> 수퍼 클래스의 static부터 실행
-> 서브 클래스의 생성자 실행 -> super()를 통해 수퍼 클래스의 생성자까지 올라가서 실행 -> 수퍼 클래스의 인스턴스부터 만들기
추상 메서드
- 메서드 body를 만들지 않는다.
- run()메서드 처럼 어차피 sub 클래스에서 재정의 해야할 메서드라면, 수퍼 클래스에서 정의하지 말자!
⇒ 메서드 선언, 메서드 형식만 정의해 놓자!
⇒ Method signature(function prototype)
→ 메서드명, 파라미터 타입, 리턴 타입만 설정- abstruct class는 인스턴스를 만들 수 없다!
// 추상 클래스
// => 서브클래스에게 공통 기능을 상속해주는 목적으로 만든 클래스이다.
// => 직접 사용하지 않는 클래스이다.
// => 즉 개발자에게 이 클래스를 상속 받아 새 클래스를 만들어 쓰라는 의미다!
public abstract class Car {
public Car() {
super();
}
public void start() {
System.out.println("시동 건다!");
}
public void shutdown() {
System.out.println("시동 끈다!");
}
// 추상 메서드
// => 서브 클래스에서 재정의할 메서드라면 굳이 수퍼 클래스에서 구현하지 말라!
// => 또는 서브 클래에서 구현하도록 강제하고 싶다면 그때 해당 메서드를 추상 메서드로 선언한다.
// => 추상 메서드를 상속 받는 서브클래스는 반드시 구현해야 한다.
// 만약 구현하지 않으면 추상 메서드인채로 남아 있기 때문에
// 서브클래스도 추상클래스가 되어야 한다.
// 일반 클래스는 인스턴스를 생성하여 메서드를 호출하기 때문에
// 구현되지 않은 메서드를 갖는 것은 오류이다.
// 그래서 일반 클래스는 추상 메서드를 가질 수 없다.
// => 왜? 추상 메서드가 있다는 것은 해당 메서드를 실행할 수 없다는 것이고
// 실행할 수 없는 메서드를 갖는 클래스는
// 인스턴스를 생성해서는 안되기 때문에
// 추상메서드를 갖는 클래스는 반드시 추상클래스여야 한다.
// 일반 클래스는 추상메서드를 가질 수 없다.
//
public abstract void run();
}
public class Sedan extends Car {
@Override
public void run() {
System.out.println("쌩쌩 달린다.");
}
public void doSunroof(boolean open) {
if (open) {
System.out.println("썬루프를 연다.");
} else {
System.out.println("썬루프를 닫는다.");
}
}
}
public class Calculator {
public int result;
public void plus(int value) {
this.result += value;
}
public void minus(int value) {
this.result -= value;
}
}
public class Calculator2 {
// plus(), minus()는 기존의 Calculator 클래스에게 위임한다.
com.eomcs.oop.ex05.x1.Calculator origin = new com.eomcs.oop.ex05.x1.Calculator();
public void plus(int value) {
// 이 클래스가 포함하고 있는 객체에게 실행을 위임한다.
origin.plus(value);
}
public void minus(int value) {
// 이 기능은 기존의 클래스가 처리하도록 기존 객체에게 위임한다.
origin.minus(value);
}
// 새 기능 또한 기존 객체의 필드를 사용하여 처리한다.
public void multiple(int value) {
origin.result *= value;
}
public int getResult() {
return origin.result;
}
}
public class CalculatorTest {
public static void main(String[] args) {
Calculator2 c = new Calculator2();
// Calculator 객체를 내장하고 있다.
//Calculator2와 Calculator의 생명주기가 같다.
c.plus(100);
c.minus(200);
c.multiple(2);
System.out.println(c.getResult());
}
}
public class Calculator2 {
// 의존 객체를 외부에서 주입받는다.
com.eomcs.oop.ex05.x1.Calculator origin;
public Calculator2(com.eomcs.oop.ex05.x1.Calculator origin) {
this.origin = origin;
}
public void plus(int value) {
// 이 클래스가 포함하고 있는 객체에게 실행을 위임한다.
origin.plus(value);
}
public void minus(int value) {
// 이 기능은 기존의 클래스가 처리하도록 기존 객체에게 위임한다.
origin.minus(value);
}
// 새 기능 또한 기존 객체의 필드를 사용하여 처리한다.
public void multiple(int value) {
origin.result *= value;
}
public int getResult() {
return origin.result;
}
}
// 5) 계산기 기능 확장 방법5 - 의존 객체 주입 방식을 적용하여 기능 확장
// - 곱하기 계산 기능 추가
// - 기존의 Calculator 객체를 포함한 후 새 기능을 추가하기
//
public class CalculatorTest {
public static void main(String[] args) {
com.eomcs.oop.ex05.x1.Calculator calculator = new com.eomcs.oop.ex05.x1.Calculator();
Calculator2 upgradeCalculator = new Calculator2(calculator);
upgradeCalculator.plus(100);
upgradeCalculator.minus(200);
upgradeCalculator.multiple(2);
System.out.println(upgradeCalculator.getResult());
}
}
class B extends A{
}
class B {
A obj;
}
class B {
A obj;
}
class B {
A obj;
}
class B {
void m(A obj){
}
}
UML 참고:
https://www.nextree.co.kr/p6753/