2022.07.25 월요일/자바 정리/클래스 사용법

Jimin·2022년 7월 25일
0

비트캠프

목록 보기
8/60
post-thumbnail

클래스 사용법

  • 스태틱 필드와 인스턴스 필드
  • 스태틱 메서드와 인스턴스 메서드, THIS 내장 변수
  • 생성자와 THIS()
  • 스태틱 초기화 블록, 스태틱 변수 초기화 문장
  • 인스턴스 초기화 블록, 인스턴스 변수 초기화 문장

static 필드와 non-static 필드

class A{
	int a;
    static int b;
}
  • int a
    • non-static 필드(변수)
    • instance 필드
    • Heap에 생성
    • Heap은 Garbage Collector가 관리해서, 이용이 끝나면 Heap에 생성된 인스턴스들은 삭제된다.
  • static int b
    • static 필드(변수)
    • class 필드
    • Method Area에 생성
    • Method Area에 생성되면 프로그램이 끝날 때까지 계속 유지된다.

인스턴스(non-static) 필드

class A{
	int v1;
    boolean v2;
    void m1(){---}
}

static public void main(String args[]){
    A a = new A();
    a.m1();
}
  • 메소드가 클래스 A에 존재한다고, 클래스가 로딩됐을 때 바로 실행되지 않는다.
  • a.m1()과 같이 call했을때만 비로소 실행된다.
  • 하지만 클래스 A의 인스턴스 변수 int v1과 boolean v2는 new를 통해 인스턴스가 생성됐을 때 비로소 Heap에 변수가 생성된다.
  • 또한 main 클래스에 선언된 레퍼런스 변수 a는 로컬 변수이므로 JVM 스택에 생성된다.

클래스 필드(변수, static feild)

  • 클래스가 Method Area에 로딩될 때 static 변수 또한 Method Area에 함께 생성된다.

    → static feild는 Heap이 아닌 Method Area에 생성된다!!!

class A{
	static int v1;
    static boolean v2;
}
main() {
	A.v1 = 100;
    A.v2 = true;
}

  • static 변수는 class 명으로 접근한다.

클래스 로딩과 실행

  1. 소스 파일 실행 -> Hello.exe

  2. Hello.class를 찾는다.

  3. Hello.class를 찾았다면,
    Byte code를 검증한다.

  4. Byte code가 검증을 통과했다면,
    Method Area에 클래스 파일을 정리해서 관리하기 쉽게 메모리에 로딩(배치)한다.

  5. 스태틱 필드를 생성한다.

  6. 스태틱 블록을 실행한다.

클래스 로딩과 static 필드 및 인스턴스 필드 생성

class Exam0140{
	class A{
    	static int v1;
        int v2;
    }
    main() {
    	A.v1 = 100;
        A p = new A();
        p.v2 = 200;
        A p2 = new A();
        p2.v2 = 300;
    }
}
  1. Exam0140 class 로딩
  2. main 호출
  3. main에서 로컬 변수 생성(args, p, p2)
  4. A.v1 -> A class 로딩
  5. A 클래스의 클래스 멤버, v1 생성
  6. v1에 100 대입
  7. new를 통해 인스턴스 메모리 생성해서 A의 레퍼런스 p에 주소 값을 넣어준다.
  8. 만들어준 레퍼런스 변수 p를 통해 인스턴스 변수 v2에 접근하여 값 200을 넣어준다.
  9. new를 통해 A의 새로운 인스턴스를 만들어 그 주소값을 로컬 변수 p2에 넣어준다.
  10. p2를 통해 인스턴스 변수 v2에 접근하여 300이라는 값을 대입해준다.
  • Exam0140 class 안에 class A가 컴파일 되지 않고, 따로따로 컴파일 된다

static과 instance를 비유를 통해 알아보자!
학생의 수 → static
각 학생들의 이름, 나이 → instance

변수 선언은 관련된 클래스에 선언하는 것이 좋다.

  • 여러 인스턴스에서 공통으로 사용할 값을 담는 변수라면, static 필드로 선언하자!
  • static 변수의 소속 클래스를 미리 밝히면, 클래스의 이름 없이도 static 변수를 바로 사용할 수 있다!
package com.eomcs.oop.ex03;
// 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: 관리자
}
package com.eomcs.oop.ex03;
// 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;
  }
}

