클래스 변수와 클래스 메소드

gustjtmd·2022년 1월 25일
0

Java

목록 보기
4/40

static 선언을 붙여서 선언하는 클래스 변수

인스턴스 변수는 인스턴스가 생성되었을 때 생성된 인스턴스 안에 존재하는 변수이다. 그러나 클래스 변수는 인스턴스의 생성과 상관없이 존재하는 변수이다

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

클래스 내에 선언된 변수 앞에 static 선언을 붙이면 이는 인스턴스 변수가 아닌 '클래스 변수'
가 된다.

class InstCnt{
    static int instNum = 0; //클래스 변수(static 변수)

    InstCnt(){  //생성자
        instNum++;
        System.out.println("인스턴스 생성 : "+instNum);
    }
}
public class ClassVar {
    public static void main(String[] args) {
        InstCnt cnt1 = new InstCnt();
        InstCnt cnt2 = new InstCnt();
        InstCnt cnn3 = new InstCnt();
    }
}


인스턴스 생성 : 1
인스턴스 생성 : 2
인스턴스 생성 : 3

-------------------------------------------------------------------------

클래스 InstCount의 생성자에서 static으로 선언된 변수 instNUm의 값을 하나 증가시킨
다음에 그 결과를 출력하고 있다. 출력 결과 인스턴스 생성 시마다 1씩 증가함을 알 수 있다.

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

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

그리고 클래스 변수도 '접근 수준 지시자'의 규칙을 그대로 적용받기 때문에 public으로 선언되면
어디서든 접근이 가능하다.

클래스 변수의 접근 방법

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

1. 클래스 내부 접근 	'변수의 이름을 통해 직접 접근'
2. 클래스 외부 접근		'클래스 또는 인스턴스의 이름을 통해 접근'

이와 관련된 코드

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++;  //외부에서 인스턴스의 이름을 통한 접근
        AccessWay.num++;    //외부에서 클래스의 이름을 통한 접근
        System.out.println("num = " +AccessWay.num);
    }
}


num = 3


-------------------------------------------------------------------------

위 코드에서 보이듯이 앞서 보였던 클래스 내부에서의 접근 방법 이외에 다음과 같이 클래스의 이름
또는 인스턴스의 이름을 통한 접근도 가능하다.

	way.num++;  //외부에서 인스턴스의 이름을 통한 접근
        AccessWay.num++;    //외부에서 클래스의 이름을 통한 접근
        
인스턴스의 이름을 통한 접근 방법을 보면서 클래스 변수를 인스턴스 내부에 위치한 것으로 오해하면
안된다 그리고 클래스 변수 num은 default로 선언되었다.
따라서 클래스 내부는 물론 클래스 외부이더라도 동일 패키지로 묶여 있으면 접근이 가능하다.

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

클래스 변수는 인스턴스의 생성과 상관이 없다 그렇다면 클래스 변수는 언제 메모리 공간에 할당되고
초기화될까? 다음 코드로 확인해보자

class InstCnt{
    static int instNum = 100;

    InstCnt(){
        instNum++;
        System.out.println("인스턴스 생성 : "+instNum);
    }
}

public class OnlyClassNoInstance {
    public static void main(String[] args) {
        InstCnt.instNum -= 15; //인스턴스 생성 없이 instNum에 접근
        System.out.println(InstCnt.instNum);
    }
}

85

------------------------------------------------------------------------

위 코드를 통해서 '클래스 변수는 인스턴스 생성 이전에 메모리 공간에 존재한다' 라는 사실을 
알 수 있다. 
클래스 변수는 해당 클래스 정보가 가상머신에 의해 읽히는 순간 메모리 공간에 할당되고 초기화된다
그리고 인스턴스의 생성과 무관하게 이뤄진다는 점이다. 
따라서 다음과 같이 생성자를 통한 클래스 변수의 초기화를 진행하지 않도록 주의해야한다.

class InstCnt{
	static int instNum = 100;	//클래스 변수의 정상적인 초기화 방법
    	InstCnt(){
        instNum = 0;	//클래스 변수의 초기화가 아니다.
     }
}


위의 클래스 변수 instNum은 100으로 초기화된다. 클래스 정보가 가상머신에 의해 읽히는 순간
100으로 초기화가 되는데 생성자에서 변수 InstNum0으로 다시 초기화한다
따라서 인스턴스가 생성될 때마다 instNum은 매번 그 값이 0으로 바뀌게된다.

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

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

앞서 코드에서는 '생성된 인스턴스의 수를 관리하는 상황'을 보였는데 이 역시 인스턴스 간 데이터
공유의 한 사례에 해당한다. 

클래스 내부와 외부에서 참조해야 할 정보를 클래스 변수에 담은 코드

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

    Circle(double rad){
        radius = rad;
    }
    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 c = new Circle(1.2);
        c.showPerimeter();
        c.showArea();
    }
}


둘레 : 7.536
넓이 : 4.5216

-----------------------------------------------------------------------

