JAVA

자바의 정석(기초편) - Ch6. 객체지향프로그래밍Ⅰ정리

광터틀 2022. 7. 25. 14:37

자바의 정석(기초편) CHAPTER6. 객체지향프로그래밍 Ⅰ을 읽으며 헷갈리는 부분, 몰랐던 부분들을 정리해봤다.

글의 모든 내용, 문장, 코드는 『자바의 정석(기초편)』, 도우출판, 남궁성 을 기반으로 한다.

 

객체지향개념 Ⅰ, Ⅱ 목차

객체지향프로그래밍 Ⅰ 객체지향프로그래밍 Ⅱ
1) 객체지향언어란? 1) 상속
2) 클래스와 객체 2) 오버라이딩
3) 변수와 메서드 3) package 와 import 
4) 메서드 오버로딩 4) 제어자
5) 생성자 5) 다형성
6) 변수의 초기화 6) 추상클래스
  7) 인터페이스

 

1) 객체지향언어란?

객체지향언어의 장점 : 코드의 재사용성이 높고 유지보수가 용이하다.


2) 클래스와 객체

클래스 : 객체를 정의 해놓은 설계도. 객체를 생성한다.

ex) 제품 설계도, 붕어빵 기계

객체(인스턴스를 아우르는 말) : 실제 존재하는 사물 or 개념

ex) 제품, 붕어빵

 

객체는 속성과 기능으로 이루어져 있다.

(속성->변수. 기능->메서드)

즉, 클래스는 변수와 메서드로 정의된다.

 

밑에 코드는 TV 클래스 안에 변수 세개(color, power, channel)와 메서드 세개(power(), channelUp(), channelDown())을 정의해놓은 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class TV {
    String color;
    boolean power;
    int channel;
    //위 세 줄은 속성(변수)
    
    void power(){
        power != power;
    }
    void channelUp(){
        channel++;
    }
    void channelDown(){
        channel--;
    }
    //위 세 줄은 기능(메서드)
    
}
 
 
 
cs

 


 

객체배열 : 많은 수의 객체를 다룰 때, 배열로 다루기 위해 만드는 배열.

(객체 배열 안에 객체가 저장되는 것이 아닌, 객체의 주소가 저장되는 것이다. 즉, 사실상 객체배열은 참조변수들을 하나로 묶은 참조변수 배열인 것이다.)

 

밑에 코드는 참조변수 배열을 생성한 후, 객체를 생성해서 배열의 각 요소에 저장하는 과정이다.

1
2
3
4
5
6
Tv[] tvArr = new Tv[3];//참조변수 배열(객체배열) 생성. 아직 객체를 생성한건 아님!
 
//객체를 생성해서 배열의 각 요소에 저장
tvArr[0= new Tv();
tvArr[1= new Tv();
tvArr[2= new Tv();
cs

 

클래스의 정의 : 객체를 생성하기 위한 틀이며 속성과 기능으로 정의되어있다.

이는 객체지향이론의 관점에서 내린 정의이다. 그렇다면 프로그래밍 관점에서는?

1) 데이터와 함수의 결합

2) 사용자 정의 타입 : 프로그래머가 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자 정의 타입이라고 하며 기본형은 8개로 정해져있는 반면, 참조형의 개수가 정해져있지 않은 것도 프로그래머가 새로운 타입을 추가할 수 있기 때문이다.

 


 

3) 변수와 메서드

변수는 클래스 변수, 인스턴스 변수, 지역변수 세 종류가 있다. 

변수의 종류 설명 선언위치 생성시기
클래스 변수
(class variable)
멤버변수, static이 붙은 것

인스턴스 변수와는 달리, 모든 인스턴스가 공통된 저장공간(변수)를 공유하게 된다
클래스 영역 클래스가 메모리에 올라갈 때
인스턴스 변수
(instance variable)
멤버변수, static이 붙지 않은 것

인스턴스마다 별도의 저장공간을 가지므로 서로 다른 값을 가질 수 있다
클래스 영역 인스턴스가 생성되었을 때
지역변수
(local variable)
멤버변수 아님
메서드 내에서 선언되어 메서드 내에서만 사용 가능. 메서드가 종료되면 소멸되어 사용 불가
클래스 영역 이외의 영역
(메서드, 생성자, 초기화 블럭 내부)
변수 선언문이 수행되었을 때

