티스토리 뷰

수업 노트/java (이론)

java static 쉬운 설명

오리지날초이 2021. 8. 31. 11:41

자바를 공부하면서 static 선언에 대한 개념은 꼭 제대로 알고 가야합니다.

static 의 역할과 기능을 모른채로 자바에서 추구하는 객체지향을 온전히 활용할 수 없습니다.

 

static 은 변수나 메소드에 주로 사용하고

인스턴스의 생성과 상관없이 메모리에 해당 영역을 초기화 시키고

해당 영역을 공유하는 기능을 합니다.

 

다음 코드를 실행하면 어떤 결과가 나올까요?

class Main {

	public static void main(String [] args){

			Tmp t1 = new Tmp();
			Tmp t2 = new Tmp();
			Tmp t3 = new Tmp();		

			t1.incNum();
			t2.incNum();
			t3.incNum();
	}
}


class Tmp {

	static int num = 0;

	public Tmp() {
		num++;
		System.out.println("인스턴스 생성 : " + num);
	}

	public void incNum(){
		num++;
		System.out.println("메소드 호출 : " + num);
	}
}
더보기
인스턴스 생성 : 1
인스턴스 생성 : 2
인스턴스 생성 : 3
메소드 호출 : 4
메소드 호출 : 5
메소드 호출 : 6

 

다음 그림을 보시면 위의 결과가 나오는 원리를 쉽게 이해할 수 있습니다.

Main 클래스의 동작 순서를 살펴보면

 

0. main 함수가 가장 먼저 메모리에 반영되고 필요한 영역을 확보합니다

   이것은 main 함수 역시 static 함수이기 때문이라 그렇습니다.

 

1. Tmp 클래스 내부에는 static int num 이 존재합니다.

   Tmp 클래스변수 num 은 모든 Tmp 클래스에서 공유해야하는 변수입니다 (static 키워드의 역할입니다.)

   Tmp 객체 생성 전에 static 을 위한 별도 공간(heap)에 Tmp.num 를 위한 공간을 확보합니다.

 

2. new Tmp(); 로 클래스 원형 Tmp 의 구조에 맞게 객체를 생성합니다.

   이를 객체를 인스턴스화 시킨다고 표현합니다.

 

3. 이름 없는 Tmp 클래스 객체가 생성되면서 생성자 Tmp() 가 작동합니다.

 

4. Tmp.Tmp() 가 작동하면서  num++ 을 하네요. 

   ++ 되는 num 은 본인 객체에서 관리하는 변수가 아니라 모든 Tmp 객체에서 공유하는 static num 입니다.

 

5. ++ 된 num 값을  화면에 출력합니다

 

6. 방금 생성한 이름없는 Tmp 인스턴스를 다루기 위해 타입에 맞춰 이름을 부여해줍니다.

   Tmp t1 = 으로 new Tmp(); 를 연결했습니다.

 

7. 3번 ~ 6번 과정을 t2, t3 도 반복해서 수행합니다.

   2번 과정은 이미 최초 인스턴스 생성때 실행되었기 때문에 반복되지 않습니다.

 

8. t1.incNum(); 을 실행합니다. 여전히 공유변수 static num 이 증가되는 것을 확인할 수 있습니다.

 

9. t2, t3 도 같은 방식으로 확인합니다.

   

* new Tmp(); 하는 순간 Tmp 객체는 생성된 것입니다. 다만 이름이 없으면 우리가 제어할 수가 없는거죠

6번 항목이 이해가 잘 되지 않는다면 Main.java 를 아래와 같이 바꾸고 실행해보세요 

class Main {

	public static void main(String [] args){

			new Tmp();
			new Tmp();
			new Tmp();		

			// t1.incNum();
			// t2.incNum();
			// t3.incNum();
	}
}
더보기
인스턴스 생성 : 1
인스턴스 생성 : 2
인스턴스 생성 : 3

 

1번과 2번의 순서가 의아할 수도 있습니다. 코드를 바꾸고 실행해보세요.

