1. 객체지향 언어
1.1 객체지향 언어의 역사
- 객체지향 이론의 기본 개념은 ‘실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다.’라는 것이다.
- 실제 사물의 속성과 기능을 분석한 다음 데이터와 함수로 정의하여 실제 세계를 컴퓨터 속에 옮겨 놓은 것과 같은 가상 세계를 구현하고 이 가상세계에서 모의 실험을 함으로써 많은 시간과 비용을 절약할 수 있었다.
- 상속, 캡슐화, 추상화 개념을 중심으로 점차 구체적으로 발전했다. (다형성 어디갔어)
1.2 객체지향 언어
- 객체지향 언어의 주요 특징
- 코드의 재사용성이 높다.
- 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.
- 코드의 관리(유지보수)가 용이하다.
- 코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.
- 신뢰성이 높은 프로그래밍을 가능하게 한다.
- 제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며, 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.
- 너무 객체지향개념에 얽매여서 고민하기 보다는 일단 프로그램을 기능적으로 완성한 다음 어떻게 더 객체지향적으로 코드를 개선할 것인지 고민하여 점차 개선해 나가는 것이 좋다.
2. 클래스와 객체
2.1 클래스와 객체의 용도
- 클래스
- 객체를 정의해놓은 것
- 객체를 생성하는데 사용된다.
- 객체
- 실제로 존재하는 것. 사물 또는 개념
- 객체가 가지고 있는 기능과 속성에 따라 다름
- 클래스에 정의된 내용대로 메모리에 생성된 것
2.2 객체와 인스턴스
- 클래스의 인스턴스화 (instantiate)
- 인스턴스 (Instance)
2.3 객체의 구성 요소 - 속성과 기능
- 객체는 속성과 기능, 두 종류의 구성요소로 이루어져 있으며, 일반적으로 객체는 다수의 속성과 다수의 기능을 갖는다.
- 객체의 멤버(Member)
- 객체가 가지고있는 속성(멤버변수)과 기능(메서드)들
- 클래스는 객체를 정의한 것이므로 클래스에는 객체의 모든 속성(멤버변수)과 기능(메서드)이 정의되어있다.
2.4 인스턴스의 생성과 사용
클래스명 변수명;
변수명 = new 클래스명();
class Tv {
boolean power;
void power() {
power = !power;
}
}
class TvTest {
public static void main(String args[]) {
Tv t;
t = new Tv();
t.power = false;
t.power();
System.out.println("power = " + power);
}
}
Tv t;
- Tv 클래스 타입의 참조변수 t를 선언한다.
- 메모리에 참조변수 t를 위한 공간이 마련된다.
- 아직 인스턴스가 생성되지 않아 참조변수로 아무것도 할 수 없다.
t = new Tv();
- 연산자
new
를 통해 Tv클래스의 인스턴스가 메모리의 빈 공간에 생성된다.
- 주소가 0x100인 곳에 생성되었다고 가정하자.
- 이 때, 멤버변수는 각 자료형에 해당하는 기본값으로 초기화된다.
- 대입 연산자
=
에 의해 생성된 객체의 주소값이 참조변수 t에 저장된다.
- 이를 통해 참조변수 t를 통해 Tv인스턴스에 접근할 수 있다.
- 인스턴스를 다루기 위해서는 참조변수가 반드시 필요하다.
t.power = false;
- 참조변수 t에 저장된 주소에 있는 인스턴스의 멤버변수 power에 false를 저장한다.
- 인스턴스의 멤버변수를
참조변수.멤버변수
의 형태로 사용할 수 있다.
t.power();
- 참조변수 t가 참조하고 있는 Tv인스턴스의 power() 메서드를 호출한다.
power()
메소드는 멤버변수 power의 값을 변경한다.
System.out.println("power = " + power);
- 참조변수 t가 참조하고 있는 Tv인스턴스의 멤버변수 power에 저장되어 있는 값을 출력한다.
- 현재 power는 true 이므로 ‘power = true’를 출력한다.
인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야 한다.
2.5 객체 배열
- 객체 배열 안에 객체를 저장하는 것이 아닌 객체의 주소를 저장한다.
- 객체 배열을 생성하면, 객체를 다루기 위한 참조 변수들이 만들어진 것일 뿐 아직 객체가 저장되지 않았다.
- 객체를 생성해서 객체 배열의 각 요소에 저장하는 것을 잊으면 안된다.
2.6 클래스의 또 다른 정의
데이터와 함수의 결합
- 데이터 처리를 위한 데이터 저장형태의 발전
- 변수
- 배열
- 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간
- 구조체
- 서로 관련된 여러 데이터를 종류에 관계업싱 하나의 집합으로 저장할 수 ㅣㅇㅆ는 공간
- 클래스
- 서로 관련된 변수들을 정의하고 이들에 대한 작업을 수행하는 함수들을 함께 정의한 것이 바로 클래스이다.
사용자 정의 타입 (User-defined Type)
- 프로그래밍 언어에서 제공하는 자료형(primitive type)외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자 정의 타입이라고 한다.
- 제어자를 이용해서 변수의 값을 직접 변경하지 못하도록 하고, 대신 메서드를 통해서 값을 변경하도록 할 수 있다.(Getter / Setter)
3. 변수와 메서드
3.1 선언 위치에 따른 변수의 종류
변수가 선언되는 위치에 따라 변수의 종류를 나누게 된다.
변수
- 멤버변수
- 클래스 변수
- static이 붙은 것
- 클래스가 메모리에 올라갈 때 생성
- 모든 인스턴스가 공통된 저장공간(변수)를 공유하게 되므로 한 클래스의 모든 인스턴스들이 공통적인 값을 가지는 경우 사용
- 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때까지 유지되며, public을 붙이면 어디에서나 접근할 수 있는 전역변수(global variable)가 된다.
- 인스턴스 변수
- static이 붙지 않은 것
- 인스턴스가 생성되었을 때 생성
- 인스턴스마다 고유한 상태를 유지해야하는 경우 사용
- 지역 변수
- 멤버변수를 제외한 나머지 변수들
- 변수 선언문이 수행되었을 때 생성
- 메서드 내에 선언되어 매서드 내에서만 사용 가능하다.
- 메서드가 종료되면 소멸되어 사용할 수 없게 된다.
3.2 클래스 변수와 인스턴스 변수
- 인스턴스 변수는 인스턴스가 생성될 때 마다 생성되므로 각기 다른 값을 유지할 수 있다.
- 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로 항상 공통된 값을 갖는다.
3.3 메서드
특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것.
메서드를 사용하는 이유
- 높은 재사용성 (reusability)
- 한 번 만들어놓은 메서드는 몇 번이고 호출할 수 있으며, 다른 프로그램에서도 사용이 가능하다.
- 중복된 코드의 제거
- 반복되는 문장들을 묶어서 하나의 메서드로 작성하면 메서드를 호출하는 한 문장으로 대체할 수 있다.
- 코드의 중복이 제거되고, 변경사항이 발생했을 때 이 메서드만 수정하면 되므로 관리도 쉽고 오류의 발생 가능성도 낮아진다.
- 프로그램의 구조화
- 큰 규모의 프로그램에서 작업단위로 나워 여러개의 메서드를 통해 프로그램의 구조를 단순화 시킬 수 있다.
3.4 메서드의 선언과 구현
메서드는 선언부
와 구현부
로 이루어져있다.
int add (int x, int y) {
return x + y;
}
메서드 선언부는 메서드의 이름
과 매개변수 선언
, 그리고 반환타입
으로 구성되어 있으며, 메서드가 작업을 수행하기 위해 어떤 값을 필요로 하고 작업의 결과로 어떤 타입의 값을 반환하느닞에 대한 정보를 제공한다.
매개변수 선언(parameter declaration)
- 배서드가 작업을 수행하는데 필요한 값들을 제공받기 위해 사용
매서드의 이름(method name)
- 변수의 명명 규칙대로 작성하면 된다.
- 동사인 경우가 많으며 이름만으로 메서드의 기능을 쉽게 알 수 있도록 이름을 지어야한다.
반환타입(return type)
- 메서드 작업수행 결과인
반환값(return value
의 타입을 적는다.
- 반환값이 없는 경우
void
를 적는다.
메서드의 구현부 (method body)
메서드를 호출했을 때 수행될 문장들을 넣는다.
return문
- 메서드의 반환 타입이
void
가 아닌 경우 구현부 안에 return 반환값;
이 반드시 포함되어야 한다.
- 이 값의 타입은 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 한다.
- return문은 단 하나의 값만 반환할 수 있다.
지역변수(local variable)
3.5 메서드의 호출
메서드이름(값1, 값2, ...);
인자(argument)와 매개변수(parameter)
- 인자
- 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다.
- 인자의 타입은 매개변수의 타입과 일치하거나 자동 형변환이 가능해야 한다.
매서드의 실행흐름
- 인스턴스를 생성하여 참조변수에 저장한다.
- 해당 참조변수를 통해 메서드를 호출한다.
- 호출과 동시에 인자를 매개변수에 복사한다.
- 메서드를 순서대로 수행한다.
- return문을 만나면 호출한 메서드로 돌아온다.
- 메서드의 결과값이 호출한 자리를 대신하여 진행된다.
3.6 return문
- return문은 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아간다.
- 모든 메서드에는 하나 이상의 return문이 있어야한다.
void
의 경우 컴파일러가 메서드의 마지막에 return;
을 자동으로 추가해준다.
반환값(return value)
- 계산된 변수가 아니더라도 알아서 계산한 결과를 반환해준다.
매개변수의 유효성 검사
- 메서드의 구현부를 작성할 때, 제일 먼저 해야하는 일이 매개변수의 값이 적절한 것인지 확인하는 것이다.
- 메서드를 작성하는 사람은 ‘호출하는 쪽에서 알아서 적절한 값을 넘겨주겠지’라는 안일한 생각을 가져서는 안된다.
- 가능한 모든 경우의 수에 대해 고민하고 그에 대비한 코드를 작성해야 한다.
3.7 JVM의 메모리 구조
- 메서드영역(method area)
- 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장한다.
- 이때 그 클래스의 클래스 변수(class variable)도 이 영역에 함께 생성된다.
- 힙(heap)
- 인스턴스가 생성되는 공간.
- 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다.
- 인스턴스 변수(instance variable)이 생성되는 공간이다.
- 호출 스택(call stack, excution stack)
- 메서드의 작업에 필요한 메모리 공간을 제공한다.
- 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간 결과등을 저장하는데 사용된다.
- 그리고 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워진다.
- 각 메서드를 위한 메모리상의 작업공간은 서로 구별되며, 첫번째로 호출된 메서드를 위한 작업공간이 호출스택의 맨 밑에 마련되고, 첫번째 메서드 수행 중에 다른 메서드를 호출하면, 첫 번째 메서드의 바로 위에 두번째로 호출된 메서드를 위한 공간이 마련된다.
- 메서드가 호출되면 수행에 필요한 만큼 메모리를 스택에 할당받는다.
- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
- 호출스택의 제일 위에있는 메서드가 현재 실행중인 메서드이다.
- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다.
- 반환타입이 있는 메서드는 종료되면서 결과값을 자신을 호출한 메서드에게 반환한다.
- 대기상태에 있던 호출한 메서드(caller)는 넘겨받은 반환값으로 수행을 계속 진행하게 된다.
3.8 기본형 매개변수와 참조형 매개변수
- 자바에서는 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다.
- 매개변수의 타입이 기본형(primitive type)일 때는 기본형 값이 복사되겠지만, 참조형(reference type) 변수라면 인스턴스의 주소가 복사된다.
- 기본형 매개변수
- 참조형 매개변수
3.9 참조형 반환타입
- 매개변수 뿐만 아니라 반환타입도 참조형이 될 수 있다.
- 모든 참조형 타입의 값은 객체의 주소이므로 그저 정수값이 반환되는 것일 뿐이다.
3.10 재귀호출(recursive call)
- 메서드의 내부에서 메서드 자신을 다시 호출하는 것을
재귀호출(recursive call)
이라 한다.
- 호출된 메서드는
값에 의한 호출(call by value)
을 통해 원래의 값이 아닌 복사된 값으로 작업하기 때문에 호출한 메서드와 관계 없이 독립적인 작업수행이 가능하다.
- 오로지 재귀호출뿐이면, 무한히 자기 자신을 호출하기 때문에 무한 반복에 빠지게 된다.
- 반복문보다 재귀호출의 수행시간이 더 오래걸린다.
- 문장을 수행하는데서 그치지 않고 매개변수 복사와 종료 후 복귀할 주소 저장 등 추가적으로 해야할 일이 많다.
- 논리적 간결함으로 인해 사용하게 된다.
3.11 클래스 메서드(static method)와 인스턴스 메서드
- 변수와 동일하게 메서드 앞에 static이 있으면 클래스 메서드, 없으면 인스턴스 메서드이다.
- 인스턴스 메서드
- 인스턴스 변수와 관련된 작업을 하는, 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다.
- 인스턴스 변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다.
- 클래스 메서드
- 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 클래스 메서드(static method)로 정의한다.
- 인스턴스 변수를 사용하지 않는다고 반드시 클래스 메서드로 정의해야 하는 것은 아니다.
- 클래스를 설계할 때 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
- 생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수는 서로 다른 값을 유지한다.
- 그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스 변수로 정의해야 한다.
- 클래스변수(static 변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
- static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.
- 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없다.
- 인스턴스 변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데, 클래스 메서드는 인스턴스 생성 없이 호출 가능하므로 클래스 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있다.
- 그래서 클래스 메서드에서 인스턴스 변수의 사용을 금지한다.
- 반면에 인스턴스 변수나 인스턴스 메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다.
- 인스턴스 변수가 존재한다는 것은 static 변수가 이미 메모리에 존재한다는 것을 의미하기 때문.
- 메서드 내에서 인스턴스 변수를 사용하지 않는다면 static을 붙이는 것을 고려한다.
- 메서드의 작업내용 중에서 인스턴스 변수를 필요로 한다면, static을 붙일 수 없다.
- 반대로 인스턴스 변수를 필요로 하지 않는다면 static을 붙이자.
- 메서드 호출 시간이 짧아지므로 성능이 향상된다.
- static을 안 붙인 메서드(인스턴스 메서드)는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.
- 클래스의 멤버변수 중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면 static으 ㄹ붙여준다.
- 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙일 것을 고려한다.
3.12 클래스 멤버와 인스턴스 멤버간의 참조와 호출
- 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.
- 단, 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
- 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수 있기 때문.
4. 오버로딩
4.1 오버로딩이란?
한 클래스 내에서 같은 이름의 메서드를 여러 개 정의하는 것을 메서드 오버로딩(method overroading)
혹은 오버로딩(overroading)
이라 한다.
4.2 오버로딩의 조건
- 메서드 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야한다.
- 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.
4.3 오버로딩의 예
- 같은 일을 하지만 매개변수를 달리해야하는 경우에 이름이 같고 매개변수를 다르게 하여 오버로딩을 구현한다.
4.4 오버로딩의 장점
- 같은 기능을 하는 메서드들이 하나의 이름으로 정의되어 기억에 용이하다.
- 이름을 절약할 수 있다.
4.5 가변인자(varargs)와 오버로딩
- JDK1.5부터 매개변수를 동적으로 지정해줄 수 있다.
타입... 변수명
과 같은 형식으로 선언한다.
- 가변인자 외에도 매개변수가 있다면, 가변인자를 매개변수 중 가장 마지막에 선언해야 한다.
- 가변인자는 내부적으로 배열을 이용한다.
- 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성된다.
- 비효율적이므로 필요한 경우에만 사용하자.
- 매개변수의 타입을 배열로하면 반드시 인자를 지정해줘야 하기 때문에 인자를 생략할 수 없다.
- 가변인자를 오버로딩하는 경우, 메서드를 호출했을 때 구분되지 않는 경우가 쉽기 때문에 주의해야 한다.
- 가변인자를 사용한 메서드는 오버로딩 하지 마라.
5. 생성자(Constructor)
5.1 생성자란?
생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드
이다.
생성자의 조건
- 생성자의 이름은 클래스의 이름과 같아야 한다.
- 생성자는 리턴 값이 없다.
- 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다.
- 생성자는 단순히 인스턴스 변수들의 초기화에 사용되는 메서들일 뿐이다.
생성자 수행 과정
Card c = new Card()
- 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
- 생성자 Card()가 호출되어 수행된다.
- 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
5.2 기본생성자
Card() {}
- 컴파일러가 자동우로 추가해주며, 매개변수도 없고 아무런 내용도 없는 간단한 생성자이다.
- 기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다.
5.3 매개변수가 있는 생성자
- 매개변수를 선언하여 호출 시 값을 넘겨받아서 인스턴스의 초기화 작업에 사용할 수 있다.
5.4 생성자에서 다른 생성자 호출하기 - this(), this
- 같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 다음 두 조건을 만족한다면 서로 호출이 가능하다.
- 생성자의 이름으로 클래스이름 대신 this를 사용한다.
- 한 생성자에서 다른 생성자를 호출할 대는 반드시 첫 줄에서만 호출이 가능하다.
- 생성자 내에서 초기화 작업 도중에 다른 생성자를 호출하게 되면, 호출된 다른 생성자 내에서도 멤버변수들의 값을 초기화 할 것이므로 다른 생성자를 호출하기 이전의 초기화 작업이 무의미해질 수 있기 때문이다.
- 두 변수(인스턴스 변수, 매개변수)의 값이 구분되지 않는 경우 인스턴스 변수 앞에
this
를 붙여 사용하게 된다.
- 생성자를 포함한 모든 인스턴스 메서드에는 자신이 관련된 인스턴스를 가리키는 참조변수
this
가 지역변수로 숨겨진 채로 존재한다.
- 인스턴스 메서드는 특정 인스턴스와 관련된 작업을 하기 때문에 자신과 관련된 인스턴스의 정보가 필요하지만, static 메서드는 인스턴스와 관련 없는 작업을 하므로 인스턴스에 대한 정보가 필요 없기 때문이다.
정리
- this
- 인스턴스 자신을 가리키는 참조변수
- 인스턴스의 주소가 저장되어 있다.
- 모든 인스턴스 메서드에 지역변수로 숨겨진 채로 존재한다.
- this(), this(매개변수)
- 생성자
- 같은 클래스의 다른 생성자를 호출할 때 사용한다.
5.5 생성자를 이용한 인스턴스의 복사
- 현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다.
Car(Car c) {
color = c.color;
gearType = c.gearType;
door = c.door;
}
- 복사해서 만들더라도 다른 메모리 공간에 위치하므로 별도의 인스턴스이다.
인스턴스를 생성할 때 고려할 사항
- 클래스
- 생성자
- 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?
6. 변수의 초기화
6.1 변수의 초기화
변수를 선언하고 처음으로 값을 저장하는 것
- 가능하면 선언과 동시에 적절한 값으로 초기화하는 것이 좋다.
- 멤버변수는 초기화하지 않아도 기본값으로 초기화해주지만, 지역변수는 사용하기 전 반드시 초기화해야한다.
멤버변수의 초기화 방법
- 명시적 초기화
- 생성자
- 초기화 블럭
6.2 명시적 초기화(explicit initialization)
변수를 선언과 동시에 초기화 하는 것
class Car {
int door = 4;
...
}
6.3 초기화 블럭
초기화 블럭
- 클래스 초기화 블럭
- 클래스 변수의 복잡한 초기화에 사용된다.
- 메모리에 처음 로딩될 때 한번만 수행
- 인스턴스 초기화 블럭
- 인스턴스 변수의 복잡한 초기화에 사용된다.
- 인스턴스를 생성할 때마다 수행
6.4 멤버변수의 초기화 시기와 순서
- 클래스 변수의 초기화 시점
- 클래스가 처음 로딩될 때 단 한번 초기화된다.
- 인스턴스 변수의 초기화 시점
- 인스턴스가 생성될 떄마다 각 인스턴스 별로 초기화가 이루어진다.
- 클래스 변수의 초기화 순서
- 기본값
- 명시적 초기화
- 클래스 초기화 블럭
- 인스턴스 변수의 초기화 순서
- 기본값
- 명시적 초기화
- 인스턴스 초기화 블럭
- 생성자