인스턴스 변수는 인스턴스가 생성될 때마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 가진다.

 

밑은 클래스 변수와 인스턴스 변수를 구분하기 좋은 코드이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Ex6_3{
    public static void main(String[] args){
        System.out.println("Card.width = " + Card.width); 
        System.out.println("Card.height = " + Card.height); 
        //width와 height는 클래스 변수로, c1과 c2가 같은 값을 공유한다.
        //클래스 변수(static 변수)는 객체생성 없이 '클래스이름.클래스변수'로 직접 사용 가능
        
        Card c1 = new Card();
        c1.kind = "Heart";
        c1.number = 7;
        
        Card c2 = new Card();
        c2.kind = "Spade";
        c2.number = 4;
        
        System.out.println("c1은 " + c1.kind + ", " + c1.number + 
                        "이며, 크기는 (" + c1.width + ", " + c1.height + ")");
        System.out.println("c2는 " + c2.kind + ", " + c2.number + 
                        "이며, 크기는 (" + c2.width + ", " + c2.height + ")");
        System.out.println("c1의 width와 height를 각각 50, 80으로 변경합니다.");
        
        c1.width=50;
        c1.height=80;
        //width와 height는 static 변수, 즉 클래스 변수이므로 
        //c1의 width와 height만 바꾸어도 c2의 width와 height가 같이 바뀐다.
        
        System.out.println("c1은 " + c1.kind + ", " + c1.number + 
                        "이며, 크기는 (" + c1.width + ", " + c1.height + ")");
        System.out.println("c2는 " + c2.kind + ", " + c2.number + 
                        "이며, 크기는 (" + c2.width + ", " + c2.height + ")");
    }
    
    
}
 
class Card{
    String kind; // 인스턴스 변수 선언
    int number; // 인스턴스 변수 선언
    static int width = 100//클래스 변수 선언 및 초기화
    static int height = 250//클래스 변수 선언 및 초기화
}
 
cs

 

Card 인스턴스인 c1과 c2는 클래스 변수인 width와 height를 공유하므로, c1의 width와 height만 바꿔도 c2의 width와 height가 같이 바뀐다. 즉, Card.width, c1.width, c2.widht 모두 같은 저장공간을 참조하므로 항상 같은 값이다.

 


메서드란?

특정 작업을 수행하는 일령의 문장들을 하나로 묶은 것이다.

int add(int x, int y) 에서 int는 반환타입, add는 메서드 이름, int x, int y 는 매개변수선언에 해당한다.

 


밑에 코드는 인수(argument)와 매개변수(parameter)의 차이를 알아보기 위함이다. (주석참고)

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args){
    ...
    int result = add(3,5); //3,5는 인수(argument, 원본)
    ...
}
 
int add(int x, int y){ //x,y는 매개변수(parameter, 복사본)
    int result = x + y;
    return result;
}
 
 
 
cs

 


반환값 유무에 관계없이 모든 메서드에는 적어도 하나의 return 문이 있어야 한다. 

반환타입이 void인 경우 return문을 생략하는 경우가 있는데, 이때도 컴파일러가 마지막에 return;을 자동으로 추가해주는 것이다.


호출스택은 메서드 작업에 필요한 메모리 공간을 제공한다.

메서드 호출

 호출스택에 메서드를 위한 메모리를 할당

 메서드 작업 동안 지역변수(매개변수), 연산 중간 결과 등 저장

 작업 완료 후 할당됐던 메모리 공간 반환 및 비워짐

호출스택의 제일 위에 있는 메서드가 현재 실행중인 메서드이다.

아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드다.


기본형 매개변수 vs 참조형 매개변수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Data { int x; }
 
public class Ex6_6{
    public static void main(String[] args){
        Data d = new Data();
        d.x = 10;
        System.out.println("main() : x = " + d.x);
        
        change(d.x);
        System.out.println("After change(d.x)");
        System.out.println("main() : x = " + d.x);
    }
    static void change(int x){
        x = 1000;
        System.out.println("change() : x = " + x);
    }
}
cs

결과

1
2
3
4
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 10
cs

매개변수 타입이 기본형일때는 기본형 값이 복사되지만, 참조형일 때는 인스턴스의 주소가 복사된다. 따라서 메서드 매개변수를 기본형으로 선언하면 저장된 값만 얻지만, 참조형으로 선언하면 값이 저장된 곳의 주소를 알 수 있기 때문에 값을 읽어오는 것은 물론 값을 변경하는 것도 가능하다. 즉,

