[윤성우의 열혈 Java 프로그래밍] Chapter 10 - 클래스 변수와 클래스 메소드

Update:     Updated:

카테고리:

태그:

10-1. static 선언을 붙여서 하는 클래스 변수

  • 인스턴스 변수: 인스턴스가 생성되었을 때, 생성된 인스턴스 안에 존재하는 변수
  • 클래스 변수: 인스턴스의 생성과 상관없이 존재하는 변수

- 선언된 클래스의 모든 인스턴스가 공유하는 클래스 변수(static 변수)

static으로 선언된 변수는 변수가 선언된 클래스의 모든 인스턴스가 공유하는 변수이다.

클래스 변수는 ‘어떠한 인스턴스에도 속하지 않는 상태로 메모리 공간에 딱 하나만 존재하는 변수’이다.
다만 이 변수가 선언된 클래스의 인스턴스들은 이 변수에 바로 접근할 수 있는 권한이 있을 뿐이다.

- 클래스 변수의 접근 방법

클래스 변수에 접근하는 방법은 접근 영역을 기준으로 크게 두 가지로 나뉜다.

  • 클래스 내부 접근: 변수의 이름을 통해 직접 접근
  • 클래스 외부 접근: 클래스 또는 인스턴스의 이름을 통해 접근
class AccessWay {
    static int num = 0;
    AccessWay() {
        incrCnt();
    }
    void incrCnt() {    // 클래스 내부에서 이름을 통한 접근
        num++;
    }
}

public class ClassVarAccess {
    public static void main(String[] args) {
        AccessWay way = new AccessWay();
        way.num++;  // 외부에서 '인스턴스'의 이름을 통한 접근(num 변수가 인스턴스 변수인지 클래스 변수인지 분간이 안 됨!)
        AccessWay.num++;    // 외부에서 '클래스'의 이름을 통한 접근(따라서 이 접근법이 추천된다!)
        System.out.println("num + " + AccessWay.num);
    }
}

- 클래스 변수의 초기화 시점과 초기화 방법

클래스 변수는 인스턴스 생성 이전에 메모리 공간에 존재한다.

클래스 변수는 해당 클래스 정보가 가상머신에 의해 읽히는 순간 메모리 공간에 할당되고 초기화된다.
따라서 다음과 같이 생성자를 통한 클래스 변수의 초기화를 진행하지 않도록 주의해야한다.(인스턴스 생성시마다 값이 리셋되기 때문)

class InstCnt{
    static int instNum = 100;   // 정상적인 초기화 방법
    
    InstCnt(){
        instNum = 0;    // 인스턴스를 생성할 때마다 값이 초기화되므로 주의
    }
}

(참고)
클래스 로딩(Class Loading)이란?
가상머신이 특정 클래스 정보를 읽는 행위를 말한다.
특정 클래스의 인스턴스 생성을 위해서는 해당 클래스가 반드시 가상머신에 의해 로딩되어야 한다.
즉, 인스턴스 생성보다 클래스 로딩이 먼저이다.

- 클래스 변수를 언제 유용하게 활용할 것인가?

인스턴스 간에 데이터 공유가 필요한 상황에서 클래스 변수를 선언한다.

예시를 통해서 한 번 알아보자!

class Circle {
    static final double PI = 3.1415;    // 변하지 않는, 참조가 목적인 값
    private double radius;

    Circle(double radius) {
        this.radius = radius;
    }
    void showPerimeter() {
        double peri = (radius * 2) * PI;
        System.out.println("둘레: " + peri);
    }
    void showArea() {
        double area = (radius * radius) * PI;
        System.out.println("넓이: " + area);
    }
}

public class CircleConstPI {
    public static void main(String[] args) {
        Circle circle = new Circle(1.2);
        circle.showArea(); circle.showPerimeter();
    }
}

포인트는 이거다. PI(원주율)처럼

  1. 모든 Circle 인스턴스가 참조해야 하는 값이지만,
  2. 인스턴스가 각각 지녀야 하는 값은 아니라면 클래스 변수로 선언되는 것이 적당하다.

참조를 목적으로만 존재하는 값은 final 선언이 된 클래스 변수에 담는다.

10-2. static 선언을 붙여서 하는 클래스 메소드

클래스 메소드는 그 성격이 클래스 변수와 유사하다.
접근 방법, 인스턴스 생성 이전부터 호출이 가능한 점, 어느 인스턴스에도 속하지 않는다는 점들 모두 클래스 변수와 동일하다.

- 클래스 메소드의(static 메소드의) 정의와 호출

클래스 변수와 동일한 특성을 가진다.

  • 인스턴스 생성 이전부터 접근이 가능하다.
  • 어느 인스턴스에도 속하지 않는다.