일단 인스턴스로 만들고, 인스턴스 멤버를 사용하지 않으면,
그제서야 스태틱을 붙여 클래스 멤버로 만들자!

  • static 메소드는 this, 내장변수가 없으니까 사용할 수 없는 것이다!
  • instance 메소드는 this, 내장변수가 있으니까 사용할 수 있는 거다!
  • 여기서 this는 인스턴스 주소를 가진다.
  • this가 1순위로 생성되고, 로컬변수가 생성된다.
public class Exam0220 {

  static class A {
    int value;
    
    static void m1() {
    }

    void m2() {
      this.value = 100;
    }

    void m3() {
    }

    void m4(int value) {
      value = 200; 
      this.value = 300;
    }
  }
  public static void main(String[] args) {
    A.m1();
    A obj1 = new A();
    obj1.m2();
    obj1.m1();

    A obj2 = new A();

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

  }
}
  • 위의 코드에서 obj1.m2()를 호출했을 때, m2()안의 this는 obj1의 주소를 가르킨다.
  • 위의 코드에서 obj2.m2()를 호출했을 때, m2()안의 this는 obj2의 주소를 가르킨다.



  • method는 instance로 선언이 되더라도 Method Area에 생성된다.
public class Exam0230 {
  static class Calculator {
    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) {
    Calculator c1 = new Calculator();
    Calculator c2 = new Calculator();
    // 인스턴스 메서드든 클래스 메서드든 모두 Method Area 영역에 올라간다.
    // 그리고 인스턴스를 가지고 그 메서드를 호출하는 것이다.
    // c1이 가리키는 인스턴스를 가지고 Method Area에 있는 plus()를 호출한다.
    c1.plus(123);
    // c2가 가리키는 인스턴스를 가지고 Method Area에 있는 plus()를 호출한다.
    c2.plus(30);
  }
}

  • c1이 가지고 있는 인스턴스 주소를 가지고 Method Area에 있는 plus 메소드 호출
  • c2가 가지고 있는 인스턴스 주소를 가지고 Method Area에 있는 plus 메소드 호출
  • 메서드에 인자로 받는 파라미터도 로컬 변수이다!
    ⇒ JVM Stack에 생성된다.
  • 따라서 메서드 내부에서, 파라미터만 가지고 작업을 하는 경우에도, 클래스 메서드로 정의한다.
    → 외부 변수, 인스턴스 변수를 사용하는 것이 아니기 때문이다.
    ⇒ 즉, this.을 붙일 수 있는 변수가 없으면 인스턴스 변수를 이용하는 것이 아니기 때문에 static으로 변경해준다. (this. 으로 구분하면 좋을 것 같다.)
  • Method는 Method Area에 로딩되지만, Method 내부의 로컬변수는 JVM Stack에 선언되어진다!

인스턴스와 생성자

new Score();
  1. new Score: Score 설계도에 따라 instance 변수들을 Heap에 생성한다.
  2. (): 인스턴스 생성 후, 그 주소를 가지고 기본 생성자를 호출한다.
    즉, ()는 생성자를 호출하는 명령어이다.
  3. (인스턴스 변수 생성 → 생성자 호출)
    → 인스턴스 주소를 레퍼런스 변수에 리턴
Score s = new Score();
  • 생성자는 인스턴스 생성시에만 호출이 가능하다!
  • 생성자는 따로 호출이 불가하다.
  • 생성자 안에 this.compute()와 같이 인스턴스 메소드를 수행하면, main에서 생성자만 호출해도 이미 계산이 끝나있다!
public class Exam0420 {

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

   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 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);
  }
}
  • Method Area에 생성되는 static 변수는 클래스 로딩시 자동 초기화 된다.
  • Heap에 생성되는 instance 변수는 new로 생성될 때 자동 초기화 된다.
  • 하지만, JVM Stack에 생성되는 local변수는 자동으로 초기화되지 않는다.

클래스 호출 & Static 블럭

  • Static 블럭의 목적?
    클래스 멤버(스태틱 변수, 스태틱 메서드)를 사용하기 전유효한 값으로 초기화 시키는 것이다.
  • 클래스 로딩 절차
    1) 클래스를 Method Area에 로딩한다.
    2) 스태틱 변수를 만든다.
    3) 스태틱 블록을 실행한다.

    스태틱 변수부터 생성하고 그 다음에 블록 들어가서 값 설정한다!!

  • 변수 선언이 아무리 static 블록보다 아래에 있어도 변수 선언이 먼저 선언되고 값 초기화, 블록이 그 다음에 실행된다.
  • Static 블럭은 나눠놔도 결국 하나로 합쳐진다!

  • 클래스 로딩은 프로그램 시작 후 단 한번만 된다!

  • 따라서, static 로딩도 단 한번만 된다!
    → Method Area에!!!

  • class에서 밑의 코드가 있다고 가정하고 살펴봐 보자!