기본형 매개변수 : 변수의 값을 읽기만 할 수 있다.
참조형 매개변수 : 변수의 값을 읽고 변경할 수 있다.

 

위 코드를 보자. change(d.x)를 해준 후에 x 값은 1000으로 변경되지 않고 그대로 10이 나온다. d.x의 값이 변경된 것이 아니라 change() 의 매개변수 x 값이 1000으로 변경된 것이다. main()의 x 원본이 변경된 것이 아니라 change()의 x 복사본만 변경된 것이므로 원본 x는 10으로 변화가 없다.

이는, x가 int 형, 즉 기본형 매개변수이기 때문이다. 다음 코드를 통해 참조형 매개변수일 경우 어떻게 되는지 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Data2 { int x; }
 
public class Ex6_7{
    public static void main(String[] args){
        Data2 d = new Data2();
        d.x = 10;
        System.out.println("main() : x = " + d.x);
        
        change(d);
        System.out.println("After change(d)");
        System.out.println("main() : x = " + d.x);
    }
    static void change(Data2 d){
        d.x = 1000;
        System.out.println("change() : x = " + d.x);
    }
}
cs

결과

1
2
3
4
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 1000
cs

위와는 다르게 결과 코드의 4번째 줄이 10이 아닌 1000이 나왔다. 

위에서 보았던 코드는 change(int x)의 매개변수가 int x 즉, 기본형이었고 이번 코드는 change(Data2 d)의 매개변수가 Data2 d 즉, 참조형이다. 따라서 x 값이 아닌 변수 d의 주소가 매개변수 d에 복사되었다. 이제 main 메서드의 참조변수 d와 change 메서드의 참조변수 d는 같은 객체를 가리키게 된다.

 

 


인스턴스 메서드 vs static 메서드

인스턴스 메서드 : 반드시 객체를 생성해야만 호출할 수 있다. 반면, 매개변수는 필요없다.

클래스 메서드 : 클래스 변수처럼, 객체를 생성하지 않고 '클래스이름.메서드이름(매개변수)' 으로 호출 가능하다. 매개변수만으로 작업이 가능하다.

 

다음 코드로 한번에 이해 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class MyMath2 {
    long a,b;
    
    //인스턴스 매서드. 인스턴스 변수 a,b만을 이용하므로 매개변수가 필요없다.
    long add(){return a + b;} // a, b는 인스턴스 변수
    long subtract() {return a - b;}
    long multiply() {return a * b;}
    double divide() {return a / b;}
    
    //static 메서드. 인스턴스 변수와 상관없이 매개변수만으로 작업이 가능하다.
    static long add(long a, long b) {return a + b;} //a,b는 지역변수
    static long subtract(long a, long b) {return a - b;}
    static long multiply(long a, long b) { return a * b;}
    static double divide(long a, long b) { return a / (double)b;}
}
 
public class Ex6_9{
    public static void main(String args[]){
        //static 메서드 호출. 인스턴스 생성없이 호출 가능
        System.out.println(MyMath2.add(200L, 100L));
        System.out.println(MyMath2.subtract(200L, 100L));
        System.out.println(MyMath2.multiply(200L, 100L));
        System.out.println(MyMath2.divide(200L, 100L));
        
        
        //인스턴스 메서드는 객체생성 후에만 호출이 가능함
        MyMath2 mm = new MyMath2();
        mm.a = 200L;
        mm.b = 100L;
        //인스턴스 메서드 호출.
        System.out.println(mm.add());
        System.out.println(mm.subtract());
        System.out.println(mm.multiply());
        System.out.println(mm.divide());
    }
}
cs

static 메서드들은 호출되기 전에 객체를 생성 안한다는 점과 매개변수를 갖고 작업한다는 점을 알 수 있다.

반면 인스턴스 메서드들은 호출되기 전에 객체를 생성한다는 점과 매개변수가 필요없다는 점을 알 수 있다.

 

그렇다면 언제 static 메서드를 이용하고 언제 인스턴스 메서드들을 이용하는걸까? 의문점이 생겼는데 놀랍게도 다음 페이지의 제목이 "static을 언제 붙여야 할까?" 이었다.

 

