* com.eomcs.oop.ex03 자바 스태틱 인스턴스 변수 및 블록 초기화 문법
매우 중요하다.. ctrl + c ctrl + v 만 잘한다고 중요한 것이 아니라 이러한 문법을 잘 아는 지가 중요하다.
프로그램을 실행하고 class 파일을 살펴보면서 각 코드가 언제 실행되고 어떻게 변하는 지를 잘 파악하려고
노력해야겠다.
스태틱 변수
1. A클래스의 스태틱 변수를 불러올 때 클래스가 로딩된다.
2. A클래스의 메소드 실행. 클래스 로딩
3. new명령어를 통해 인스턴스를 만드려는 시점. 클래스가 로딩된다.
[상세한 설명]
A.v1 = 100;와 같이 A 클래스의 스태틱 변수를 참조하려 할 때, 해당 클래스가 로딩됩니다.
스태틱 변수에 접근하기 위해서는 해당 클래스가 메모리에 로드되어 있어야 합니다.
A.m1();과 같이 A 클래스의 메소드를 실행하려 할 때도, 해당 클래스가 로딩됩니다.
메소드를 호출하기 위해서는 메소드가 정의된 클래스가 로드되어 있어야 합니다.
obj = new A();와 같이 A 클래스의 인스턴스를 생성하려 할 때에도 클래스가 로딩됩니다.
인스턴스를 생성하기 위해서는 해당 클래스의 정보가 메모리에 존재해야 합니다.
예제)
그러나 클래스는 한번만 로딩되기 때문에 위에 이미 로딩되었다면
System.out.println 은 더 이상 출력되지 않는다.
예제)
클래스가 로딩되지 않았을 때 ,
Class.forName("bitcamp.test.A") 을 통해 클래스를 로딩할 수 있다.
예제)
스태틱 블록
예제처럼. 하나의 클래스 안에 여러개의 스태틱 블록을 만드는 것은 옳지 않다.
컴파일러는 하나의 스태틱 블록으로 인식하기 때문이다.
예제)
만약 컴파일러가 실행하게 된다면 아래와 같이 실행된다.
예제)
초기화 필드 선언이 먼저 실행되고
하나의 static 블록 안으로 나머지 코드가 이동된다.
예제)
package bitcamp.test;
class A {
static int v1; // 변수선언만 실행
static int v2;
static {
v1 = 111; // 초기화가 아래로 옮겨짐
v1 = 100;
System.out.println("A 클래스의 스태틱 블록 실행1 !");
v1 = 200;
System.out.println("A 클래스의 스태틱 블록 실행2 !");
v1 = 300;
System.out.println("A 클래스의 스태틱 블록 실행3 !");
System.out.println(v1);
v2 = 222;
}
static void m1() {}
}
public class Exam01 {
public static void main(String[] args) {
A obj; // 레퍼런스를 생성할 때는 클래스 로딩이 되지 않는다.
// A.v1 = 100; // 1. A클래스의 스태틱 변수를 불러올 때 클래스가 로딩된다.
// A.m1(); // 2. A클래스의 메소드 실행. 클래스 로딩
// obj = new A(); // 3. new명령어를 통해 인스턴스를 만드려는 시점. 클래스가 로딩된다.
try {
Class.forName("bitcamp.test.A"); // 4. 클래스가 로딩되지 않았을 때 로딩하는 역할
} catch (ClassNotFoundException e) {
System.out.println("클래스를 못찾네!");
}
}
}
생성자 문법 (인스턴스 블록)
1. 생성자의 변수 초기화 문장은 변수선언만 남는다.
2. 각각의 생성자의 맨 앞으로 변수 선언이 이동한다.
3. 인스턴스 변수 선언은 모든 생성자의 블록 안으로 이동한다.
[상세한 설명]
생성자의 변수 초기화 문장은 변수 선언만 남습니다. 생성자는 객체가 생성될 때 호출되며,
해당 클래스의 인스턴스 변수를 초기화하는 역할을 합니다.
주어진 코드에서 생성자의 변수 초기화 문장은 v1 = 100;과 v2 = 200;입니다.
각각의 생성자의 맨 앞으로 변수 선언이 이동합니다.
생성자의 맨 앞에 변수 선언이 위치하게 됩니다.
주어진 코드에서 변수 선언은 int v1;과 int v2;입니다.
인스턴스 변수 선언은 모든 생성자의 블록 안으로 이동합니다.
주어진 코드에서 인스턴스 변수 선언인 int v1;과 int v2;는 모든 생성자의 블록 안에 위치하게 됩니다.
따라서 생성자가 호출될 때마다 변수가 초기화되는 구조가 됩니다.
3번으로 인해 각 생성자 실행마다 인스턴스 변수가 실행됨.
* com.eomcs.oop.ex05 상속
# 기능 추가하기
1) 기존 클래스에 코드를 추가하는 방법
- 기존 코드를 변경하게 되면 원래 되던 기능도 오류가 발생할 수 있는 위험이 있다.
- 그래서 원래 코드를 손대는 것은 매우 위험한 일이다.
- 기존에 잘 되던 기능까지 동작이 안되는 문제가 발생할 수 있기 때문이다.
2) 기존 코드를 복사하여 새 클래스를 만드는 방법
- 장점
- 기존 코드를 손대지 않기 때문에 문제가 발생할 가능성은 줄인다.
- 단점
- 기존 코드의 크기가 큰 경우에는 복사 붙여넣기가 어렵다.
- 기존 클래스의 소스가 없는 경우에는 이 방법이 불가능하다.
엥? 다른 개발자가 배포한 라이브러리만 있는 경우를 말한다.
소스가 없는 다른 개발자가 만든 클래스에 기능을 덧 붙일 때는 이 방법이 불가능하다.
- 기존 코드에 버그가 있을 때 복사 붙여넣기 해서 만든 클래스도 영향을 받는다.
- 기존 코드를 변경했을 때 복사 붙여넣기 한 모든 클래스를 찾아 변경해야 한다.
3) 기존 코드를 상속 받아 기능을 추가하는 방법
- 장점
- 기존 클래스의 소스 코드가 필요 없다.
- 간단한 선언으로 상속 받겠다고 표시한 후 새 기능만 추가하면 된다.
- 단점
- 일부 기능만 상속 받을 수 없다.
- 쓰든 안쓰든 모든 기능을 상속 받는다.
// 상속 - 상속하지 않고 기능 추가 I
package com.eomcs.oop.ex05.b;
public class Exam01 {
public static void main(String[] args) {
// 새 프로젝트에서는 제조사, 모델명, 수용인원 외에
// 썬루프 장착여부, 자동변속 여부를 추가적으로 저장하고 싶다!
Car c1 = new Car("비트자동차", "티코", 5, true, true);
}
}
// 방법1) 기존의 Car 클래스를 변경한다
// 문제점:
// => 기존 소스를 변경하게 되면 기존의 Car를 사용해서 만든 프로그램들도
// 영향을 받는다.
// => 심각한 오류가 발생할 수 있다.
// => 그래서 기존의 소스 코드를 손대는 것은 매우 위험하다.
// => 그리고 계속 코드를 덧 붙이다 보면 누더기 코드가 될 가능성이 있다.
// => 쓰지도 않는 코드가 계속 누적되는 문제가 있다.
// 상속 - 상속하지 않고 기능 추가 II
package com.eomcs.oop.ex05.c;
public class Exam01 {
public static void main(String[] args) {
// 새 프로젝트에서는 제조사, 모델명, 수용인원 외에
// 썬루프 장착여부, 자동변속 여부를 추가적으로 저장하고 싶다!
Car2 c = new Car2("비트자동차", "티코", 5, true, true);
}
}
// 방법2) 기존 코드를 복사하여 새 클래스(Car2)를 만든다.
// 문제점:
// => 같은 일을 하는 여러 클래스가 존재(코드가 중복됨)하게 되면 관리하기가 힘들다!
// => 만약 원본 코드에 버그가 있으면 버그도 복사하게 된다.
// 따라서 버그를 고칠 때는 복사한 모든 소스 파일을 찾아 고쳐야 한다.
// 상속 - 상속 문법을 이용한 기능 추가
package com.eomcs.oop.ex05.d;
public class Exam01 {
public static void main(String[] args) {
// 새 프로젝트에서는 제조사, 모델명, 수용인원 외에
// 썬루프 장착여부, 자동변속 여부를 추가적으로 저장하고 싶다!
Sedan c = new Sedan("티코", "비트자동차", 5, true, true);
}
}
// 방법3) 상속을 이용하여 기능을 추가한 클래스를 사용한다.
// 장점:
// => 기존 코드에 문제가 있으면 그 코드를 수정하는 순간
// 그 코드를 상속 받아 만든 모든 클래스에 자동으로 적용된다.
// => 기존 코드를 손대지 않기 때문에 새 기능을 추가하더라도
// 기존 기능에 문제가 발생할 가능성이 거의 없다.
// => 소스 코드의 유지보수가 쉽다.
// 이것이 상속이라는 문법이 등장한 이유이다.
// => 즉 기존 코드의 수정을 최소화하면서 새 기능을 추가하는 방법!
// => 기존 기능을 재작성하지 않고 다시 사용할 수 있게 만드는 문법이다.
// => 코드 재사용성을 높인다.
* 상속과 객체 및 인스턴스 변수 사용
단 상속을 받아 생성한 객체가 인스턴스 변수를 사용하기위해서는컴파일러가 인식할 수 있게 인스턴스 변수가 어디에 있는 지클래스를 알려주어야한다.
ex) B obj = new D();D의 설계도를 바탕으로 B의 객체를 생성하게되면,B는 D의 부모 클래스라서 가능하다.
하지만 B는 상속받은 A의 인스턴스 변수를 갖고 있지만
obj.m1()
C와 D에 대한 인스턴스 변수가 없기 때문에 ((D)obj).m4();이렇게 표현해주어야한다.
package com.eomcs.oop.ex05.f;
public class Exam01 {
public static void main(String[] args) {
// B 클래스의 설계도에 따라 Heap 영역에 변수를 준비한다.
// - B 클래스는 A 클래스에 기능을 덧붙인 것이라 선언했기 때문에
// A 클래스의 설계도에 따라 A 클래스에 선언된 인스턴스 변수도 함께 생성된다.
B obj = new B();
obj.v2 = 200; // B 클래스 설계도에 따라 만든 변수
obj.v1 = 100; // A 클래스 설계도에 따라 만든 변수
System.out.printf("v2=%d, v1=%d\n", obj.v2, obj.v1);
System.out.println("---------------------------------");
// 클래스는 오직 한 번만 로딩된다.
// - 그래서 static 블록도 위에서 한 번 실행되면 다시 실행하지 않는다.
//
B obj2 = new B();
obj2.v2 = 2000;
obj2.v1 = 1000;
System.out.printf("v2=%d, v1=%d\n", obj2.v2, obj2.v1);
}
}
// B 클래스의 인스턴스 생성 과정
// 1) B의 수퍼 클래스가 로딩되어 있지 않다면, 수퍼 클래스(A 클래스)를 먼저 로딩한다.
// - 스태틱 필드 생성한 후
// - 스태틱 블록 실행한다.
// 2) 그런 후 B 클래스를 로딩한다.
// - 스태틱 필드 생성한 후
// - 스태틱 블록 실행한다.
// 3) 인스턴스 필드 생성
// - 수퍼 클래스의 인스턴스 필드부터 생성한다.
// v1 | v2 : A의 v1 필드 생성, B의 v2 필드 생성
// 0 | 0 : 각 필드를 기본 값으로 설정한다.
// 100 | 0 : A 클래스의 생성자 수행
// 100 | 200 : B 클래스의 생성자 수행
//
// - B 클래스의 인스턴스는 수퍼 클래스의 인스턴스 필드도 포함한다.
//
// 인스턴스 생성 절차 정리!
// 1) 상속 받은 수퍼 클래스를 먼저 메모리에 로딩한다.
// 이미 로딩되어 있다면 다시 로딩하지는 않는다.
// 2) 그런 후 해당 클래스를 메모리에 로딩한다.
// 마찬가지로 이미 로딩되어 있다면 다시 로딩하지는 않는다.
// 3) 수퍼 클래스에 선언된 대로 인스턴스 변수를 Heap에 만든다.
// 4) 해당 클래스에 선언된 대로 인스턴스 변수를 Heap에 만든다.
// 5) 수퍼 클래스부터 생성자를 실행하며 해당 클래스까지 내려온다.
// 그래서 인스턴스를 생성할 때는 항상 상속 받아야 하는 클래스 파일이 모두 있어야 한다.
// 테스트 하는 방법?
// => A.class 파일을 제거하고 실행해 보라!
// 용어 정리!
// 상속(inheritance)
// - 기존에 만든 클래스를 자신의 코드처럼 사용하는 기법이다.
// - 보통 기존 코드를 손대지 않고 새 코드를 덧붙일 때 많이 사용한다.
//
// 수퍼클래스(super class) = 부모클래스(parents class)
// - B 클래스 입장에서, B 클래스에게 상속 해주는 A 클래스를 말한다.
//
// 서브클래스(sub class) = 자식클래스(child class)
// - A 클래스 입장에서, A 클래스가 상속해주는 B 클래스를 말한다.
//
// 즉 수퍼 클래스나 서브 클래스는 상대적인 개념이다.
// 상속 - 생성자 호출 순서
package com.eomcs.oop.ex05.g;
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 클래스의 생성자를 실행한다.
// 상속 - 다중 상속
package com.eomcs.oop.ex05.i;
public class Exam01 {
public static void main(String[] args) {
C obj = new C(); // ?
}
}
// 만약 C 클래스가 A와 B를 모두 상속 받을 수 있다면,
// C 클래스의 인스턴스를 만들 때
// v2 변수는 A 설계도에 따라 float 메모리를 만들어야 하는가?
// 아니면 B 설계도에 따라 int 메모리를 만들어야 하는가?
// 메서드의 경우도 마찬가지이다.
// 다중 상속이 가능하다고 가정하자.
// 두 개의 수퍼 클래스에 같은 이름의 메서드가 있을 때,
// 어떤 메서드를 호출해야 하는가?
// 그래서 자바는 클래스의 다중 상속을 지원하지 않는다.
package com.eomcs.oop.ex05.m;
public abstract class Car {
public Car() {
super();
}
public void start() {
System.out.println("시동 건다!");
}
public void shutdown() {
System.out.println("시동 끈다!");
}
public void run() {
System.out.println("달린다.");
}
}
// 이렇게 Sedan과 Truck의 경우처럼
// 여러 클래스의 공통점을 추출하여 수퍼 클래스를 정의하는 경우,
// 그 수퍼 클래스의 목표는 서브 클래스의 공통 기능을 물려주는 것이다.
// 처음부터 Car를 먼저 만들어 쓰다가 더 특별한 클래스가 필요해서
// Sedan이나 Truck을 만든 것이 아니라,
// 여러 클래스를 사용하다 보니 공통 분모가 보여서,
// 소스 코드의 유지보수가 쉽도록
// 한 클래스로 모아두기 위해 만든 경우는
// 해당 클래스를 직접 사용할 이유가 없다.
//
// 특히 generalization을 수행하여 만든 수퍼 클래스의 경우
// 직접 사용할 목적으로 만든 클래스가 아니다.
// 단지 서브 클래스에 공통 기능을 물려주기 위해 존재하는 클래스이다.
// 이런 클래스들은 직접 사용하지 못하게 막는 것이 좋다.
//
// 클래스를 직접 사용하지 못하게 막고
// 단지 서브 클래스를 만들어 사용하도록 제한하는 문법이
// "추상 클래스" 이다.
//
// 추상 클래스
// => 서브클래스에게 공통 기능을 상속해주는 목적으로 만든 클래스이다.
// => 직접 사용하지 않는 클래스이다.
// => 즉 개발자에게 이 클래스를 상속 받아 새 클래스를 만들어 쓰라는 의미다!
// => 보통 '일반화(generalization)' 과정에서 생성되는 클래스를 추상 클래스로 만든다.
// => 문법:
// abstract class 클래스명 {...}
//
// 상속 - 추상 메서드
package com.eomcs.oop.ex05.n;
// Car 클래스의 start()와 shutdown()은 서브 클래스에서 그대로 받아 사용해도 된다.
// 그러나 run() 메서드는 서브 클래스마다 자신의 특징에 맞춰 재정의해야 한다.
// 그렇다면 굳이 수퍼 클래스에서 run() 메서드를 구현할 필요가 없지 않는가?
// run()처럼 서브 클래스에서 무조건 재정의되어야 하는 메서드인 경우
// 즉 수퍼 클래스에서 정의하지 않고 서브클래스에 반드시 정의하도록 강제하는 문법이
// "추상메서드(abstract method)"이다.
//
public class Exam01 {
public static void main(String[] args) {
Sedan car1 = new Sedan();
car1.run();
}
}
단, 수퍼클래스에서 abstract 메소드가 있다면 반드시 서브클래스가
해당 시그니처를 가진 메서드가 존재해야한다.
왜냐하면 수퍼클래스는 추상 메서드를 갖고 있기 때문이다.
// 다형성 - 다형적 변수와 instanceof 연산자
package com.eomcs.oop.ex06.a;
public class Exam0510 {
public static void main(String[] args) {
Vehicle v = new Sedan();
System.out.println(v instanceof Sedan);
System.out.println(v instanceof Car);
System.out.println(v instanceof Vehicle);
System.out.println(v instanceof Object);
System.out.println(v instanceof Truck);
System.out.println(v instanceof Bike);
}
}
// instanceof 연산자?
// => 레퍼런스에 들어있는 주소가 특정 클래스의 인스턴스인지 검사한다.
// => 또는 그 상위 | 상속받는 하위 클래스의 인스턴스인지 검사한다.
// 다형성 - 다형적 변수의 활용
package com.eomcs.oop.ex06.a;
public class Exam0521 {
public static void main(String[] args) {
// 수퍼 클래스의 레퍼런스는 하위 클래스의 인스턴스를 담을 수 있다.
Vehicle[] arr = new Vehicle[] {
new Car("비트자동차", 5, 1980, 16),
new Bike("캠프모터", 5, true),
new Sedan("현대자동차", 5, 1980, 16, true, true),
new Truck("기아자동차", 5, 10000, 32, 15f, true)};
for (int i = 0; i < arr.length; i++) {
printCar(arr[i]);
}
}
static void printCar(Vehicle v) {
System.out.printf("모델: %s\n", v.model);
System.out.printf("수용인원: %s\n", v.capacity);
if (v instanceof Car) {
Car c = (Car) v;
System.out.printf("cc: %s\n", c.cc);
System.out.printf("밸브: %s\n", c.valve);
if (v instanceof Sedan) {
Sedan s = (Sedan) v;
System.out.printf("썬루프: %b\n", s.sunroof);
System.out.printf("오토: %b\n", s.auto);
} else if (v instanceof Truck) {
Truck t = (Truck) v;
System.out.printf("톤: %f\n", t.ton);
System.out.printf("덤프여부: %b\n", t.dump);
}
} else if (v instanceof Bike) {
Bike b = (Bike) v;
System.out.printf("engine: %b\n", b.engine);
}
System.out.println("-----------------------");
}
}
첫째로 sedan 일 경우에는 해당 클래스 sedan과 상위클래스인 car, vehicle, object가 True이다.
두번째 예제로 vehicle 일 경우에는 vehicle과 하위 클래스인 bike, car, sedan, truck 이 모두 포함된다.
// 오버라이딩(overriding) - this/super의 사용
this.m1()은 현재 클래스를 기준으로 m1()을 찾는다.
super.m1()은 수퍼 클래스를 기준으로 m1()을 찾는다.
static class X4 extends X3 {
@Override
void m1() {
System.out.println("X4의 m1()");
}
void test() {
this.m1(); // X4의 m1() : this가 실제 가리키는 인스턴스 클래스를 기준으로 메서드를 찾아 올라 간다.
super.m1(); // X2의 m1() : test()가 소속된 클래스를 기준으로 수퍼 클래스부터 메서드를 찾아 올라간다.
this.m2(); // X3의 m2()
super.m2(); // X3의 m2()