위 코드에서 PI가 상수로 선언이 되었다. PI가 지닌 값은 '원주율'로 결고 변하지 않는 값이기
때문이다. 그런데 인스턴스 변수가 아닌 '클래스 변수'로 선언되었다. 
모든 Circle 인스턴스가 참조해야 하는 값이지만 인스턴스가 각각 지녀야 하는 값은 아니기 떄문

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

게다가 이 값은 외부에서 참조한다고 해서 문제가 되는 값은 아니다.
그래서 private으로 선언하지 않았다.

static 선언을 붙여서 선언하는 클래스 메소드

클래스 내에 정의된 메소드에 static 선언을 하면 '클래스 메소드'가 된다 그리고 클래스 메소드는 그 성격이 클래스 변수와 유사핟. 접근 방법도 동일하며 인스턴스 생성 이전부터 호출이 가능한 그리고 어느 인스턴스에도 속하지 않는 메소드라는 점도 클래스 변수와 동일하다

클래스 메소드(static 메소드의)wjddmldhk ghcnf

클래스 변수의 특성 두 가지는 다음과 같았다
1) 인스턴스 생성 이전부터 접근이 가능하다.
2) 어느 인스턴스에도 속하지 않는다

이 두가지는 클래스 메소드도 동일하게 갖는 특성이다 다음 코드를 통해서 확인해보자
더불어 클래스 변수의 접근 방법과 동일한 '클래스 메소드의 호출 방법'도 확인해보자.

class NumberPrinter{
    private int myNum = 0;