static 은 언제 붙여야 할까?

1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.

2. 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static 을 붙일 것을 고려한다.

 


 

메서드 간의 호출과 참조

같은 클래스 내의 메서드는 서로 객체의 생성이나 참조변수 없이 직접 호출이 가능하다.

➔ 인스턴스메서드가 같은 클래스 안의 다른 인스턴스메서드 호출 : 가능

➔ 인스턴스메서드가 같은 클래스 안의 static메서드 호출 : 가능

➔ static메서드가 같은 클래스 안의 static메서드 호출 : 가능

➔ static메서드가 같은 클래스 안의 인스턴스메서드 호출 : 불가능!

 

메서드와 변수 간의 호출과 참조

위와 같지만 static메서드는 인스턴스 변수를 사용할 수 없다.

➔ 인스턴스메서드가 같은 클래스 안의 다른 인스턴스변수 사용 : 가능

➔ 인스턴스메서드가 같은 클래스 안의 static변수 사용 : 가능

➔ static메서드가 같은 클래스 안의 static변수 사용 : 가능

➔ static메서드가 같은 클래스 안의 인스턴스변수 사용 : 불가능!

 


4) 메서드 오버로딩

오버로딩

한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면 같은 이름을 사용해서 메서드를 정의할 수 있다. 이처럼, 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것을 '오버로딩'이라고 한다. 대표적으로 println메서드가 있다.(println()의 매개변수로 int를 넘겨주든, long을 넘겨주든 무엇을 넘겨주든 출력하는데 문제가 없다. 하지만 넘겨주는 매개변수 타입에 따라 호출되는 println() 메서드는 사실 다르다.)

 

하지만, 같은 이름의 메서드를 정의한다고 무조건 오버로딩은 아니다. 다음 세가지 조건을 만족해야 한다.

1. 메서드 이름이 같아야 한다.
2. 매개변수의 개수 또는 타입이 달라야 한다.
3. 반환 타입은 관계없다.

무슨 소리일까? 싶었는데 예제로 한번에 이해가 됐다.

 

보기1

1
2
3
4
5
6
int add(int a, int b) { return a+b; }
int add(int x, int y) { return x+y; }
/*
오버로딩이 아니다. 두 메서드는 정확히 같은 것이다. 
세 가지 조건을 만족하는지 보면 1. 메서드 이름은 같으나 2. 매개변수 개수 또는 타입이 같다.
*/
cs

보기2

1
2
3
4
5
6
7
8
int add(int a, int b) { return a+b; }
long add(int a, int b) { return (long)(a+b); }
/*
1. 메서드 이름은 같고, 2. 매개변수 개수 또는 타입도 같다. 3. 반환타입은 다르다.
즉, 반환타입만 다른건데 이렇다해서 오버로딩이 아니다. 
add(3,3)과 같이 호출되면 어떤 메서드로 호출될지 결정할 수가 없다.
그래서 3. 반환타입은 관계없다라는 말이 나온 것이다.
*/
cs

보기3

1
2
3
4
5
6
7
8
long add(int a, long b) { return a+b; }
long add(long a, int b) { return a+b; }
/*
오버로딩이 맞다.
1. 메서드 이름 같고, 2. 매개변수 개수 또는 타입이 다르다. 3. 반환타입은 관계없다.
단, 이 경우에는 add(3,3)과 같은 것은 호출할 수 없다. 
둘 중 어느 메서드를 호출한 것인지 알수가 없기 때문에 이럴 경우에는 컴파일 에러가 발생한다.
*/
cs

 

밑은 컴파일 에러가 발생하지 않게 add() 메서드를 오버로딩을 이용하며 짠 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Ex6_10 {
    public static void main(String []args){
        MyMath3 mm = new MyMath3();
        System.out.println("mm.add(3, 3) 결과:" + mm.add(3,3));
        System.out.println("mm.add(3L, 3) 결과:" + mm.add(3L,3));
        System.out.println("mm.add(3, 3L) 결과:" + mm.add(3,3L));
        System.out.println("mm.add(3L, 3L) 결과:" + mm.add(3L,3L));
        
        int [] a = {100200300};
        System.out.println("mm.add(a) 결과:" + mm.add(a));
        
        /*
       add()메서드의 매개변수로 (int,int), (long,int), (int,long), (long,long), (array)를 넘겨줬다.
        오버로딩을 이용하여 add()라는 같은 이름의 메서드를 매개변수의 개수와 종류를 다르게하며 
        모든 경우에 문제가 없게 설정해줘야 한다.
        */
    }
}
 