static int a = 300;

위의 코드는 컴파일시, 결국 밑에와 같이 변하게 된다.

static int a;
satatic{
	a = 300;
}
  • 나만의 정리: 즉, static은 Method Area에 Method와 별개로 생성되는 상자가 있다고 생각하자.

  • 또한 static은 변수 선언 따로, 그 변수에 값 설정 따로 된다고 생각하자!!

  • 예시

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); // ?
  }
}
  • System.out.println(A.a); 결과 = 36
  • System.out.println(B.b); 결과 = 29
  • 과정
    1. A클래스 로딩
    2. 스태틱 변수 a 생성
    3. 스태틱 변수 a가 7로 초기화
    4. System.out.println("A.static{}"); 출력
    5. B클래스 로딩
    6. 스태틱 변수 b 생성
    7. 스태틱 변수 b가 22로 초기화
    8. System.out.println("B.static{}"); 출력
    9. b에 A.a의 값, 7 더하기 → b가 29로 초기화
    10. a에 b의 값 29가 리턴되어 a에 29 더하기 → a가 36으로 초기화
    11. 메인에서 System.out.println(A.a); a, 36 출력
    12. 메인에서 System.out.println(B.b); b, 29 출력
  • 예시
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);
  }
}



인스턴스 초기화(instance initializer)

여러 생성자에 공통으로 들어갈 코드가 있다면, 인스턴스 초기화 블럭에 담아두자!!

인스턴스 초기화 블록의 용도

  1. 여러 생성자에 공통으로 들어가는 초기화 문장을 작성할 때
  2. 생성자를 만들지 못하는 상황에서 복잡한 로직에 따라 인스턴스 필드를 초기화시켜야 할 때
  • 인스턴스 초기화 블럭?
    class 내부에 아무것도 안붙은 블럭

  • 인스턴스 초기화 블럭 안에 들어 있는 코드는 모든 생성자의 앞부분에 복사된다.

  • 인스턴스 초기화 블럭 코드가 생성자 뒤에 위치하더라도 생성자의 앞부분에 복사된다.

  • 복사된 이후의 인스턴스 블럭은 삭제된다.

  • object 클래스는 클래스 이름이 없는 익명 클래스이기 때문에 생성자를 만들지 못한다. 따라서, 초기화 명령을 작성하기 위해서는 인스턴스 블록을 이용해야 한다.

public class Exam0760 {
  public static void main(String[] args) {
    // 생성자를 만들지 못하는 상황?
    // - "익명 클래스"를 만들 때이다.
    // - 클래스 이름이 없기 때문에 생성자를 만들 수 없다.
    //
    // 다음은 Object 클래스를 상속 받은 익명 클래스를 정의하고 객체를 만드는 명령이다.
    Object obj1 = new Object() {
      // 이 클래스는 이름이 없기 때문에 생성자를 만들 수 없다.
      // 그래서 초기화 명령을 작성하려면 인스턴스 블록을 이용해야 한다.
      {
        System.out.println("인스턴스 블록...");
      }
    };
  }
}
  • 인스턴스 변수 또한 인스턴스 초기화 블럭 밖에서 인스턴스 변수를 초기화 명령어를 써놓더라도, static 블럭 처럼,
    인스턴스 변수의 초기화는 인스턴스 초기화 블럭 안에 들어와서, 생성자 맨 앞에 복사되어서, 생성자 맨 앞에서 이루어지게 된다.
  • 순서도
    1. 인스턴스 변수 선언
    2. 인스턴스 초기화 블럭에서 선언됐던 변수 초기화
    3. 인스턴스 초기화 블럭 안의 코드가 생성자 맨 앞으로 복사되고 사라짐
  • 변수는 괄호 상관 없이, 선언된 순서로 초기화 된다.
    단지 선언만 먼저 되는 것이다.
public class Exam0820 {

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

    // 인스턴스 필드 초기화 문장은
    // 생성자의 앞 부분에 삽입된다.
    //
    public A() {
      // 생성자가 있으면,
      // - 존재하는 생성자의 앞 부분에 삽입된다.
      // - 즉 인스턴스 필드를 초기화시키는 문장이 다음과 같이 삽입된다.
      //
      // 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);
  }
}
profile
https://github.com/Dingadung

0개의 댓글