Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[Java] 예제 소스 정리 - 변수 종류, 인스턴스 메서드, 스태틱 메서드, 생성자, 초기화 블록 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[Java] 예제 소스 정리 - 변수 종류, 인스턴스 메서드, 스태틱 메서드, 생성자, 초기화 블록

끈기JK 2023. 1. 5. 19:05

com.eomcs.oop.ex03

 

예제 소스 정리

 

변수 종류

 

 

# 변수의 종류
public class Exam0100 {

  // static 필드 = 클래스 필드(변수)
  // - 클래스를 로딩할 때 Method Area 영역에 생성된다.
  // - 클래스는 단 한 번만 로딩된다.
  // - 따라서 스태틱 변수도 한 번만 생성된다.
  // - JVM을 종료할 때 메모리에서 한꺼번에 제거된다.
  static int a;

  // non-static 필드 = instance 필드
  // - new 연산자를 실행할 때 Heap 영역에 생성된다.
  // - new 연산자를 실행할 때마다 생성된다.
  // - Garbage Collector에 의해 인스턴스가 해제될 때 제거된다.
  int b;

  public static void main(String[] args /* 파라미터 = 로컬 변수 */) {

    // 로컬 변수
    // - 메서드가 호출될 때 JVM Stack 영역에 생성된다.
    // - 메서드 호출이 끝나면 제거된다.
    int c;
    c = 100;
    // <=== 현재 실행 시점
    // - Method Area: a 변수 존재
    // - JVM Stack: args, c, obj 변수 존재
    // - Heap: 아직 생성된 객체 없음

    Exam0100 obj;  // obj는 main()을 호출할 때 시작 시점에 JVM Stack에 생성된 상태이다.


    obj = new Exam0100();

    // <=== 현재 실행 시점
    // - Method Area: a 변수 존재
    // - JVM Stack: args, c, obj 변수 존재
    // - Heap: b 변수 존재

    System.out.println(c);
  }
}

 

 

# 인스턴스 변수
public class Exam0110 {

  // 지금 당장 A 클래스 앞에 붙은 static은 고민하지 말라!
  // 이 예제의 목표는 인스턴스 변수이다.
  static class A {
    // 인스턴스 변수 = 논스태틱 변수
    // => new 명령을 통해 생성된다.
    // => new 명령을 실행하기 전까지는 인스턴스 변수는 존재하지 않는다.
    // => Heap 영역에 생성된다.
    // => static이 붙지 않는다.
    int v1; // 4바이트 int 값을 저장할 메모리를 만들라는 명령!
    boolean v2; // true/false 논리값을 저장할 메모리를 만들라는 명령!
    // 이 명령은 new 명령을 실행할 때 비로서 실행된다.
  }

  public static void main(String[] args) {

    // A 클래스에 대해 new 명령을 사용하기 전에는 v1, v2 메모리는 존재하지 않는다.
    // 단지 설계도일 뿐이다.
    A obj1 = new A(); // A 클래스에서 변수 선언 명령을 실행한다. 주의! 메서드 정의는 실행하지 않는다!
    A obj2 = new A();
    A obj3 = new A();

    // 이렇게 생성된 메모리를 "인스턴스", "객체"라고 부른다.
    // 이 인스턴스의 주소를 저장하는 obj1, obj2, obj3를 "레퍼런스"라 부른다.
    // 인스턴스가 생성될 때 만들어지는 v1, v2를 변수를 "인스턴스 변수"라 부른다.

    // 인스턴스 변수는 레퍼런스를 통해 사용할 수 있다.
    obj1.v1 = 100;
    obj2.v1 = 200;
    obj3.v1 = 300;

    System.out.printf("%d, %d, %d\n", obj1.v1, obj2.v1, obj3.v1);
  }
}

// 인스턴스 변수는 new 명령을 실행할 때 마다 생성되기 때문에
// 각각 구분되는 개별 데이터를 저장할 때 사용한다.

 

 

# 인스턴스 변수 응용 - 성적 데이터 저장할 메모리 만들기
public class Exam0120 {

  // 1) 성적 데이터를 설계할 클래스이기 때문에 그에 맞는 클래스명을 사용하라!
  static class Score {

    // 2) 여러 명의 구별되는 성적 데이터를 저장해야 하기 때문에
    // 인스턴스 변수로 메모리를 설계하라!
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float average;
  }

  public static void main(String[] args) {

    // 저장하고 싶은 데이터 개수 만큼 인스턴스를 생성하라!
    Score s1 = new Score(); // 1명 분의 성적 데이터를 저장할 메모리
    Score s2 = new Score();
    Score s3 = new Score();

    // 각 인스턴스에 한 명의 성적 데이터를 저장하라!
    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 80;
    s1.sum = s1.kor + s1.eng + s1.math;
    s1.average = s1.sum / 3f;

    s2.name = "임꺽정";
    s2.kor = 100;
    s2.eng = 100;
    s2.math = 100;
    s2.sum = s2.kor + s2.eng + s2.math;
    s2.average = s2.sum / 3f;

    s3.name = "유관순";
    s3.kor = 100;
    s3.eng = 90;
    s3.math = 60;
    s3.sum = s3.kor + s3.eng + s3.math;
    s3.average = s3.sum / 3f;

  }
}

 

 

# 클래스 변수
public class Exam0130 {

  // 지금 당장 A 클래스 앞에 붙은 static은 고민하지 말라!
  // 이 예제의 목표는 스태틱 변수이다.
  static class A {

    // 클래스 변수 = 스태틱 변수
    // - static 이 붙은 변수이기 때문에 "스태틱 변수"라고도 부른다.
    // - 클래스를 로딩하는 순간 자동 생성된다.
    // - 클래스와 함께 "Method Area" 영역에 존재한다.
    // - 클래스 이름으로 접근
    //   클래스 이름으로 접근한다고 해서 "클래스에 소속된 변수", "클래스 변수"라 부른다.
    // - 문법
    //     static 데이터타입 변수명;
    //
    static int v1;
    static boolean v2;
  }

  public static void main(String[] args) {

    // 클래스 변수 사용법
    // 클래스명.스태틱변수명 = 값;
    // 클래스를 사용하는 순간 클래스가 로딩되고, 스태틱 변수는 자동 생성된다.
    A.v1 = 100;
    A.v2 = true;

    System.out.printf("%d, %b\n", A.v1, A.v2);
  }
}

JVM을 실행하는 동안 한 번 클래스가 로딩되면 같은 클래스에 대해 중복 로딩되지 않는다. 
클래스 변수는 클래스가 로딩될 때 자동 생성되기 때문에
클래스에 대해 딱 한 번 생성된다.


## 클래스 로딩
- 외부 저장장치(예: HDD, USB 메모리, DVD-ROM 등)에 있는 .class 파일을
  JVM이 관리하는 메모리로 로딩하는 것.
- 클래스의 코드를 사용하는 시점에 메모리(Method Area 영역)에 로딩된다.

## 클래스의 코드를 사용하는 시점?
- 스태틱 멤버(필드와 메서드)를 사용할 때
    예) A.v1 = 200; <--- 스태틱 변수 v1 사용
    예) System.out.println(A.v1); <--- 스태틱 변수 out 사용 
    예) Integer.parseInt(..); <--- 스태틱 메서드 parseInt() 사용
- new 명령을 사용하여 인스턴스를 생성할 때
    예) new A();
- 한 번 클래스가 로딩되면 JVM을 종료할 때까지 유지한다.
- 물론 강제로 클래스를 unloading 할 수 있다. 
  그리고 다시 로딩할 수 있다.

## 주의! 클래스를 로딩할 거라고 착각하는 경우
- 다음과 같이 레퍼런스 변수를 선언할 때는 클래스를 로딩하지 않는다. 
  로딩하지 않는다! 로딩하지 않는다! 로딩하지 않는다!
예) A obj;
예) String str;

## 클래스 로딩 과정
$ java com.eomcs.oop.ex03.Exam0130
1) 클래스 파일 'Exam0130.class'을 찾는다.
   - JDK에서 제공하는 기본 라이브러리에서 찾는다.
   - JVM을 실행할 때 -classpath(또는 -cp) 지정한 CLASSPATH 디렉토리에서 찾는다.
   - CLASSPATH에 없으면 JVM을 실행하는 현재 폴더에서 찾는다. 
   - 그래도 없으면 오류를 띄운다.
2) 바이트코드 검증(Verify)
   - 클래스의 바이트코드 유효성을 검사한다.
3) Exam0130.class를 "Method Area 영역"에 로딩한다.
   - 즉 클래스를 외부 저장소(HDD)에서 내부 저장소(RAM)로 로딩한다.
   - bytecode를 분석하여 코드(생성자, 메서드)와 상수를 따로 분리하여 보관한다.
4) 스태틱 필드 및 메서드 테이블 준비(Prepare)
   - Method Area 에 스태틱 필드 생성한다.
   - 클래스 내부에서 사용하는 이름(변수명, 메서드명, 클래스명 등) 목록을 준비한다.
5) 참조하는 외부 클래스나 인터페이스 검사(Resolve)
   - 로딩된 클래스가 참조하는 외부 클래스나 인터페이스의 유효성을 검사한다.
6) 클래스 초기화시키기
   - 스태틱 블록(static initializers)을 실행한다.
7) main() 메서드를 호출한다.
   - 클래스를 실행하는 것이라면 main() 메서드를 찾아 실행한다.

## Exam0130의 main() 메서드 호출
1) main() 메서드에 선언된 로컬 변수를 "JVM 스택 영역"에 생성한다.
   - args 변수를 스택 영역에 생성한다.
2) main()의 코드를 실행한다.
   - A.v1 = 100;
     => A.class 를 "Method Area"에 로딩한다.
     => A의 클래스(스태틱) 필드를 "Method Area"에 생성한다.
     => `A.v1 = 100` 문장을 실행한다.
   - A.v2 = true;
     => A 클래스가 이미 로딩되었기 때문에 다시 로딩하지 않는다.
     => `A.v2 = true` 문장을 실행한다.
   - System.out.printf() 를 실행한다.


## JVM이 관리하는 메모리 영역
1) Heap
   - new 명령으로 생성한 인스턴스 변수가 놓인다.
   - 즉 인스턴스 필드가 이 영역에 생성된다.
     - 메서드는 생성하지 않는다!
   - 가비지 컬렉터는 이 메모리의 가비지들을 관리한다.