    static void showInt(int n){ //클래스 메소드 (static 메소드)
        System.out.println(n);
    }
    static void showDouble(double n){   //클래스 메소드
        System.out.println(n);
    }
    void setMyNumber(int n){    //인스턴스 메소드
        myNum = n;
    }
    void showMyNumber(){    //인스턴스 메소드
        showInt(myNum); //클래스 내부에서 클래스 메소드 호출
    }
}
public class ClassMethod {
    public static void main(String[] args) {
        NumberPrinter.showInt(20);  //클래스 이름을 통한 클래스 메소드 호출

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


20
3.15
75

-----------------------------------------------------------------------

위 코드에서 다음과 같이 클래스의 이름을 통해서 클래스 메소드를 호출하였다.

NumberPrinter.showInt(20);

위 코드를 통해 클래스 메소드는 어느 인스턴스에도 속하지 않는다는 사실을 알 수 있다.
인스턴스 생성 이전에 호출이 되었기 때문이다. 코드의 주석에서 설명하고 있듯이 클래스의 내부와
외부에서 클래스 메소드를 호출하는 방법은 클래스 변수에 접근하는 방법과 차이가 없다.

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

class SimpleCalculator{
    static final double PI = 3.14;

    double add(double n1, double n2){
        return n1 + n2;
    }
    double min (double n1, double n2){
        return n1 - n2;
    }
    double caclCircleArea(double r){
        return PI * r * r;
    }
    double calCirclePeri(double r){
        return PI * (r * 2);
    }
}
public class UseCalculator {
    public static void main(String[] args) {
        SimpleCalculator sc = new SimpleCalculator();
        System.out.println("3 + 4 = " + sc.add(3,4));
        System.out.println("반지름 2.2, 원의 넓이 : "+sc.caclCircleArea(2.2));
        System.out.println();
        System.out.println("15 - 7 = "+sc.min(15,7));
        System.out.println("반지름 5.0, 원의 둘레 : "+sc.calCirclePeri(5.0));
    }
}

3 + 4 = 7.0
반지름 2.2, 원의 넓이 : 15.197600000000003

15 - 7 = 8.0
반지름 5.0, 원의 둘레 : 31.400000000000002

-----------------------------------------------------------------------

위 코드에서 클래스 SimpleCalculator에 정의된 메소드가 갖는 특징 두 가지는 다음과 같다

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

따라서 SimpleCalculator에 정의된 메소드들은 인스턴스에 속할 이유가 없다
------------------------------------------------------------------------------

수정된 코드

class SimpleCalculator{
    static final double PI = 3.14;

    static double add(double n1, double n2){
        return n1 + n2;
    }
    static double min (double n1, double n2){
        return n1 - n2;
    }
    static double caclCircleArea(double r){
        return PI * r * r;
    }
    static double calCirclePeri(double r){
        return PI * (r * 2);
    }
}
public class UseCalculator {
    public static void main(String[] args) {
        System.out.println("3 + 4 = " + SimpleCalculator.add(3,4));
        System.out.println("반지름 2.2, 원의 넓이 : "+SimpleCalculator.caclCircleArea(2.2));
        System.out.println();
        System.out.println("15 - 7 = "+SimpleCalculator.min(15,7));
        System.out.println("반지름 5.0, 원의 둘레 : "+SimpleCalculator.calCirclePeri(5.0));
    }
}

3 + 4 = 7.0
반지름 2.2, 원의 넓이 : 15.197600000000003

15 - 7 = 8.0
반지름 5.0, 원의 둘레 : 31.400000000000002


위 코드에서 확인할수 있듯이 메소드에 static 선언을 추가함으로 인해 불필요한 인스턴스의
생성 과정을 생략할수 있게 되었다.

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

결론부터 말하면 당연히 불가능하다 생각해보자

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

class AAA{
	int num = 0;
    	static void addNum(int n){
        	num += n;	//이 문장이 유효한가?
        }
}

당연히 불가능하다 인스턴스 변수는 인스턴스에 속한다. 
더불어 인스턴스가 생성이 되어야 메모리 공간에 존재하게 된다.

반면 클래스 메소드는 인스턴스 생성 이전부터 호출이 가능하다 따라서서 불가능하다
"클래스 메소드는 인스턴스에 속하지 않으므로 인스턴스 변수에 접근이 불가능하다"
"같은 이유로 클래스 메소드는 인스턴스 메소드의 호출도 불가능하다"

클래스 메소드는 같은 클래스의 정의되어 있는 다른 클래스 메소드나 성격이 동일한 클래스 변수에는
접근이 가능하다

class AAA{
	static int num = 0;
    	static void showNum(){
        	System.out.println(num);	//클래스 변수 접근 가능
        }
        static void addNum(int n){
        	num += n;	//클래스 변수 접근 가능
            	showNum();	//클래스 메소드 호출 가능.
        }
}

System.out.println(),public static void main()

System.out.println()에서 out과 println 은?

System은 자바에서 제공하는 클래스로 java.lang 패키지에 묶여있다
따라서 원칙적으로 java.lang.System.out.println() 과 같이 호출해야 하지만
컴파일러가 다음 문장을 자동으로 삽입해주기 때문에 패키지의 이름 부분을 생략할수 있다.

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

그리고 out은 System.out 으로 접근을 하니 이는 분명 static으로 선언된 클래스 변수가
분명하다. 클래스의 이름을 통해 접근하니 말이다. 실제로 out은 System 클래스 내에
다음과 같이 선언된 클래스 변수이다

public final class System extends Object{
	public static final PrintStream out;	//참조 변수 out
}

마지막으로 println은 PrintStream 클래스의 인스턴스 메소드이다. 따라서 다음 문장을 보면서
다음과 같이 이해할수 있어야 한다.

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

main 메소드가 public이고 static인 이유

main 메소드는 반드시 다음의 모양새를 갖춰야 한다

public static void main(String[] args){
	...
}

이렇듯 main 메소드는 public으로 static으로 선언해야 한다
이는 일종의 약속이며 이러한 약속에 근거하여 다음과 같이 실행하면

C:\JavaStudy>Java MyMainClass

MyMainClasspublic으로 그리고 static으로 선언된 main 메소드를 찾아 실행을 하게 된다.

main 메소드의 호출이 이뤄지는 영역은 클래스 외부이다 따라서 public으로 선언하는 것이 타당

그리고 main 메소드는 인스턴스가 생성되기 전에 호출된다 따라서 static 선언하는것이
옳을을 알 수 있다.

또 다른 용도의 static 선언

static 선언은 클래스 변수와 클래스 메소드의 선언 이외에 다른 용도로도 사용이 된다. 사용 빈도가 높지는 않으나 상황에 따라 유용하게 사용할 수 있다.

static 초기화 블록

class dateOf{
    static String date;	//프로그램의 실행 날짜를 저장하기 위한 변수

    static{
        LocalDate nDate = LocalDate.now();
        date = nDate.toString();
    }
}
public class DateOfExcution {
    public static void main(String[] args) {
        System.out.println(dateOf.date);
    }
}

2022-01-25


-----------------------------------------------------------------------------

class dateOf{
    static String date;	//프로그램의 실행 날짜를 저장하기 위한 변수
}

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

이 클래스에는 프로그램의 실행 날짜를 저장할 목적으로 변수가 하나 선언되었다.
그리고 이 변수는 변경의 대상이 아니고 참조만을 목적으로 하므로 '클래스 변수'로 선언하였다.

위에서 클래스 변수 date를 선언과 동시에 오늘 날짜 정보를 담고 있는 문자열로 초기화하고 싶다
오늘 날짜를 얻어오는 코드는 다음과 같다. 다음 두 문장이 실행되어야 변수 date에는 오늘 
날짜 정보가 담긴다.

LocalDate nDate = LocalDate.now();
          date = nDate.toString();
        

변수 date가 인스턴스 변수라면 위의 두 문장을 생성자에 넣으면 된다 그러나 이는 클래스 변수여서
생성자는 적절치 않다 이러한 상황을 고려하여 자바는 'static 초기화 블록' 이라는 것을 제공한다

그리고 위의 두 문장을 static 초기화 블록으로 감싼 결과는 다음과 같다

static{
        LocalDate nDate = LocalDate.now();
        date = nDate.toString();
    }
    
'static 초기화 블록'은 클래스 변수와 마찬가리고 가상머신이 클래스의 정보를 읽어 들일때
(가사엄신이 클래스를 로딩할때) 실행이 된다.
따라서 다음과 같이 static 초기화 블록을 사용하면 블래스 변수를 선언과 동시에 초기화 가능하다
출처 : 윤성우의 열혈 Java 프로그래밍
profile
반갑습니다

0개의 댓글