객체 생성 전에 static 영역을 미리 확보하는게 이해가 될 겁니다.

class Main {

	public static void main(String [] args){

			// new Tmp();
			// new Tmp();
			// new Tmp();		

			System.out.println("Tmp.num : " + Tmp.num);

			// t1.incNum();
			// t2.incNum();
			// t3.incNum();
	}
}

class Tmp {

	static int num = 777;

	public Tmp() {
		num++;
		System.out.println("인스턴스 생성 : " + num);
	}

	public void incNum(){
		num++;
		System.out.println("메소드 호출 : " + num);
	}
}
더보기
Tmp.num : 777

 

위의 예제를 통해 static 변수가 공유변수로서 작동함을 확인했습니다.

static 키워드는 메소드에도 붙여줄 수 있습니다.

위와 같은 로직으로 공유 메소드로 작동합니다.

class Main {

	public static void main(String [] args){

			// new Tmp();
			// new Tmp();
			// new Tmp();		

			// System.out.println("Tmp.num : " + Tmp.num);

			Tmp.incNum();
			Tmp.incNum();
			Tmp.incNum();
	}
}

class Tmp {

	static int num = 777;

	public Tmp() {
		num++;
		System.out.println("인스턴스 생성 : " + num);
	}

	static public void incNum(){
		num++;
		System.out.println("메소드 호출 : " + num);
	}
}
더보기
메소드 호출 : 778
메소드 호출 : 779
메소드 호출 : 780

 

객체 선언없이 Tmp.incNum(); 으로 사용한 부분을

객체 선언과 t1.incNum(); 식으로 변경해주면 결과는 약간 달라집니다.

 

본 예제에서는 생성자가 끼어들어서 그렇습니다.

static 메소드를 사용하는 것은 static 변수와 다를게 없습니다.

class Main {

	public static void main(String [] args){

			Tmp t1 = new Tmp();
			Tmp t2 = new Tmp();
			Tmp t3 = new Tmp();		

			// System.out.println("Tmp.num : " + Tmp.num);

			t1.incNum();
			t2.incNum();
			t3.incNum();
	}
}

class Tmp {

	static int num = 777;

	public Tmp() {
		num++;
		System.out.println("인스턴스 생성 : " + num);
	}

	static public void incNum(){
		num++;
		System.out.println("메소드 호출 : " + num);
	}
}
더보기

 

인스턴스 생성 : 778
인스턴스 생성 : 779
인스턴스 생성 : 780
메소드 호출 : 781
메소드 호출 : 782
메소드 호출 : 783

 

 

static 변수는 실제 값을 공유하기 위해서 사용하는데

static 메소드는 주로 일회성 기능을 수행할 때 적합합니다.

class Main {

	public static void main(String [] args){

			System.out.println(Math.random());
			System.out.println(Math.pow(2,10));
	}
}
0.09053141414131338
1024.0

 

물론 내가 사용하고자 하는 메소드가 static 으로 선언되어 있어야 합니다.

수학에 관련된 함수의 경우 값을 전달해서 결과만 받으면 되기 때문에

번거롭게 Math m = new Math(); m.pow(2,10) 식으로 사용할 필요가 없이

애초에 static 으로 선언하고 사용하는게 훨씬 낫습니다.

 

각 객체별로 같은 기능을 하는 메소드를 가지고 있는게 아니라 한 곳에서 기능을 공유하는 개념이죠.

 

다만 이런 static 메소드 활용에는 꼭 알아야할 주의사항이 있습니다.

static 메소드에서 활용하는(불러오는) 변수나 메소드는 무조건 static 이어야 합니다.

 

이걸 모른채로 이클립스가 가이드하는대로 fix 만 해서는 

자바에서 추구하는 객체지향을 온전히 활용할 수 없습니다.

 

예제 코드를 다음과 같이 변경해보겠습니다.

class Main {