2) JVM Stack
   - 각 스레드가 개인적으로 관리하는 메모리 영역이다.
   - 스레드에서 메서드를 호출할 때 메서드의 로컬 변수를 이 영역에 만든다.
   - 메서드가 호출될 때 그 메서드가 사용하는 로컬 변수를 프레임에 담아 만든다.
   - 메서드 호출이 끝나면 그 메서드가 소유한 프레임이 삭제된다.
3) Method Area
   - JVM이 실행하는 바이트코드(.class 파일)를 두는 메모리 영역이다.
     - 바이트코드를 그대로 메모리에 두는 것이 아니라, 멤버의 종류에 따라 적절하게 분류한다. 
   - 즉 클래스 코드가 이 영역에 놓이는 것이다.
   - JVM은 코드를 실행할 때 이 영역에 놓은 명령어를 실행하는 것이다.
   - 개발자가 작성한 클래스, 메서드 등 이런 코드들이 이 영역에 놓이는 것이다.
   - 스태틱 필드를 이 영역에 생성한다.
   - 주의! 
     Heap에는 개발자가 작성한 명령어가 없다.

 

 

# 클래스 변수와 인스턴스 변수 생성 시점과 메모리 영역
public class Exam0140 {

  static class A {
    static int v1;
    int v2;
  }

  public static void main(String[] args) {

    // 클래스 변수는 클래스가 로딩되는 순간 바로 사용할 수 있다.
    // 클래스가 로딩되는 경우:
    // - 클래스 변수나 클래스 메서드를 사용할 때
    // - 인스턴스를 생성할 때
    // - 단 중복 로딩되지 않는다.
    //
    A.v1 = 100;

    // v2 는 인스턴스 변수이기 때문에 사용하기 전에 new 명령으로 먼저 생성해야 한다.
    // A.v2 = 200; // 컴파일 오류!

    A p = new A();
    // 이제 v2 변수는 Heap에 생성되었다.
    // A클래스의 인스턴스를 만들 때
    // static 이 안붙은 변수(non-static 변수 = 인스턴스 변수)가 그 대상이다.
    //
    // v2 인스턴스 변수는 인스턴스 주소를 통해 사용해야 한다.
    // 클래스이름으로 사용할 수 없다.
    //    A.v2 = 200; // 컴파일 오류!

    p.v2 = 200; // OK!

    // 인스턴스 변수는 인스턴스를 만들 때 마다 생성된다.
    A p2 = new A(); // 새 v2 변수가 생성된다.
    p2.v2 = 300;

    System.out.printf("A.v1=%d, p.v2=%d, p2.v2=%d\n", A.v1, p.v2, p2.v2);
  }
}

 

 

# 클래스 변수와 인스턴스 변수 응용
public class Exam0150 {

  static class Student {

    // 클래스 필드 = 스태틱 필드
    // - 모든 인스턴스가 공유하는 값을 저장할 때는 클래스 변수를 사용한다.
    static int count; 

    // 인스턴스 필드 = 논스태틱 필드
    // - 인스턴스마다 개별적으로 관리해야 할 값은 인스턴스 변수에 저장한다.
    String name;
    int age;
  }

  public static void main(String[] args) {

    // 클래스 필드는 인스턴스를 생성할 필요 없이 클래스 이름으로 바로 사용한다.
    Student.count = 0;

    // 인스턴스 필드는 new 명령을 수행해야지만 생성된다.
    Student s1 = new Student();
    s1.name = "홍길동"; // 인스턴스 필드는 인스턴스의 주소(s1)가 있어야만 접근할 수 있다.
    s1.age = 10;

    // 클래스 이름으로는 인스턴스 필드에 접근할 수 없다.
    // A.name = "홍길동"; // 컴파일 오류!

    Student.count++;

    Student s2 = new Student();
    s2.name = "임꺽정";
    s2.age = 20;

    Student.count++; // 이처럼 클래스 필드는 클래스 이름으로 사용하라!

    Student s3 = new Student();
    s3.name = "유관순";
    s3.age = 30;

    Student.count++; // 이처럼 클래스 필드는 클래스 이름으로 사용하라!

    System.out.printf("%d, %s, %d\n", Student.count, s1.name, s1.age);
    System.out.printf("%d, %s, %d\n", Student.count, s2.name, s2.age);
    System.out.printf("%d, %s, %d\n", Student.count, s3.name, s3.age);

    // 클래스 필드에 접근할 때는 다음과 같이 레퍼런스를 통해서도 접근할 수 있다.
    System.out.println(s1.count);
    System.out.println(s2.count);
    System.out.println(s3.count);
    // - 인스턴스에 count라는 변수가 없으면 클래스에서 찾는다.
    // - 하지만 이렇게 사용하지 말라!
    // - 다른 개발자가 이 코드를 봤을 때, count가 인스턴스 변수라고 착각할 것이다.
    //   "클래스 변수는 클래스 이름을 사용하여 접근하라!"
  }
}

 

 

클래스 변수의 응용: 클래스 변수 활용 전
public class Exam0160 {

  static class Member {
    String id;
    String password;
    int type; // 0: 손님, 1: 회원, 2: 관리자
  }

  public static void main(String[] args) {
    Member m1 = new Member();
    m1.id = "aaa";
    m1.password = "1111";
    m1.type = 0; // 손님

    Member m2 = new Member();
    m2.id = "bbb";
    m2.password = "1111";
    m2.type = 2; // 관리자

    Member m3 = new Member();
    m3.id = "ccc";
    m3.password = "1111";
    m3.type = 1; // 회원
  }
}

 회원의 타입을 지정할 때 숫자를 이용하였다.
 그런데 소스 코드를 작성한 후 시간이 오래 지나면 숫자의 의미를 기억하기가 쉽지 않다.
 소스 코드를 작성한 사람도 숫자의 의미를 기억하기가 쉽지 않은데,
 남의 소스 코드를 유지보수하는 사람은 더더욱 숫자의 의미를 파악하기란 어렵다.
 주석이나 개발 문서를 자세히 살펴보지 않는 이상에는 숫자의 의미를 파악하기 쉽지 않다.

 

 

클래스 변수의 응용
public class Exam0161 {

  static class Member {
    String id;
    String password;
    int type; 
  }

  public static void main(String[] args) {

    // 이전 예제의 문제점을 해결하기 위해 나온 것이 상수이다.
    // - 즉 의미있는 이름으로 변수를 만들어 놓고 그 변수에 숫자를 저장한다.
    // - 그리고 그 값을 사용할 때는 변수를 통해 사용한다.
    // - 조회 용으로 사용할 변수이기에 final 로 선언한다.
    // - 상수라는 것을 직관적으로 알리기 위해 변수 이름을 대문자로 선언한다.
    //
    final int GUEST = 0;
    final int MEMBER = 1;
    final int MANAGER = 2;

    // 다음은 상수를 사용한 예이다.
    Member m4 = new Member();
    m4.id = "aaa";
    m4.password = "1111";
    m4.type = GUEST; // 숫자로 타입을 지정하는 대신 변수명을 사용했기 때문에 따로 주석을 달 필요가 없다.

    Member m5 = new Member();
    m5.id = "bbb";
    m5.password = "1111";
    m5.type = MANAGER;

    Member m6 = new Member();
    m6.id = "ccc";
    m6.password = "1111";
    m6.type = MEMBER;
  }
}

 

public class Exam0162 {

  static class Member {

    // Member와 관련된 값을 갖고 있는 변수라면 
    // 다음과 같이 Member 클래스에 두는 것이 유지보수에 좋다.
    // - 여러 인스턴스에서 공통으로 사용할 값을 담는 변수라면 static 필드로 선언하라!
    // - 조회용으로 사용할 변수이면서 외부에서도 사용해야 할 변수라면 public 으로 공개하라!
    // - 다음 변수는 클래스 변수(스태틱 변수)의 전형적인 예이다.
    public static final int GUEST = 0;
    public static final int MEMBER = 1;
    public static final int MANAGER = 2;

    String id;
    String password;
    int type; // 0: 손님, 1: 회원, 2: 관리자
  }

  public static void main(String[] args) {

    // Member와 관련된 상수라면 
    // Member 클래스에 선언하는 것이 유지보수에 더 낫다.
    //    final int GUEST = 0;
    //    final int MEMBER = 1;
    //    final int MANAGER = 2;

    Member m4 = new Member();
    m4.id = "aaa";
    m4.password = "1111";
    m4.type = Member.GUEST;
    // 스태틱 변수는 변수명 앞에 클래스명을 명시해야 한다.

    Member m5 = new Member();
    m5.id = "bbb";
    m5.password = "1111";
    m5.type = Member.MANAGER;

    Member m6 = new Member();
    m6.id = "ccc";
    m6.password = "1111";
    m6.type = Member.MEMBER;
  }
}

 

 

클래스 변수의 응용 : 상수 변수를 import 하기

 Member 클래스를 외부의 다른 클래스에서도 사용한다면,
 nested class 로 선언하지 많고 패키지 멤버로 분리하라.

// 패키지 멤버의 스태틱 필드를 사용할 때는 다음과 같이 import로
// 그 변수의 소속을 미리 밝힐 수 있다.
// => 스태틱 변수의 소속 클래스를 미리 밝혀두면
//    클래스 이름 없이 스태틱 변수를 바로 사용할 수 있다.
import static com.eomcs.oop.ex03.Member.GUEST;
import static com.eomcs.oop.ex03.Member.MANAGER;
import static com.eomcs.oop.ex03.Member.MEMBER;

// 여러 개를 한 번에 명시하고 싶다면 다음과 같이 wildcard(*)로 지정해도 된다.
//import static com.eomcs.oop.ex03.Member.*;

public class Exam0163 {

  public static void main(String[] args) {

    Member m4 = new Member();
    m4.id = "aaa";
    m4.password = "1111";
    m4.type = GUEST; // import static 명령문에서 변수의 소속을 이미 밝혔기 때문에 클래스 이름을 적을 필요가 없다.
    // 만약 import에 선언되지 않았다면 스태틱 변수명 앞에 클래스명을 붙여야 한다.
    // 예) Member.GUEST

    Member m5 = new Member();
    m5.id = "bbb";
    m5.password = "1111";
    m5.type = MANAGER;

    Member m6 = new Member();
    m6.id = "ccc";
    m6.password = "1111";
    m6.type = MEMBER;
  }
}

 