class MyMath3 {
    int add(int a, int b){
        System.out.print("int add(int a, int b) - ");
        return a+b;
    }
    long add(int a, long b){
        System.out.print("long add(int a, long b) - ");
        return a+b;
    }
    long add(long a, int b){
        System.out.print("long add(long a, int b) - ");
        return a+b;
    }
    long add(long a, long b){
        System.out.print("long add(long a, long b) - ");
        return a+b;
    }
    int add(int[] a){
        System.out.print("int add(int[] a) - ");
        int result = 0;
        for (int i=0; i<a.length; i++){
            result += a[i];
        }
        return result;
    }
}
cs

5) 생성자

생성자 : 인스턴스 생성될 때 호출되는 '인스턴스 초기화 메서드'

1. 생성자 이름은 클래스 이름과 같아야 한다.
2. 생성자는 리턴값이 없다.

● 생성자도 오버로딩이 가능하다.

➔ 즉, 하나의 클래스에 여러 개의 생성자가 존재할 수 있다.

 

 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.

(지금까지 생성자를 정의하지 않고 인스턴스를 생성한 건, 컴파일러가 알아서 기본 생성자를 추가해주었기 때문이다.)

기본 생성자가 컴파일러에 의해 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다.

기본 생성자가 아닌 생성자를 만들어놓고, 기본 생성자를 호출하면 에러가 뜬다.

 


매개변수가 없는 생성자와 매개변수가 있는 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Car{
    String color;
    String gearType;
    int door;
    
    Car() {}
    
    Car(String c, String g, int d){
        color = c;
        gearType = g;
        door = d;
    }
}
 
public class Ex6_12 {
    public static void main(String args[]) {
        //Car c1 은 매개변수가 없는 생성자이다. 
        Car c1 = new Car();
        c1.color = "white";
        c1.gearType = "auto";
        c1.door = 4;
        //Car c2 는 매개변수가 있는 생성자이다. 
        Car c2 = new Car("white""auto"4);
        
        System.out.println("c1의 color=" + c1.color + ", gearType=" 
                                + c1.gearType + ", door=" + c1.door);
        System.out.println("c2의 color=" + c2.color + ", gearType=" 
                                + c2.gearType + ", door=" + c2.door);
        //두 println 값은 똑같이 나온다. 
        //즉, 매개변수 없는 생성자 Car c1 과 매개변수 있는 생성자 Car c2가 같은 결과를 부른다.
        
    }
}
cs

생성자에서 다른 생성자 호출하기 - this()

같은 클래스의 멤버들 간 서로 호출이 가능하듯 생성자 간에도 서로 호출이 가능하다.

1. 생성자 이름으로 클래스 이름 대신 this를 사용한다.
2. 한 생성자에서 다른 생성자를 호출할 때, 반드시 첫 줄에서만 호출이 가능하다.

밑에 코드에서 Car2 클래스 안에 Car2라는 이름의 생성자가 세 개가 있다. 그 중, Car2()와 Car2(String color) 이 두 생성자에서 Car2(String color, String gearType, int door) 생성자를 호출하며 호출을 Car2로 하는것이 아니라 this()를 이용함을 볼 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Car2 {
    String color;
    String gearType;
    int door;
    
    Car2(){
        this("white""auto"4);
    }
   //Car2() 의 this()안에 설정된 값으로 인스턴스변수가 초기화된다.
//자동차를 살 때, 아무런 옵션도 안 주면 흰색, 자동변속기어, 문 4개인 자동차가 주문 되듯
//Car2() c1 = new Car2(); 와 같이 Car2() 생성자로 인스턴스를 생성하면
//white, auto, 4로 초기화 되도록 한 것이다.

    Car2(String color){
        this(color, "auto"4);
    }
    /*
    Car2()와 Car2(String color)에서 this()는 
    Car2(String color, String gearType, int door)를 호출하는 것이다.
    즉, 생성자에서 다른 생성자를 호출하는 것이며 이때는 Car2를 쓰는 것이 아니라
    this를 써야 한다.
    */
    Car2(String color, String gearType, int door){
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }
}
 
 
public class Ex6_13{
    public static void main(String[] args){
        Car2 c1 = new Car2();
        Car2 c2 = new Car2("blue");
        
        System.out.println("c1의 color=" + c1.color + ", gearType=" 
                                + c1.gearType + ", door=" + c1.door);
        System.out.println("c2의 color=" + c2.color + ", gearType=" 
                                + c2.gearType + ", door=" + c2.door);
    }
}
 