class NumberPrinter {
    static void showInt(int n){ // 클래스 메소드
        System.out.println(n);
    }
    static void showDouble(double n){   // 클래스 메소드
        System.out.pritnln(n);
    }
}

class ClassMethod {
    public static void main(String[] args){
        NumberPrinter.showInt(20);  // 클래스 이름을 통한 클래스 메소드 호출

        NumberPrinter np = new NumberPrinter(); 
        np.showDouble(3.15);    // 인스턴스 이름을 통한 클래스 메소드 호출
    }
}

- 클래스 메소드로 정의하는 것이 더 나은 경우

다음의 예시를 보자!

class SimpleCalculator {
    static final double PI = 3.1415;

    double add(double n1, double n2) {
        return n1 + n2;
    }
    double min(double n1, double n2) {
        return n1 - n2;
    }
    double calCircleArea(double r) {
        return PI * r * r;
    }
    double calCirclePeri(double r) {
        return PI * (r * 2);
    }
}

SimpleCalculator에 정의된 메소드가 갖는 특징 두 가지는 다음과 같다.

  • 모두 외부에 기능을 제공하기 위한 메소드들이다.
  • 모두 인스턴스 변수의 값을 참조하거나 수정하지 않는다.

따라서, 굳이 저 메소드들은 인스턴스에 속할 이유가 없다. 그럼 적절하게 클래스를 수정해보자!

class SimpleBetterCalculator {
    static final double PI = 3.1415;

    static double add(double n1, double n2) {
        return n1 + n2;
    }
    static double min(double n1, double n2) {
        return n1 - n2;
    }
    static double calCircleArea(double r) {
        return PI * r * r;
    }
    static double calCirclePeri(double r) {
        return PI * (r * 2);
    }
}

메소드에 static 선언을 추가함으로 인해 불필요한 인스턴스의 생성 과정을 생략할 수 있게 되었다.
실제로 ‘클래스 메소드’로 구성된, 인스턴스의 생성을 목적으로 설계되지 않은 클래스들도 존재한다.

- 클래스 메소드에서 인스턴스 변수에 접근이 가능할까?

클래스 메소드에서 같은 클래스에 선언된 인스턴스 변수에 접근이 가능한가?

image
클래스 메소드는 인스턴스에 속하지 않으므로 인스턴스 변수에 접근이 불가능하다.
같은 이유로 클래스 메소드는 인스턴스 메소드의 호출도 불가능하다.

10-3. System.out.println() 그리고 public static void main()

- System.out.println()dptj out과 println의 정체는?

public final class System extends Object {
    public static final PrintStream out;
    ...
}

System에 위치한 클래스 변수 out이 참조하는 인스턴스의 println 메소드를 호출하는 문장

- main 메소드는 어디에 위치시켜야하는가?

일반적으로는 main 메소드를 담을 목적으로 별도의 클래스를 정의한다.
하지만 main 메소드는 static 메소드이기 때문에, 즉 특정 인스턴스의 멤버로 존재하는 메소드가 아니기 때문에 위치가 상관이 없다.

10-4. 또 다른 용도의 static 선언

- static 초기화 블록(Static Initialization Block)

import java.time.LocalDate;

public class DateOfExecution {
    static String date; // 클래스 변수 선언

    static {
        LocalDate nDate = LocalDate.now();
        date = nDate.toString();
    }

    public static void main(String[] args) {
        System.out.println(date);
    }
}

date는 클래스 변수이므로 인스턴스 생성 시가 아닌 클래스가 로드될 때 초기화되어야 한다.
따라서, 생성자에 넣는 것이 아닌 static 초기화 블록을 사용해야한다.

- static import 선언

java.lang.Math에는 여러 클래스 변수와 클래스 메소드들이 있다.

import java.lang.*; // 컴파일러가 자동으로 삽입하는 import 선언

System.out.println(Math.PI);    // 따라서 다음과 같은 접근이 허용된다.

하지만, 그냥 클래스 변수의 이름만으로 접근하고 싶은 경우가 있다.
이런 경우 static import를 사용하면 된다.

import static java.lang.Math.*;

public class StaticImport {
    public static void main(String[] args) {
        System.out.println(E);
        System.out.println(PI);

        System.out.println(abs(-55));
        System.out.println(max(77, 88));
        System.out.println(min(33, 55));
    }
}

적절히, 최소한으로 사용하면 도음이 되지만 무분별하게 사용한다면 해당 메소드 또는 변수가 어디에 저장되고 선언된 것인지 구분이 힘들어져 오히려 방해가 될 수 있다.

Java lang 카테고리 내 다른 글 보러가기

댓글 남기기