public class Member {
  public static final int GUEST = 0;
  public static final int MEMBER = 1;
  public static final int MANAGER = 2;

  String id;
  String password;
  int type; // 0: 손님, 1: 회원, 2: 관리자
}

 

 

 

인스턴스 메서드, 클래스 메서드

 

 

인스턴스 메서드와 클래스 메서드
public class Exam0210 {
  static class A {
    //1) 클래스 메서드 = static 메서드
    //   => static 붙은 메서드이다.
    //   => 클래스 이름으로 호출할 수 있다.
    static void m1() {
      System.out.println("m1()");
    }

    //2) 인스턴스 메서드 = non-static 메서드
    //   => static 이 붙지 않은 메서드이다.
    //   => 인스턴스 주소가 있어야만 호출할 수 있다.
    void m2() {
      System.out.println("m2()");
    }
  }

  public static void main(String[] args) {

    // 클래스 메서드 호출
    // 문법) 클래스명.메서드명();
    A.m1(); // OK!

    // => 인스턴스 메서드는 클래스 이름으로 호출할 수 없다.
    //    A.m2(); // 컴파일 오류!

    A obj1 = new A();

    obj1.m1(); // OK! 그런데, 이렇게 하지 말라!
    // 물론 "클래스 메서드"를 인스턴스 주소를 사용하여 호출할 수 있지만,
    // 다른 개발자가 그냥 "인스턴스 메서드"인 줄 착각할 수 있기 때문에
    // 이렇게 호출하지 말기를 권고한다!
    obj1.m2(); // OK! 인스턴스 메서드는 반드시 인스턴스 주소를 사용하여 호출해야 한다.

    A obj2 = null;
    obj2.m2(); // 컴파일은 OK! 실행은 오류!
    // 왜? obj2 변수에 들어 있는 인스턴스 주소가 무효하기 때문이다.
  }
}

 결론!
 클래스 메서드(=스태틱 메서드)
   => 인스턴스 변수를 사용하지 않을 경우 클래스 메서드로 선언하라!
 인스턴스 메서드
   => 인스턴스 변수를 사용할 경우 인스턴스 메서드로 선언하라!

 실무
 => 일단 인스턴스 메서드로 무조건 만들라!
 => 인스턴스 변수를 완전히 사용하지 않음을 확신하면 
    그 때 클래스 메서드로 전환하라!

 

 

인스턴스 메서드 - 내장 변수 this
public class Exam0220 {

  static class A {

    int value;

    static void m1() {
      // 클래스 메서드는 인스턴스 주소 없이 호출되기 때문에 
      // 인스턴스 주소를 받는 내장 변수가 없다.
      //      this.value = 100; // 컴파일 오류!
    }

    void m2() {
      // 인스턴스 메서드는 인스턴스 주소가 있어야만 호출되기 때문에
      // 인스턴스 주소를 받을 변수가 내장되어 있다.
      this.value = 100;
    }

    void m3() {
      // 인스턴스 변수를 사용할 때 this를 생략할 수 있다.
      //this.value = 200;
      value = 200; // 앞에 자동으로 this가 붙는다.
    }

    void m4(int value) {
      // 로컬 변수의 이름이 인스턴스 이름과 같을 경우 
      // this를 붙이지 않으면 로컬 변수를 가리킨다.
      value = 200; // 로컬 변수이다.

      this.value = 300; // 인스턴스 변수이다.
      // 따라서 로컬 변수가 아닌 인스턴스 변수를 가리키고 싶으면 this를 붙여야 한다.
    }
  }
  public static void main(String[] args) {
    // 클래스 메서드는 인스턴스 주소 없이 클래스 이름으로 호출한다.
    // 그래서 클래스 메서드는 this라는 내장 변수가 없는 것이다.
    A.m1();

    A obj1 = new A();

    // 인스턴스 메서드는 인스턴스 주소가 있어야만 호출할 수 있기 때문에
    // 인스턴스 메서드에는 인스턴스 주소를 받을 수 있는 내장 변수 this가 있다.
    obj1.m2();

    // 물론 클래스 메서드도 레퍼런스를 가지고 호출할 수 있지만,
    // 인스턴스 주소가 메서드에 전달되지 않는다.
    // 그래서 클래스 메서드는 내장 변수 this가 없다.
    obj1.m1();

    A obj2 = new A();

    // 인스턴스 메서드의 this 변수는 메서드를 호출할 때 전달한 인스턴스 주소 값을 가진다.
    // 그래서 메서드가 호출될 때 마다 this 변수의 값이 바뀐다.
    obj2.m2();
    obj2.m3();
    obj2.m4(100);

  }
}

 

 

인스턴스와 인스턴스 변수, 인스턴스 메서드
public class Exam0230 {
  static class Calculator {
    // new 명령을 실행하면, 다음 변수 선언이 실행되어 Heap에 메모리를 만든다.
    int result;

    // 주의!
    // => 이름에 인스턴스가 붙었다고 해서 인스턴스 메서드는 Heap에 만들어지는 것이 아니다!
    // => 클래스의 모든 코드는 Method Area 영역에 로딩 된다.
    public void plus(int value) {
      this.result += value;
    }
    public void minus(int value) {
      this.result -= value;
    }
  }

  public static void main(String[] args) {

    // Heap에 인스턴스 변수가 생성된다.
    // 인스턴스 메서드가 생성되는 것이 아니다!
    Calculator c1 = new Calculator();
    Calculator c2 = new Calculator();

    // 인스턴스 메서드든 클래스 메서드든 모두 Method Area 영역에 올라간다.
    // 그리고 인스턴스를 가지고 그 메서드를 호출하는 것이다.

    // c1이 가리키는 인스턴스를 가지고 Method Area에 있는 plus()를 호출한다.
    c1.plus(123);

    // c2가 가리키는 인스턴스를 가지고 Method Area에 있는 plus()를 호출한다.
    c2.plus(30);
  }
}

 

 

클래스 메서드 응용
public class Exam0310 {

  static class Math {
    // 다음 메서드와 같이 인스턴스 변수를 사용하지 않고 
    // 파라미터 값을 가지고 작업하는 경우에 보통 클래스 메서드로 정의한다.
    public static int abs(int value) {
      if (value < 0)
        return value * -1;
      return value;
    }
  }

  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);
    System.out.print("숫자를 입력하세요> ");
    int value = keyScan.nextInt();

    // 이렇게 특정 인스턴스의 변수를 다루는 것이 아니라
    // 외부에서 전달한 파라미터 값을 가지고 작업을 수행하는 메서드인 경우
    // 클래스 메서드로 정의하면 사용하기 편하다!
    int result = Math.abs(value);

    // 만약 abs() 메서드가 인스턴스 메서드라면,
    // 다음과 같이 인스턴스를 만든 후에 호출해야 한다.
    // abs()는 인스턴스 변수를 사용하지도 않는데 
    // 이렇게 인스턴스를 생성해야 한다면 얼마나 낭비적인가!
    //
    //    Math m = new Math();
    //    int result = m.abs(value);
    //

    System.out.printf("절대값=%d\n", result);
  }
}

 

 

인스턴스 메서드 응용
public class Exam0320 {

  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float average;

    // 다음 메서드와 같이 인스턴스 변수를 사용하는 경우 인스턴스 메서드로 정의한다.
    public void compute() {
      // 내장 변수 this에는 compute()를 호출할 때 넘겨준 인스턴스 주소가 들어 있다.
      this.sum = this.kor + this.eng + this.math;
      this.average = this.sum / 3f;
    }
  }

  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    System.out.print("성적 데이터를 입력하세요(예: 홍길동 100 100 100)> ");
    Score s1 = new Score();
    s1.name = keyScan.next();
    s1.kor = keyScan.nextInt();
    s1.eng = keyScan.nextInt();
    s1.math = keyScan.nextInt();

    System.out.print("성적 데이터를 입력하세요(예: 홍길동 100 100 100)> ");
    Score s2 = new Score();
    s2.name = keyScan.next();
    s2.kor = keyScan.nextInt();
    s2.eng = keyScan.nextInt();
    s2.math = keyScan.nextInt();

    // 특정 인스턴스에 대해 작업을 수행할 때는 인스턴스 메서드를 호출한다.
    s1.compute(); // s1에 들어 있는 인스턴스 주소는 compute()에 전달된다. 
    s2.compute(); // 이번에는 s2에 들어 있는 주소를 compute()에 전달한다.


    System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
        s1.name, s1.kor, s1.eng, s1.math, s1.sum, s1.average);

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
        s2.name, s2.kor, s2.eng, s2.math, s2.sum, s2.average);
  }
}

 

 

 

생성자

 

 

특별한 인스턴스 메서드 - 생성자(constructor)
public class Exam0410 {

  // Exam0410 클래스 조차도 기본 생성자가 자동으로 추가된다.
  // 즉 다음의 생성자를 컴파일러가 자동으로 붙일 것이다.
  //public Exam0410() {}

  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float average;

    // 생성자
    // => 클래스 이름과 같은 이름으로 메서드를 만든다.
    // => 리턴 타입을 선언하지 않는다. 즉 값을 리턴하지 않는다.
    // => 인스턴스를 만들 때 자동 호출된다. 
    //    일반 메서드처럼 나중에 따로 호출할 수 없다.
    // => 만약 개발자가 생성자를 만들지 않으면 컴파일러가 기본 생성자를 자동으로 추가한다.
    //    따라서 모든 클래스는 반드시 한 개 이상의 생성자가 있다.
    // => 보통 인스턴스를 만든 후에 인스턴스 변수를 초기화시키는 용도로 정의한다.
    // => 생성된 인스턴스가 제대로 쓰일 수 있도록 유효한 값으로 초기화시키는 일을 한다.
    // => 다음과 파라미터가 없는 생성자가 기본 생성자이다.(default constructor)
    Score() {
      System.out.println("Score()");
    }