cs

객체 자신을 가리키는 참조변수 - this

인스턴스 변수 앞에 this를 붙여줌으로서 매개변수로 선언된 변수의 이름과 구분이 되도록 한다.

this.color = color;

에서 this.color 은 인스턴스 변수이고, color 은 생성자의 매개변수로 정의된 지역변수인 것이다.

 

this 와 this()는 비슷하게 생겼을 뿐 전혀 다르다.
여기서 나온 this는 인스턴스 변수를 가리키는 참조변수이다.
위에서 나온 this()는 같은 클래스 안에서 생성자가 다른 생성자를 호출할 때 쓰는 것이다.

6) 변수의 초기화

멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화된다. 

지역변수는 사용하기 전에 반드시 초기화해야 한다.

 

멤버변수의 초기화를 자세히 알아보자.

위에서 배웠다시피 멤버변수에는 클래스변수와 인스턴스변수가 있다. 인스턴스변수보다 클래스변수가 먼저 초기화된다.

그리고 명시적 초기화, 초기화 블럭, 생성자의 순서로 초기화 된다. 즉, 초기화 순서는

1. 클래스 변수(cv) 초기화 ➔ 인스턴스 변수(iv) 초기화
2. 자동 초기화 ➔ 명시적 초기화(간단) ➔ 초기화 블럭, 생성자(복잡)

이다.

 

명시적 초기화 : 변수를 선언과 동시에 초기화 하는 것.

1
2
3
4
5
class Car{
    int door = 4;//기본형 변수의 초기화
    Engine e = new Engine();//참조형 변수의 초기화
    //이렇게 선언(좌항)함과 동시에 초기화(우변) 하는 것을 명시적 초기화라고 한다.
}
cs

 

초기화 블럭 : 클래스 초기화 블럭과 인스턴스 초기화 블럭이 있다.둘 다 단순히 클래스 내에 블럭{}를 만들고 그 안에 코드를 작성하기만 하면 된다.(클래스 초기화 블럭 앞에는 static을 붙인다.) 

 

밑에 코드를 통해 클래스 변수, 인스턴스 변수, 초기화블럭, 생성자 순서로 초기화가 수행됨을 확인하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Ex6_14{
    //클래스 초기화 블럭이다. Ex6_14이 메모리에 로딩될 때 가장 먼저 실행된다.
    static{
        System.out.println("static { }");
    }
    //인스턴스 초기화 블럭이다.
    {
        System.out.println("{ }");
    }
    //생성자이다. 가장 나중에 실행된다.
    public Ex6_14(){
        System.out.println("생성자");
    }
    
    public static void main(String[] args){
        System.out.println("Ex6_14 bt = new Ex6_14(); ");
        Ex6_14 bt = new Ex6_14();
        //Ex6_14의 인스턴스가 생성되며 인스턴스 초기화 블럭이 먼저 수행되고
        //그 후에 생성자가 수행된다.
        
        System.out.println("Ex6_14 bt2 = new Ex6_14(); ");
        Ex6_14 bt2 = new Ex6_14();
        //위와 마찬가지이다. 인스턴스 초기화 블럭 수행 후 생성자
    }
}
 
cs

결과값

1
2
3
4
5
6
7
8
static { }
Ex6_14 bt = new Ex6_14(); 
{ }
생성자
Ex6_14 bt2 = new Ex6_14(); 
{ }
생성자
 
cs

결과값에서 볼 수 있듯이 클래스 초기화 블럭이 가장 먼저 수행됐다.( static { } )

 그 후에 인스턴스 초기화 블럭 ( { } ), 마지막으로 생성자 (생성자)가 수행되었다.

또한, 클래스 초기화 블럭은 처음 메모리에 로딩될 때 한번만 수행되었지만, 인스턴스 초기화 블럭은 인스턴스가 생성될 때마다 수행됐음을 알 수 있다.