	public static void main(String [] args){

			Tmp t1 = new Tmp();
			Tmp t2 = new Tmp();
			Tmp t3 = new Tmp();		
			
			t1.incNum();
			t2.incNum();
			t3.incNum();
	}
}

class Tmp {

	static int num1 = 100;
	int num2 = 200;

	public static void incNum(){
		num1++;
		num2++;		

		System.out.println("num1 : " + num1);
		System.out.println("num2 : " + num2);
	}
}
javac -classpath .:/run_dir/junit-4.12.jar:target/dependency/* -d . Main.java Tmp.java
Tmp.java:8: error: non-static variable num2 cannot be referenced from a static context
        num2++;
        ^
1 error
exit status 1

결과는 위와 같이 compile 에러입니다.

 

주된 내용은 non-static variable num2 는 static context 에서 cannont be referenced 라네요.

static 메소드 incNum() 에서 일반변수 num2를 참조못한다는 이야기입니다.

 

해결하기 위해서 Tmp.incNum() 에서 static 을 지우고 실행해봅시다.

class Main {

	public static void main(String [] args){

			Tmp t1 = new Tmp();
			Tmp t2 = new Tmp();
			Tmp t3 = new Tmp();		
			
			t1.incNum();
			t2.incNum();
			t3.incNum();
	}
}

class Tmp {

	static int num1 = 100;
	int num2 = 200;

	public void incNum(){
		num1++;
		num2++;		

		System.out.println("num1 : " + num1);
		System.out.println("num2 : " + num2);
	}
}
더보기
num1 : 101
num2 : 201
num1 : 102
num2 : 201
num1 : 103
num2 : 201

 

static 변수 num1 은 공유자원으로 잘 작동하고

일반 변수 num2 는 각 객체단위에서 인스턴스 변수로 잘 작동했습니다.

 

그런데 왜 static incNum()은 non-static int num2 을 참조하지 못했을까요?

현재의 구조는 아래 그림과 같습니다

여기서 static 메소드 incNum() 이 non-static 변수 num2 를 참조하려면 아래와 같은 모양이 됩니다.

하지만 여기서 오류가 발생합니다.

static 메소드는 메모리 한곳에서만 공용으로 관리해야 되는데

일반 인스턴스 변수는 각각의 객체안에서 제각각 다른 값을 가지고 있기 때문입니다.

 

공유자원이 불특정 다수의 개별 자원을 참조하는게 논리적으로 맞지 않기 때문에 막혀있는 것입니다

이것은 static 메소드에도 그대로 적용됩니다.

 

static 메소드로 선언된 내부에서 

다른 메소드나 변수를 참조하려면 그 변수나 메소드도 역시 static 이어야 합니다.

 

그래서 우리가 main 함수 바깥에 사용자 함수를 만들면 

static main 의 특성상 사용자 함수도 static 으로 바꿔줄 수 밖에 없었던 것입니다.

 

이것을 해결하려면 클래스를 설계하고

main 에서도 클래스 자체를 선언하고 활용하는 방식으로 객체지향을 구현해야 합니다.

 

 

바람직하지 않은 main 함수와 엮인 사용자 함수의 예를 들며 마치겠습니다.

(이런식으로 코드를 짜면 오만데다 다 static 을 붙여야합니다. )

class Main {

	static int num1 = 100;
	static int num2 = 200;

	public static void main(String [] args){

			incNum();
			incNum();
			incNum();			
	}

	public static void incNum(){
		num1++;
		num2++;		

		System.out.println("num1 : " + num1);
		System.out.println("num2 : " + num2);
	}		
}
num1 : 101
num2 : 201
num1 : 102
num2 : 202
num1 : 103
num2 : 203
728x90
반응형

'수업 노트 > java (이론)' 카테고리의 다른 글

String 객체의 원리(2)  (0) 2021.08.24
for each 사용법  (0) 2021.08.24
String 객체의 원리(1)  (2) 2021.08.23
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/12   »
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 31
글 보관함