    public void compute() {
      this.sum = this.kor + this.eng + this.math;
      this.average = this.sum / 3f;
    }
  }

  public static void main(String[] args) {
    // 생성자를 호출하는 방법
    // => 인스턴스를 생성하는 new 명령에서 호출할 생성자의 파라미터 값을 지정하면 된다.
    // => 다음과 같이 파라미터 값을 주지 않으면 기본 생성자를 호출하라는 뜻이 된다.
    Score s1 = new Score(); // 드디어 클래스 명 다음에 오는 괄호()의 의미를 알게 되었다.

    // 생성자를 호출하지 않을 수 있는가?
    // => 컴파일 오류!
    //    Score s2 = new Score;

    Score s3 = new Score();

    //    s3.Score(); // 생성자는 따로 호출할 수 없다!
  }
}

 

 

생성자 - 파라미터를 받는 생성자
public class Exam0420 {

  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float average;

    // 생성자가 한 개라도 있으면 컴파일러는 자동으로 기본 생성자를 만들어주지 않는다.
    //      public Score() {}

    Score(String name, int kor, int eng, int math) {
      System.out.println("Score(String,int,int,int) 호출!");
      // 파라미터로 받은 값을 새로 만든 인스턴스 변수에 저장한다.
      // => 이렇게 생성자는 새로 만든 인스턴스 변수를 초기화시키는 일을 한다.
      this.name = name;
      this.kor = kor;
      this.eng = eng;
      this.math = math;
      this.compute();
    }

    public void compute() {
      this.sum = this.kor + this.eng + this.math;
      this.average = this.sum / 3f;
    }
  }

  public static void main(String[] args) {
    // Score 클래스에는 기본 생성자가 없기 때문에
    // 다음 문장은 컴파일 오류이다!
    //    Score s0 = new Score();

    // Score 인스턴스를 만든 후에는 
    // 생성자를 호출할 때 그 생성자의 파라미터 값을 주어야 한다.
    Score s1 = new Score("홍길동", 100, 90, 77);
    Score s2 = new Score("임꺽정", 80, 88, 87);

    // 생성자에서 이미 계산을 수행했기 때문에 
    // 합계와 평균을 계산하기 위해 따로 compute()를 호출할 필요가 없다.
    // 이것이 생성자를 사용하는 이유이다.
    // 생성자를 사용하면 좀 더 코드가 간결해진다.
    System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
        s1.name, s1.kor, s1.eng, s1.math, s1.sum, s1.average);

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
        s2.name, s2.kor, s2.eng, s2.math, s2.sum, s2.average);
  }
}

 생성자?
 => 인스턴스(객체)를 생성한 후에 사용하기 전에(제대로 쓰일 수 있도록) 
    유효한(적당한) 값으로 초기화시키는 작업을 수행한다.

 

 

생성자 - 여러 개의 생성자 정의하기
public class Exam0430 {

  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float average;

    Score() {
      System.out.println("Score()");
      this.name = "이름없음";
    }

    Score(String name) {
      System.out.println("Score(String)");
      this.name = name;
    }

    Score(String name, int kor, int eng, int math) {
      System.out.println("Score(String,int,int,int) 호출!");
      this.name = name;
      this.kor = kor;
      this.eng = eng;
      this.math = math;
      this.compute();
    }

    public void compute() {
      this.sum = this.kor + this.eng + this.math;
      this.average = this.sum / 3f;
    }
  }

  public static void main(String[] args) {

    // 생성자가 여러 개 일 때 파라미터에 전달하는 값으로 호출될 생성자를 구분한다.
    Score s1 = new Score();

    // 인스턴스 생성 후에 나중에 따로 생성자를 호출할 수 없다!
    //    s1.Score("홍길동", 100, 90, 77); // 컴파일 오류!

    Score s2 = new Score("유관순");
    Score s3 = new Score("홍길동", 100, 90, 77);
    //    Score s4 = new Score(true); // 논리 값을 받는 생성자는 없다!

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
        s1.name, s1.kor, s1.eng, s1.math, s1.sum, s1.average);

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
        s2.name, s2.kor, s2.eng, s2.math, s2.sum, s2.average);

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
        s3.name, s3.kor, s3.eng, s3.math, s3.sum, s3.average);
  }
}

 

 

생성자 - this()
public class Exam0440 {

  static class Score {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float average;

    Score() {
      // 다른 생성자를 호출할 때는 this()를 사용한다.
      // 단, 생성자의 첫 문장으로 와야 한다.

      this("이름없음"); // Score(String) 생성자 호출
      System.out.println("Score()");
    }

    Score(String name) {
      // 다른 생성자를 호출할 때는 this()를 사용한다.
      // 단, 생성자의 첫 문장으로 와야 한다.
      this(name, 0, 0, 0); // Score(String, int, int, int) 생성자 호출

      System.out.println("Score(String)");
    }

    Score(String name, int kor, int eng, int math) {
      System.out.println("Score(String,int,int,int) 호출!");
      this.name = name;
      this.kor = kor;
      this.eng = eng;
      this.math = math;
      this.compute();
    }

    public void compute() {
      //      this("오호라"); // 일반 메서드에서 생성자를 호출할 수 없다!
      this.sum = this.kor + this.eng + this.math;
      this.average = this.sum / 3f;
    }
  }

  public static void main(String[] args) {

    // 생성자가 여러 개 일 때 파라미터에 전달하는 값으로 구분한다.
    Score s1 = new Score();

    // 인스턴스 생성 후에 나중에 따로 생성자를 호출할 수 없다!
    //    s1.Score("홍길동", 100, 90, 77); // 컴파일 오류!
    System.out.println("--------------------------------");


    Score s2 = new Score("유관순");
    System.out.println("--------------------------------");

    Score s3 = new Score("홍길동", 100, 90, 77);
    System.out.println("--------------------------------");
    // Score s4 = new Score(true); // 논리 값을 받는 생성자는 없다!

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n", s1.name, s1.kor, s1.eng, s1.math, s1.sum,
        s1.average);

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n", s2.name, s2.kor, s2.eng, s2.math, s2.sum,
        s2.average);

    System.out.printf("%s, %d, %d, %d, %d, %.1f\n", s3.name, s3.kor, s3.eng, s3.math, s3.sum,
        s3.average);
  }
}

 

 

생성자 - 생성자 호출 막기 = 인스턴스 생성을 막기
class X {
  // 생성자의 접근 범위를 private으로 설정하면 외부에서 접근할 수 없기 때문에
  // 생성자를 호출할 수 없다.
  // 결국 인스턴스를 생성하지 못하게 만든다.
  private X() {
    System.out.println("X()");
  }
}

public class Exam0450 {
  public static void main(String[] args) {
    X obj1;

    // 생성자의 접근이 막혀있기 때문에 생성자를 호출할 수 없다.
    // 따라서 new 명령으로 객체를 생성할 수 없도록 만든다.
    //    obj1 = new X(); // 컴파일 오류!

  }
}

 

 

변수 자동 초기화 - 스태틱 변수
public class Exam0510 {

  static class A {
    static byte b;
    static short s;
    static int i;
    static long l;
    static char c;
    static float f;
    static double d;
    static boolean bool;
    static String str;
    static Date date;
  }

  public static void main(String[] args) {

    // 클래스 변수(스태틱 변수)는 생성되는 순간 0으로 자동 초기화 된다.
    // => 정수(byte, short, int, long, char) : 0
    // => 부동소수점(float, double) : 0.0 = 0
    // => 논리(boolean) : false = 0
    // => 레퍼런스: null = 0
    //
    System.out.printf("%d, %d, %d, %d, %c, %.1f, %.1f, %b, %s, %s\n", 
        A.b, A.s, A.i, A.l, A.c, A.f, A.d, A.bool, A.str, A.date);

    // 로컬 변수는 자동으로 초기화되지 않는다.
    // 사용하기 전에 반드시 초기기화시켜야 한다.
    // 다음과 같이 초기화시키지 않은 상태에서 사용하면 컴파일 오류가 발생한다.
    // => 어떤 값으로 초기화시키나요? 
    //    변수를 만든 후 처음에 어떤 값으로 시작할 지 개발자가 결정한다.
    //
    //    int x;
    //    System.out.println(x); // 컴파일 오류!
  }
}

 

 

변수 자동 초기화 - 인스턴스 변수
public class Exam0520 {

  static class A {
    byte b2;
    short s2;
    int i2;
    long l2;
    char c2;
    float f2;
    double d2;
    boolean bool2;
    String str2;
    Date date2;
  }

  public static void main(String[] args) {

    // 인스턴스 변수도 클래스 변수와 마찬가지이다.
    // 힙(heap)에 생성되는 인스턴스 필드(변수)는 자동으로 초기화된다.
    // 그래서 인스턴스 필드(변수)는 생성되는 즉시 0으로 초기화 된다.
    // => 정수(byte, short, int, long, char) : 0
    // => 부동소수점(float, double) : 0.0 = 0
    // => 논리(boolean) : false = 0
    // => 레퍼런스: null = 0
    A obj = new A();

    System.out.printf("%d, %d, %d, %d, %c, %.1f, %.1f, %b, %s, %s\n", 
        obj.b2, obj.s2, obj.i2, obj.l2, obj.c2, obj.f2, obj.d2, obj.bool2, obj.str2, obj.date2);
  }
}

 

 

변수 자동 초기화 - 로컬 변수
public class Exam0530 {

  public static void main(String[] args) {

    // 로컬 변수는 자동으로 초기화되지 않는다.
    // 반드시 변수를 사용하기 전에 초기화 시켜야 한다.
    // 초기화하지 않은 로컬 변수는 사용할 수 없다.
    // 초기화?
    // - 값을 한 번이라도 저장하는 것!
    int i;
    String str;
    Date date;

    // 다음과 같이 초기화시키지 않은 로컬 변수를 사용하려고 하면 컴파일 오류가 발생한다!
    //    System.out.println(i); // 컴파일 오류!
    //    System.out.println(str); // 컴파일 오류!
    //    System.out.println(date); // 컴파일 오류!
  }
}

 

 

스태틱 초기화 블록(static initializer) - 레퍼런스 선언
public class Exam0610 {

  public static class A {

    static {
      System.out.println("Static{} 11111");
    }

    static int a;

    static void m() {}

    // 클래스 로딩,
    // 1) 스태틱 필드를 만든다(Method Area)
    // 2) 스태틱 블록을 실행한다.
    // 
    // 컴파일할 때
    // - 여러 개의 스태틱 블록이 있으면 컴파일러는 한 개의 블록으로 합친다.
    // - 스태틱 블록의 위치에 상관없이 작성된 순서대로 한 개의 static 블록으로 합친다.
    // - 바이트코드(Exam0610$A.class)를 확인해 보라.
    //
    static {
      System.out.println("Static{} 22222");
    }
  }

  public static void main(String[] args) throws Exception {

    // 클래스가 로딩되는 경우,
    // 1) 스태틱 필드나 스태틱 메서드를 사용할 때
    // 2) 인스턴스를 생성할 때(new 명령을 실행할 때)
    //
    // 주의!
    // - 각각의 클래스는 딱 한 번만 로딩된다.
    // - 레퍼런스를 선언할 때는 클래스가 로딩되지 않는다.
    // - 그래서 스태틱 초기화 블록(static initializer)이 실행되지 않는다.
    A obj1;
    A obj2;

    System.out.println("종료!");
  }
}

 

 

스태틱 초기화 블록(static initializer) - 클래스 필드 사용
public class Exam0620 {

  public static class A {
    static int a;

    static void m() {}

    // 클래스가 로딩될 때 스태틱 초기화 블록은 실행된다.
    // 여러 개의 스태틱 블록이 있을 때, 컴파일러는 한 개의 블록으로 합친다.
    // - 바이트코드(Exam0620$A.class)를 확인해 보라.
    //
    static {
      System.out.println("Static{} 11111");
    }

    static {
      System.out.println("Static{} 22222");
    }
  }

  public static void main(String[] args) throws Exception {
    System.out.println("main() 호출됨!");

    // 클래스가 로딩되는 경우,
    // 1) 클래스 멤버(필드와 메서드)를 사용할 때,
    //    클래스가 로딩된 상태가 아니라면 클래스를 로딩한다.
    A.a = 100;

    System.out.println("종료!");
  }
}

 

 

스태틱 초기화 블록(static initializer) - 클래스 메서드 사용
public class Exam0630 {

  public static class A {
    static int a;

    static void m() {
      System.out.println("A.m()");
    }

    // 클래스가 로딩될 때 스태틱 초기화 블록은 실행된다.
    // 여러 개의 스태틱 블록이 있을 때, 컴파일러는 한 개의 블록으로 합친다.
    // - 바이트코드(Exam0630$A.class)를 확인해 보라.
    //
    static {
      System.out.println("Static{} 11111");
    }

    static {
      System.out.println("Static{} 22222");
    }
  }

  public static void main(String[] args) throws Exception {

    // 클래스가 로딩되는 경우,
    // 2) 클래스 멤버(필드와 메서드)를 사용할 때,
    //    클래스가 로딩된 상태가 아니라면 클래스를 로딩한다.
    //
    A.m();
    System.out.println("종료!");
  }
}

 

 

스태틱 초기화 블록(static initializer) - 인스턴스 생성
public class Exam0640 {

  public static class A {
    static int a;

    static void m() {}

    // 클래스가 로딩될 때 스태틱 초기화 블록은 실행된다.
    // 여러 개의 스태틱 블록이 있을 때, 컴파일러는 한 개의 블록으로 합친다.
    // - 바이트코드(Exam0640$A.class)를 확인해 보라.
    //
    static {
      System.out.println("Static{} 11111");
    }

    static {
      System.out.println("Static{} 22222");
    }
  }

  public static void main(String[] args) throws Exception {

    // 클래스가 로딩되는 경우,
    // 3) 해당 클래스의 인스턴스를 생성할 때
    //    클래스가 로딩된 상태가 아니라면 클래스를 로딩한다.
    // 
    // - 인스턴스를 만들려면 설계도가 있어야 하고,
    // - 설계도는 메모리에 로딩되어 있어야 한다.
    // - 따라서 설계도가 없으면 즉시 설계도를 로딩할 것이다.
    //
    new A();
    System.out.println("-------------------------------");

    new A();
    System.out.println("-------------------------------");

    new A();
    System.out.println("-------------------------------");

    System.out.println("종료!");
  }
}

 

 

스태틱 초기화 블록(static initializer) - Class.forName()
public class Exam0650 {

  public static class A {
    static int a;

    static void m() {}

    // 클래스가 로딩될 때 스태틱 초기화 블록은 실행된다.
    // 여러 개의 스태틱 블록이 있을 때, 컴파일러는 한 개의 블록으로 합친다.
    // - 바이트코드(Exam0650$A.class)를 확인해 보라.
    //
    static {
      System.out.println("Static{} 11111");
    }

    static {
      System.out.println("Static{} 22222");
    }
  }

  public static void main(String[] args) throws Exception {

    // 클래스가 로딩되는 경우
    // 4) 자바에서 제공하는 도구를 사용하여 클래스를 로딩할 때,
    // - 이미 클래스가 로딩되어 있다면 다시 로딩하지 않는다.
    // - 메모리 절약을 위해, 자바는 중복해서 클래스를 메모리에 로딩하지 않는다.
    //
    Class.forName("com.eomcs.oop.ex03.Exam0650$A"); 
    // import 하는 것과 상관없이 반드시 패키지 이름을 포함해서 클래스 이름을 지정해야 한다.
    // 주의!
    // => import 문장에서는 $ 대신 .을 써야 한다.
    //    예) import com.eomcs.oop.ex03.Exam0650.A;

    System.out.println("----------------------------------");

    Class.forName("com.eomcs.oop.ex03.Exam0650$A"); // 클래스는 중복 로딩되지 않는다.
    System.out.println("----------------------------------");

    System.out.println("종료!");
  }
}

 

 

스태틱 초기화 블록(static initializer) - 중복 로딩 불가 확인
public class Exam0660 {

  public static class A {
    static {
      int x = 200;
      System.out.println(x);
      System.out.println("Static{} 11111");
    }
    static int a;


    // 클래스가 로딩될 때 스태틱 초기화 블록은 실행된다.
    // 여러 개의 스태틱 블록이 있을 때, 컴파일러는 한 개의 블록으로 합친다.
    // - 바이트코드(Exam0660$A.class)를 확인해 보라.
    //

    static {
      String x = "Hello";
      System.out.println(x);
      System.out.println("Static{} 22222");
    }

    static void m() {}

    static {
      int x = 300;
      System.out.println(x);
      System.out.println("Static{} 33333");
    }
  }

  public static void main(String[] args) throws Exception {

    // 클래스가 로딩되는 경우
    // 1) 클래스 멤버(변수와 메서드)를 사용할 때 클래스가 로딩되어 있지 않다면,
    //    A.a = 100;

    //    A.m();

    // 2) 해당 클래스의 인스턴스를 생성할 때 클래스가 로딩되어 있지 않다면,
    //    new A();

    // 3) 자바에서 제공하는 도구를 사용하여 클래스를 로딩할 때 클래스가 로딩되어 있지 않다면,
    //    Class.forName("com.eomcs.oop.ex03.Exam0660$A");
    //
    // 클래스가 로딩된 후
    // 1) 클래스 필드가 생성된다.
    // 2) 클래스 블록(스태틱 블록)이 실행된다.
    //

    // 레퍼런스를 선언할 때는 클래스가 로딩되지 않는다.
    A obj;
    System.out.println("------------------------------");

    // 클래스는 중복 로딩되지 않는다.
    A.a = 100;
    //    A.m();
    //    new A();
    //    Class.forName("com.eomcs.oop.ex03.Exam0660$A");
    System.out.println("------");

    // 만약 이전에 클래스가 로딩되었다면, 다시 로딩하지 않는다.
    // - 즉 static 블록을 다시 실행하지 않는다!
    new A(); 

  }
}

 

 

스태틱 초기화 블록(static initializer) - 변수 초기화 문장(variable initializer)
public class Exam0670 {

  public static class A {
    static int a = 100;

    // 위 문장 해석:
    // 1) "클래스가 로딩될 때" int 타입의 메모리를 만들고 그 메모리의 이름을 'a'라고 하라.
    // 2) 'a'라는 변수가 생성된 후 100으로 초기화시켜라.

    // 변수 초기화 문장(variable initializer)?
    // - 변수를 만들자마자 어떤 값으로 초기화시켜야 하는지 명령하는 문장.
    // - 변수 선언문에 할당 연산자(=)를 붙여서 초기화할 값을 지정한다.
    //
    // 변수 초기화 문장을 컴파일 할 때,
    // - 스태틱 초기화 블록이 없으면 컴파일러가 자동으로 삽입한다.
    // - 스태틱 초기화 블록에 a 에 100을 할당하는 문장을 삽입한다.
    // - 위의 문장은 다음 문장으로 바뀐다.
    //
    //    static int a;
    //    static {
    //      a = 100;
    //    }
    // - 바이트 코드(Exam0670$A.class)를 확인해 보라!
  }

  public static void main(String[] args) throws Exception {

    System.out.println(A.a);
  }
}

 

public class Exam0680 {

  public static class A {
    static {
      b = 400;
    }

    static int a = 100; 

    static {
      a = 200;
      System.out.println("static {} 실행");
    }

    static int b = 300;

    // 변수 초기화 문장(variable initializer)을 컴파일 할 때,
    // 1) 스태틱 변수 선언에서 변수 초기화 문장을 별도의 스태틱 블록으로 분리한다.
    //
    //     static {
    //       b = 400;
    //     }
    //     static int a;
    //     static {
    //       a = 100;
    //     }
    //     static {
    //       a = 200;
    //       System.out.println("static {} 실행");
    //     }
    //     static int b;
    //     static {
    //       b = 300;
    //     }
    // 2) 스태틱 블록을 한 개의 블록으로 합친다.
    //     static int a;
    //     static int b;
    //
    //     static {
    //       b = 400;
    //       a = 100;
    //       a = 200;
    //       System.out.println("static {} 실행");
    //       b = 300;
    //     }
    // - 바이트 코드(Exam0680$A.class)를 확인해 보라!
  }

  public static void main(String[] args) throws Exception {

    System.out.println(A.a);
    System.out.println(A.b);
  }
}

 

스태틱 초기화 블록(static initializer) - 활용
public class Exam0690 {

  public static class A {
    static float pi;

    // 스태틱 블록은 클래스 멤버를 사용하기 전에 유효한 값으로 초기화시키는 것.
    static {
      pi = 3.14159f;
    }

    static float area(int r) {
      return pi * r * r;
    }

  }

  public static void main(String[] args) throws Exception {
    System.out.println(A.area(25));
  }
}

 

 

스태틱 초기화 블록(static initializer) - 활용 II : 클래스 로딩과 스태틱 블록 실행
public class Exam0691 {

  static class A {
    static int a = 7;

    static {
      System.out.println("A.static{}");
      a += B.b;
    }
  }

  static class B {
    static int b = 22;

    static {
      System.out.println("B.static{}");
      b += A.a;
    }
  }

  public static void main(String[] args) {
    System.out.println(A.a); // ?
    System.out.println(B.b); // ?
  }
}

클래스 로딩 절차
1) 클래스를 Method Area에 로딩한다.
2) 스태틱 변수를 만든다.
3) 스태틱 블록을 실행한다.

클래스 로딩
=> 클래스 멤버(변수, 메서드)를 사용할 때
=> Class.forName("클래스명") 을 통해 임의로 로딩할 때
=> 단 한 번 로딩된 클래스는 다시 로딩하지 않는다.

스태틱 블록의 목적
=> 클래스 멤버(스태틱 변수, 스태틱 메서드)를 사용하기 전에 유효한 값으로 초기화 시키는 것이다.

 

 

인스턴스 초기화 블록(instance initializer) - 인스턴스 초기화 블록
public class Exam0710 {

  static class A {
    int a;
    int b;
    int c;

    // 인스턴스 초기화 블록 사용 후
    // - 여러 생성자에 공통으로 존재해야 하는 코드가 있다면
    //   별도의 블록으로 뽑아내는 것이 소스 코드 관리에 좋다.
    // - 이럴 때 사용하라고 만든 문법이 인스턴스 블록이다.
    // - 다음과 같이 인스턴스 초기화 블록을 사용하여 
    //   생성자에 공통으로 들어갈 코드를 작성하면 된다.
    //
    {
      a = 100; // a 변수 앞에 this가 생략됨
      System.out.println("Hello!");
    }

    // 생성자가 없으면,
    // - 다음과 같이 컴파일러는 기본 생성자를 만든 후 
    //   생성자의 시작 부분에 인스턴스 초기화 블록에 들어 있는 코드를 삽입한다.
    // - 그리고 인스턴스 초기화 블록은 제거된다.
    // - 바이트코드(Exam0710$A.class)를 확인해 보라!
    //
    //    public A() {
    //      a = 100;
    //      System.out.println("Hello!");
    //    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

인스턴스 초기화 블록(instance initializer) - 인스턴스 초기화 블록과 생성자
public class Exam0720 {

  static class A {
    int a;
    int b;
    int c;

    // 인스턴스 초기화 블록 사용 후
    // - 여러 생성자에 공통으로 존재하는 코드가 있다면
    //   별도의 블록으로 뽑아내는 것이 소스 코드 관리에 좋다.
    // - 이럴 때 사용하라고 만든 문법이 인스턴스 초기화 블록이다.
    // - 다음과 같이 인스턴스 초기화 블록을 사용하여 생성자에 공통으로 들어갈
    //   코드를 작성하면 된다.
    //
    {
      a = 100;
      System.out.println("인스턴스 초기화 블록 실행!");
    }

    // 생성자가 있으면,
    // - 존재하는 생성자의 앞 부분에 삽입된다.
    // - 바이트코드(Exam0720$A.class)를 확인해 보라!
    public A() {
      a = 200;
      System.out.println("A() 생성자 호출");
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

인스턴스 초기화 블록(instance initializer) - 인스턴스 초기화 블록과 여러 개의 생성자
public class Exam0730 {

  static class A {
    int a;
    int b;
    int c;

    A() {
      // 자바 컴파일러는 필드 초기화(variable initializer)나 
      // 인스턴스 초기화 블록(initializer block)이 있으면 
      // 선언된 순서대로 모든 생성자의 첫 부분에 복사한다.
      // 즉 선언된 초기화 블록에 안에 있는 코드가 그대로 다음과 같이 
      // 복사된다.
      // Java Tutorial 문서: Learning the Java Language > Classes and Objects
      // 'The Java compiler copies initializer blocks 
      //  into every constructor. 
      //  Therefore, this approach can be used 
      //  to share a block of code 
      //  between multiple constructors.'
      // 
      // a = 100;
      // System.out.println("Hello!");

      System.out.println("A()");
      b = 200;
      c = 300;
    }

    A(int b) {
      // 자바 컴파일러는 이 생성자에도 인스턴스 블록의 코드를 그대로 복사한다.
      // a = 100;
      // System.out.println("Hello!");

      System.out.println("A(int)");
      this.b = b;
      this.c = 300;
    }

    A(int b, int c) {
      // 자바 컴파일러는 이 생성자에도 인스턴스 블록의 코드를 그대로 복사한다.
      // a = 100;
      // System.out.println("Hello!");

      System.out.println("A(int, int)");
      this.b = b;
      this.c = c;
    }

    { // 인스턴스 초기화 블록 (initializer block)
      a = 100;
      System.out.println("Hello!");
    }
    // 여러 개의 생성자가 있으면,
    // - 인스턴스 초기화 블록 코드는 존재하는 모든 생성자의 앞 부분에 삽입된다.
    // - 바이트코드(Exam0730$A.class)를 확인해 보라!
  }
  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", 
        obj1.a, obj1.b, obj1.c);
    System.out.println("--------------------------");

    A obj2 = new A(222);
    System.out.printf("a=%d, b=%d, c=%d\n", 
        obj2.a, obj2.b, obj2.c);
    System.out.println("--------------------------");

    A obj3 = new A(222, 333);
    System.out.printf("a=%d, b=%d, c=%d\n", 
        obj3.a, obj3.b, obj3.c);
    System.out.println("--------------------------");
  }
}

 

 

인스턴스 초기화 블록(instance initializer) - 여러 개의 인스턴스 초기화 블록
public class Exam0740 {

  static class A {
    int a;
    int b;
    int c;

    // 여러 개의 인스턴스 초기화 블록이 있을 때,
    // - 선언된 순서대로 생성자의 앞 부분에 삽입된다.
    // - 바이트코드(Exam0740$A.class)를 확인해 보라!
    {
      this.a = 101;
      System.out.println("첫 번째 인스턴스 초기화 블록");
    }

    {
      this.a = 102;
      System.out.println("두 번째 인스턴스 초기화 블록");
    }

    {
      this.a = 103;
      System.out.println("세 번째 인스턴스 초기화 블록");
    }

    A() {
      // 인스턴스 초기화 블록은 선언된 순서대로 삽입한다.
      // 즉 다음과 같다.
      // this.a = 101;
      // System.out.println("첫 번째 인스턴스 초기화 블록");
      // this.a = 102;
      // System.out.println("두 번째 인스턴스 초기화 블록");
      // this.a = 103;
      // System.out.println("세 번째 인스턴스 초기화 블록");

      System.out.println("A()");
      b = 200;
      c = 300;
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

인스턴스 초기화 블록(instance initializer) - 인스턴스 초기화 블록의 위치
public class Exam0750 {

  static class A {
    int a;
    int b;
    int c;

    // 여러 개의 인스턴스 초기화 블록이 있을 때,
    // - 선언된 순서대로 생성자의 앞 부분에 삽입된다.
    // - 인스턴스 초기화 블록이 선언된 위치는 상관없다.
    // - 바이트코드(Exam0750$A.class)를 확인해 보라!
    {
      this.a = 101;
      System.out.println("첫 번째 인스턴스 초기화 블록");
    }

    A() {
      // 인스턴스 초기화 블록은 위치에 상관없이 선언된 순서대로 삽입한다.
      // 즉 다음과 같다.
      // this.a = 101;
      // System.out.println("첫 번째 인스턴스 초기화 블록");
      // this.a = 102;
      // System.out.println("두 번째 인스턴스 초기화 블록");
      // this.a = 103;
      // System.out.println("세 번째 인스턴스 초기화 블록");

      System.out.println("A()");
      b = 200;
      c = 300;
    }

    {
      this.a = 102;
      System.out.println("두 번째 인스턴스 초기화 블록");
    }

    {
      this.a = 103;
      System.out.println("세 번째 인스턴스 초기화 블록");
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

인스턴스 초기화 블록(instance initializer) - 인스턴스 블록의 활용
public class Exam0760 {

  public static void main(String[] args) {

    // 인스턴스 초기화 블록의 용도
    // 1) 여러 생성자에 공통으로 들어가는 초기화 문장을 작성할 때
    // 2) 생성자를 만들지 못하는 상황에서 복잡한 로직에 따라
    //    인스턴스 필드를 초기화시켜야 할 때

    // 생성자를 만들지 못하는 상황?
    // - "익명 클래스"를 만들 때이다.
    // - 클래스 이름이 없기 때문에 생성자를 만들 수 없다.
    //
    // 다음은 Object 클래스를 상속 받은 익명 클래스를 정의하고 객체를 만드는 명령이다.
    Object obj1 = new Object() {
      // 이 클래스는 이름이 없기 때문에 생성자를 만들 수 없다.
      // 그래서 초기화 명령을 작성하려면 인스턴스 블록을 이용해야 한다.
      {
        System.out.println("인스턴스 블록...");
      }
    };

  }
}

 

 

변수 초기화 문장(variable initializer) - 변수 초기화 문장
public class Exam0810 {

  static class A {
    int a = 100;
    int b = 200;
    int c;

    // 인스턴스 필드 초기화 문장은
    // 생성자의 앞 부분에 삽입된다.
    //
    // 생성자가 없으면,
    // - 기본 생성자가 생성되어 앞 부분에 삽입된다.
    // - 바이트코드(Exam0810$A.class)를 확인해 보라!
    //
    // public A() {
    //   a = 100;
    //   b = 200;
    // }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

변수 초기화 문장(variable initializer) - 변수 초기화 문장과 생성자
public class Exam0820 {
  static class A {
    int a = 100;
    int b = 200;
    int c;

    // 인스턴스 필드 초기화 문장은
    // 생성자의 앞 부분에 삽입된다.
    //
    public A() {
      // 생성자가 있으면,
      // - 존재하는 생성자의 앞 부분에 삽입된다.
      // - 바이트코드(Exam0820$A.class)를 확인해 보라!
      // - 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // a = 100;
      // b = 200;

      a = 111;
      c = 333;
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

변수 초기화 문장(variable initializer) - 여러 개의 생성자
public class Exam0830 {

  static class A {
    int a = 100;
    int b = 200;
    int c;

    // 인스턴스 필드 초기화 문장은
    // 생성자의 앞 부분에 삽입된다.
    // 여러 개의 생성자가 있으면 모든 생성자에 삽입된다.
    // - 바이트코드(Exam0830$A.class)를 확인해 보라!

    public A() {
      // 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // a = 100;
      // b = 200;

      c = 300;
    }

    public A(int a) {
      // 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // a = 100;
      // b = 200;

      this.a = a;
      c = 300;
    }

    public A(int a, int b) {
      // 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // a = 100;
      // b = 200;

      this.a = a;
      this.b = b;
      c = 300;
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
    System.out.println("--------------------------");

    A obj2 = new A(111);
    System.out.printf("a=%d, b=%d, c=%d\n", obj2.a, obj2.b, obj2.c);
    System.out.println("--------------------------");

    A obj3 = new A(111, 222);
    System.out.printf("a=%d, b=%d, c=%d\n", obj3.a, obj3.b, obj3.c);
    System.out.println("--------------------------");
  }
}

 

 

변수 초기화 문장(variable initializer) - 여러 개의 생성자
public class Exam0830 {

  static class A {
    int a = 100;
    int b = 200;
    int c;

    // 인스턴스 필드 초기화 문장은
    // 생성자의 앞 부분에 삽입된다.
    // 여러 개의 생성자가 있으면 모든 생성자에 삽입된다.
    // - 바이트코드(Exam0830$A.class)를 확인해 보라!

    public A() {
      // 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // a = 100;
      // b = 200;

      c = 300;
    }

    public A(int a) {
      // 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // a = 100;
      // b = 200;

      this.a = a;
      c = 300;
    }

    public A(int a, int b) {
      // 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // a = 100;
      // b = 200;

      this.a = a;
      this.b = b;
      c = 300;
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
    System.out.println("--------------------------");

    A obj2 = new A(111);
    System.out.printf("a=%d, b=%d, c=%d\n", obj2.a, obj2.b, obj2.c);
    System.out.println("--------------------------");

    A obj3 = new A(111, 222);
    System.out.printf("a=%d, b=%d, c=%d\n", obj3.a, obj3.b, obj3.c);
    System.out.println("--------------------------");
  }
}

 

 

변수 초기화 문장(variable initializer) - 변수 초기화 문장 순서
public class Exam0840 {

  static class A {

    int b = 200;
    int c;

    // 인스턴스 필드 초기화 문장은
    // 선언된 순서대로 모든 생성자의 앞 부분에 삽입된다.
    // - 바이트코드(Exam0840$A.class)를 확인해 보라!

    public A() {
      // 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // b = 200;
      // a = 100;

      c = 300;
    }

    int a = 100;
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a=%d, b=%d, c=%d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

변수 초기화 문장(variable initializer) - 인스턴스 초기화 블록과 필드 초기화, 생성자의 실행 순서
public class Exam0850 {

  static class A {

    int b = 200;
    int c;

    A() {
      // 자바 컴파일러는
      // 인스턴스 초기화 블록이나 필드 초기화 문장이 있다면,
      // 종류에 구분없이 선언된 순서 그대로 모든 생성자의 처음 부분에 복사한다.
      // 즉 다음과 같다.
      //
      // b = 200;
      // a = 111;
      // System.out.println("초기화 블록 실행");
      // a = 100;

      System.out.println("A()");
    }

    // 인스턴스 초기화 블록(initializer block)
    {
      a = 111;
      System.out.println("초기화 블록 실행");
    }

    // 필드 초기화 문장(variable initializer)
    int a = 100;
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a = %d, b = %d, c = %d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

변수 초기화 문장(variable initializer) - 인스턴스 초기화 블록과 필드 초기화, 생성자의 실행 순서
public class Exam0851 {

  static class A {

    int b = 200;
    int c;

    A() {
      // 자바 컴파일러는
      // 인스턴스 초기화 블록이나 필드 초기화 문장이 있다면,
      // 종류에 구분없이 선언된 순서 그대로 모든 생성자의 처음 부분에 복사한다.
      // 즉 다음과 같다.
      //
      // b = 200;
      // a = 100;
      // a = 111;
      // System.out.println("초기화 블록 실행");

      System.out.println("A()");
    }

    // 필드 초기화 문장(variable initializer)
    int a = 100;

    // 인스턴스 초기화 블록(initializer block)
    {
      a = 111;
      System.out.println("초기화 블록 실행");
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.printf("a = %d, b = %d, c = %d\n", obj1.a, obj1.b, obj1.c);
  }
}

 

 

변수 초기화 문장(variable initializer) - 인스턴스 초기화 블록과 필드 초기화, 생성자의 실행 순서
public class Exam0860 {

  static class A {
    // 문법을 헷갈리지 않도록 하기 위해서 가능한 다음 순서로 코드를 작성하라.
    // 1) 필드 선언 및 초기화 문장
    // 2) 스태틱 블록 (가능한 한 개의 블록으로 묶어라.)
    // 3) 인스턴스 블록 (가능한 한 개의 블록으로 묶어라.)
    // 4) 생성자 (기본 생성자를 먼저 두고 파라미터 개수에 따라 나열하라.)
    //

    // 필드 선언 및 초기화 문장(variable initializer)
    int a = 100;

    // 인스턴스 초기화 블록(initializer block)
    {
      this.a = 200;
      System.out.println("초기화 블록");
    }

    A() {
      // a = 100;
      // a = 200;
      // System.out.println("초기화 블록");

      System.out.println("A()");
    }

    A(int a) {
      // a = 100;
      // a = 200;
      // System.out.println("초기화 블록");

      System.out.println("A(int)");
      this.a = a;
    }
  }

  public static void main(String[] args) {
    A obj1 = new A();
    System.out.println(obj1.a);

    System.out.println("-------------------");

    A obj2 = new A(1111);
    System.out.println(obj2.a);
  }
}

 

 

종합 정리 - 인스턴스 필드 기본 값 사용
public class Exam0910 {

  static class Monitor {

    // 인스턴스 변수(클래스 변수 포함)가 생성되면 0으로 자동 초기화 된다.
    // byte, short, int, long = 0
    // float, double = 0.0
    // boolean = false
    // char = '\u0000'
    //
    // 주의! 로컬 변수는 자동 초기화 되지 않는다.
    //
    int bright; // 밝기 (0% ~ 100%)
    int contrast; // 명암 (0% ~ 100%)
    int widthRes; // 해상도 너비
    int heightRes; // 해상도 높이

    void display() {
      System.out.println("----------------------------------");
      System.out.printf("밝기(%d)\n", this.bright);
      System.out.printf("명암(%d)\n", this.contrast);
      System.out.printf("해상도(%d x %d)\n", this.widthRes, this.heightRes);
      System.out.println("----------------------------------");
    }
  }

  public static void main(String[] args) {
    // 모니터 인스턴스 생성
    Monitor m1 = new Monitor();

    // 출력결과는 모두 0이다.
    m1.display();

    // 모니터의 중요 필드 값을 초기화시키지 않고 사용하면 제대로 동작이 안 될 수 있다.
    // 현실 세계에서 모니터의 각 값들이 유효한 기본 값으로 초기화 되지 않은 상태에서
    // 모니터를 켠다면? 까만 화면만 볼 뿐이다.
    // 사용자는 모니터가 고장난 줄 알고 AS를 요청할 것이다.

    // 그래서 모니터를 만든 후 출하하기 전에
    // 모니터의 인스턴스 필드의 값을 유효한 값으로 설정해야 한다.
    // 그래야 모니터의 기능(funciton=method)을 제대로 사용할 수 있다.
    //

    // 다음은 모니터 인스턴스를 만든 후 에 모니터 인스턴스를 사용하는 사용자가
    // 직접 값을 초기화시킨 후 사용하는 예이다.
    m1.bright = 50;
    m1.contrast = 50;
    m1.widthRes = 1920;
    m1.heightRes = 1080;

    m1.display();

    // 고객의 의견!
    // => 모니터를 구매한 후 바로 사용할 수 있도록 모니터의 속성 값들이 유효한 값으로
    //    미리 초기화 되었으면 좋겠습니다!
    //
    // 이 클래스를 사용하는 다른 개발자의 의견!
    // => 인스턴스의 메서드를 사용하는데 문제가 없도록 인스턴스의 각 필드를 유효한 값으로
    //    미리 초기화 했으면 좋겠습니다!

  }
}

 

 

종합 정리 - 인스턴스 필드 초기화(variable initializer) 적용
public class Exam0920 {

  static class Monitor {

    // 초기화 문장?
    // 변수를 선언할 때 값을 설정하는 것을 초기화 문장이라 부른다.
    //
    int bright = 50; // 밝기 (0% ~ 100%)
    int contrast = 50; // 명암 (0% ~ 100%)
    int widthRes = 1920; // 해상도 너비
    int heightRes = 1080; // 해상도 높이

    void display() {
      System.out.println("----------------------------------");
      System.out.printf("밝기(%d)\n", this.bright);
      System.out.printf("명암(%d)\n", this.contrast);
      System.out.printf("해상도(%d x %d)\n", this.widthRes, this.heightRes);
      System.out.println("----------------------------------");
    }
  }

  public static void main(String[] args) {
    // 모니터 인스턴스 생성
    Monitor m1 = new Monitor();

    // 인스턴스 필드의 값이 유효한 기본 값들로 미리 초기화 되었기 때문에 바로 사용할 수 있다.
    m1.display();

    // 물론 특정 속성의 값을 바꾼 후에 사용해도 된다.
    m1.bright = 40;

    m1.display();
  }
}

 

 

종합 정리 - 인스턴스 블록(instance initializer) 적용
public class Exam0930 {

  static class Monitor {

    int bright; // 밝기 (0% ~ 100%)
    int contrast; // 명암 (0% ~ 100%)
    int widthRes; // 해상도 너비
    int heightRes; // 해상도 높이

    { // 인스턴스 블록 <= 실무에서는 인스턴스 블록을 잘 사용하지 않는다. 대신에 생성자를 주로 사용한다.
      this.bright = 50;

      // 다음과 같이 인스턴스 필드에 값을 직접 대입하지 못하는 경우
      // 이렇게 인스턴스 초기화 블록에서 값을 설정하면 된다.
      // => 값을 설정하는 것이 복잡할 경우 초기화 문장이 아닌 인스턴스 초기화 블록에서 수행한다.
      if (this.bright < 50)
        this.contrast = 70;
      else
        this.contrast = 40;

      this.widthRes = 1920;
      this.heightRes = 1080;
    }

    void display() {
      System.out.println("----------------------------------");
      System.out.printf("밝기(%d)\n", this.bright);
      System.out.printf("명암(%d)\n", this.contrast);
      System.out.printf("해상도(%d x %d)\n", this.widthRes, this.heightRes);
      System.out.println("----------------------------------");
    }
  }

  public static void main(String[] args) {
    // 모니터 인스턴스 생성
    Monitor m1 = new Monitor();

    // 인스턴스 필드의 값이 유효한 기본 값들로 미리 초기화 되었기 때문에 바로 사용할 수 있다.
    m1.display();

    // 물론 특정 속성의 값을 바꾼 후에 사용해도 된다.
    m1.bright = 40;

    m1.display();
  }
}

 

 

종합 정리 - 생성자(constructor) 적용
public class Exam0940 {

  static class Monitor {

    int bright = 10; // 밝기 (0% ~ 100%)
    int contrast = 10; // 명암 (0% ~ 100%)
    int widthRes = 480; // 해상도 너비
    int heightRes = 320; // 해상도 높이

    // 호출될 생성자는 new 명령에서 지정한다.
    //   예) new 클래스명(호출할 생성자의 전달할 파라미터 값)
    // => 다음과 같이 파라미터의 값을 주지 않으면 파라미터 값을 안 받는 생성자가 호출된다.
    //   예) Monitor()
    // => 파라미터를 받지 않는 생성자(파라미터 없는 생성자)를 
    //    기본 생성자(default constructor)라 부른다.
    // => new 명령을 실행하여 인스턴스를 생성할 때는 반드시 호출될 생성자를 지정해야 한다.
    //    예) new 클래스명();
    // => 다음과 같이 생성자를 지정하지 않으면 컴파일 오류가 발생한다.
    //    예) new 클래스명;
    Monitor() {  
      System.out.println("생성자 실행!");
      this.bright = 50;
      this.contrast = 50;
      this.widthRes = 1920;
      this.heightRes = 1080;
      // 인스턴스 초기화 블럭이라는 문법이 있는데 왜 생성자가 필요한가?
      // - 생성자는 외부에서 초기화시킬 값을 받을 수 있다.
      // - 다만 현재의 생성자는 기본 생성자로서 외부로부터 값을 받지 않기 때문에 
      //   초기화 블록이랑 다를 바가 없다.
    }

    void display() {
      System.out.println("----------------------------------");
      System.out.printf("밝기(%d)\n", this.bright);
      System.out.printf("명암(%d)\n", this.contrast);
      System.out.printf("해상도(%d x %d)\n", this.widthRes, this.heightRes);
      System.out.println("----------------------------------");
    }
  }

  public static void main(String[] args) {
    // 모니터 인스턴스 생성
    // 1) Monitor 설계도에 따라 인스턴스 필드를 Heap 에 생성한다.
    // 2) 필드의 초기화 문장을 실행한다.
    //    예) int bright = 10;
    // 3) 인스턴스 초기화 블록을 실행한다.
    //    예) 
    //    {
    //      System.out.println("인스턴스 초기화 블록 실행!");
    //      bright = 20;
    //      ...
    //     }
    // 4) 파라미터를 안 받는 생성자(default constructor)를 호출한다.
    //    예) Monitor() {...}
    //
    Monitor m1 = new Monitor();

    // 주의!
    // => 다음과 같이 호출할 생성자를 지정하지 않는다면 컴파일 오류가 발생한다.
    // => 반드시 존재하는 생성자를 지정해야 한다.
    //Monitor m2 = new Monitor; // 컴파일 오류!

    // 인스턴스 필드의 값이 생성자를 통해 유효한 기본 값들로 
    // 미리 초기화 되었기 때문에 바로 사용할 수 있다.
    m1.display(); 

    // 물론 특정 속성의 값을 바꾼 후에 사용해도 된다.
    m1.bright = 40;

    m1.display();
  }
}

 

 

종합 정리 - 파라미터가 있는 생성자(constructor) 적용
public class Exam0950 {

  static class Monitor {

    int bright; // 밝기 (0% ~ 100%)
    int contrast = 50; // 명암 (0% ~ 100%)
    int widthRes; // 해상도 너비
    int heightRes = 1080; // 해상도 높이

    // 만약 생성자가 한 개라도 있으면 컴파일러는 기본 생성자를 추가하지 않는다.
    //
    Monitor(int bright, int contrast) {
      this.bright = bright;
      this.contrast = contrast;
    }

    void display() {
      System.out.println("----------------------------------");
      System.out.printf("밝기(%d)\n", this.bright);
      System.out.printf("명암(%d)\n", this.contrast);
      System.out.printf("해상도(%d x %d)\n", this.widthRes, this.heightRes);
      System.out.println("----------------------------------");
    }
  }

  public static void main(String[] args) {
    // 인스턴스 생성

    // 기본 생성자가 없는 경우 다음과 같이 기본 생성자를 지정할 수 없다.
    //    new Monitor(); // 컴파일 오류!

    // 생성자가 원하는 타입의 원하는 개수의 값을 줘야 한다.
    //    new Monitor(3f, 2); // 컴파일 오류!
    //    new Monitor(2); // 컴파일 오류!

    // 존재하는 생성자를 지정해야 하고, 그 생성자의 파라미터에 맞춰 값을 넘겨야 한다.
    // => Monitor 설계도에 따라 인스턴스를 생성한 후, 
    //    int 값 두 개를 받는 생성자를 호출하라는 의미다.
    Monitor m = new Monitor(50, 50); // OK!
    m.display();

  }
}

 

 

종합 정리 - 생성자(constructor)를 여러 개 정의하기
public class Exam0960 {

  static class Monitor {

    int bright; // 밝기 (0% ~ 100%)
    int contrast = 50; // 명암 (0% ~ 100%)
    int widthRes; // 해상도 너비
    int heightRes = 1080; // 해상도 높이

    // 여러 개의 생성자를 정의할 수 있다.
    Monitor() {
      this.bright = 50;
      this.contrast = 50;
      this.widthRes = 2560;
      this.heightRes = 1200;
      System.out.println("Monitor()");
    }

    Monitor(int bright, int contrast) {
      this.bright = bright;
      this.contrast = contrast;
      System.out.println("Monitor(int, int)");
    }

    Monitor(int bright, int contrast, int widthRes, int heightRes) {
      this.bright = bright;
      this.contrast = contrast;
      this.widthRes = widthRes;
      this.heightRes = heightRes;
      System.out.println("Monitor(int, int, int, int)");
    }

    void display() {
      System.out.println("----------------------------------");
      System.out.printf("밝기(%d)\n", this.bright);
      System.out.printf("명암(%d)\n", this.contrast);
      System.out.printf("해상도(%d x %d)\n", this.widthRes, this.heightRes);
      System.out.println("----------------------------------");
    }
  }

  public static void main(String[] args) {
    // 인스턴스 생성

    // 생성자를 지정할 때 파라미터의 값에 결정된다.
    Monitor m1 = new Monitor(); // 기본 생성자를 지정한다.
    Monitor m2 = new Monitor(50, 50); // Monitor(int, int) 생성자를 지정한다.
    Monitor m3 = new Monitor(50, 50, 1920, 1080); // Monitor(int, int, int, int) 생성자를 지정한다.

    // 파라미터 타입이나 개수가 일치하지 않는 생성자는 호출할 수 없다.
    //    new Monitor(50); // 컴파일 오류!

    m1.display();
    m2.display();
    m3.display();
  }
}

 

 

종합 정리 - 생성자에서 다른 생성자 호출하기: this()
public class Exam0970 {

  static class Monitor {

    int bright; // 밝기 (0% ~ 100%)
    int contrast = 50; // 명암 (0% ~ 100%)
    int widthRes; // 해상도 너비
    int heightRes = 1080; // 해상도 높이

    Monitor() {
      // 다른 생성자를 호출할 수 있다.
      // 단 첫 문장으로 와야 한다.
      // => 이 경우 변수 초기화 문장이 삽입되지 않는다.
      this(50, 50, 2560, 1200);
      System.out.println("Monitor()");
    }

    Monitor(int bright, int contrast) {
      // 다른 생성자를 호출할 수 있다.
      // 단 첫 문장으로 와야 한다.
      // => 이 경우 변수 초기화 문장이 삽입되지 않는다.
      this(bright, contrast, 2560, 1200);
      System.out.println("Monitor(int, int)");
    }

    Monitor(int bright, int contrast, int widthRes, int heightRes) {
      // 다른 생성자를 호출하는 코드가 없다면 
      // 변수 초기화 문장이 생성자 맨 앞에 삽입된다.
      //      this.contrast = 50;
      //      this.heightRes = 1080;

      this.bright = bright;
      this.contrast = contrast;
      this.widthRes = widthRes;
      this.heightRes = heightRes;
      System.out.println("Monitor(int, int, int, int)");
    }

    void display() {
      //      this(); // 일반 메서드에서는 생성자를 호출할 수 없다.
      System.out.println("----------------------------------");
      System.out.printf("밝기(%d)\n", this.bright);
      System.out.printf("명암(%d)\n", this.contrast);
      System.out.printf("해상도(%d x %d)\n", this.widthRes, this.heightRes);
      System.out.println("----------------------------------");
    }
  }

  public static void main(String[] args) {
    // 인스턴스 생성

    Monitor m1 = new Monitor();
    System.out.println("--------------------------------");

    Monitor m2 = new Monitor(60, 60);
    System.out.println("--------------------------------");

    Monitor m3 = new Monitor(70, 70, 1920, 1080);
    System.out.println("--------------------------------");

    m1.display();
    m2.display();
    m3.display();

    // 주의!
    // 임의로 생성자를 호출할 수 없다.
    //    m1.Monitor(); // 컴파일 오류!
  }
}