JAVA

static 키워드 (static 멤버 / 메모리 할당 순서 / 싱글톤 디자인 패턴)

태로미 2023. 3. 14. 09:15

 

목차

0.   JAVA 프로그램 실행 과정


1.   static 키워드

    1-1.   static 변수

    1-2.   static 메서드

    1-3.   static 멤버와 인스턴스 멤버의 메모리 할당 순서


2.    싱글톤 디자인 패턴 (Singleton Design Pattern)

 

 

 

 

 

 

 

 

 

 

📍   JAVA  프로그램 실행 과정

 

0.   소스 코드 작성 및 컴파일 후 클래스 실행

      →   .java파일을 만들어서 javac라는 명령어로 컴파일

1.   클래스 로딩

      →   static변수 및 메서드가 먼저 메모리에 로딩됨.

2.   main() 메서드 호출 (실행)

3.   인스턴스 생성

       →   인스턴스 변수(= field = 멤버변수) 및 메서드가 메모리에 로딩됨.

4.   참조변수를 통해 인스턴스 메서드 호출 (실행)

       →   로컬 변수가 메모리에 로딩됨.

5.   결과 출력

6.   프로그램 종료

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

📍   static 키워드

–   변수,  메서드,  클래스의 제한자로 사용.
–   변수 또는 메서드에 static 키워드를 사용할 경우, 
     인스턴스 생성과 상관없이 클래스가 로딩되는 시점에 메모리에 로딩됨.

     (클래스가 로딩되는 Method Area에 함께 로딩됨.)
      →   따라서 참조변수 없이 클래스명만으로 멤버에 접근 가능함.  즉,  인스턴스를 생성하지 않아도 접근이 가능한 멤버.
             ex )   static이 없으면 s.sister_1() 형태로 인스턴스 생성 후 메서드 호출.
                      static이 있으면 sister_1() 형태로 바로 메서드 호출.

–    Java에서 static 키워드를 사용한다는 것은 메모리에 한번 할당되어 프로그램이 종료될 때 해제되는 것을 의미.

 

 

 

 

 

 

 

 

 

 

▶   static 변수

–   클래스명.멤버변수명 형태로 접근.


EX1   )

 

•   클래스 생성하기

class NormalMember{

	int a = 10;		// 인스턴스 변수(=멤버변수 =필드)
	int b = 20;		// 인스턴스 변수
	
}


class StaticMember{
	static int a = 10;	// static 멤버변수(=클래스 멤버변수 =정적 멤버변수)
	int b = 20;		// 인스턴스 멤버변수
}

 

 

•   main 메서드에서 실행하기

 

1 )   NormalMember 클래스

// NomalMember 클래스의 인스턴스 생성
NormalMember n1 = new NormalMember();
NormalMember n2 = new NormalMember();

System.out.printf("n1.a : %d, n2.a : %d \n", n1.a, n2.a);
System.out.printf("n1.b : %d, n2.b : %d \n", n1.b, n2.b);

n1.a = 99;		// n1 인스턴스의 인스턴스 멤버변수 a값을 99로 변경
n1.b = 999;		// n1 인스턴스의 인스턴스 멤버변수 b 값을 999로 변경

System.out.printf("n1.a : %d, n2.a : %d \n", n1.a, n2.a);
System.out.printf("n1.b : %d, n2.b : %d \n", n1.b, n2.b);

–   인스턴스 멤버변수인 a와 b는 인스턴스마다 각각 생성되므로,
     하나의 인스턴스에서 인스턴스 멤버변수 값을 바꾸더라도 다른 인스턴스 멤버변수에는 아무런 영향이 없음.
     (성된 메모리 공간이 다르기 때문.)

     →   따라서 n1 인스턴스의 인스턴스 멤버변수 값을 변경해도 n2의 인스턴스 멤버변수 값은 그대로임. 

   실행 결과

더보기

n1.a : 10,  n2.a : 10 
n1.b : 20,  n2.b : 20 
n1.a : 99,  n2.a : 10 
n1.b : 999,  n2.b : 20 

 

2 )   StaticMember 클래스

System.out.println("StaticMember.a : " + StaticMember.a);

–   StaticMember 클래스의 인스턴스 생성 전에 static 멤버에 접근 가능.
–   인스턴스가 생성되기 전이지만 이미 메모리에 로딩되어 있으므로 클래스명만으로 static 변수에 접근할 수 있음. 
✓   실행 결과

더보기

StaticMember.a : 10

 

StaticMember s1 = new StaticMember();
StaticMember s2 = new StaticMember();		

System.out.printf("s1.a : %d, s2.a : %d \n", s1.a, s2.a);
System.out.printf("s1.b : %d, s2.b : %d \n", s1.b, s2.b);

–   두 개의 인스턴스가 생성되며 인스턴스 멤버변수도 두 개씩 생성됨.
   실행 결과

더보기

s1.a : 10, s2.a : 10 
s1.b : 20, s2.b : 20 

 

s1.a = 99;		// static 멤버변수 값을 변경
s1.b = 999;		// 인스턴스 멤버변수 값을 변경

System.out.printf("s1.a : %d, s2.a : %d \n", s1.a, s2.a);
System.out.printf("s1.b : %d, s2.b : %d \n", s1.b, s2.b);

–   static 멤버변수 값(a)을 s1 인스턴스에서 변경하게 되면,
     s2 인스턴스도 동일한 변수 값을 공유하므로 변경된 값이 두 인스턴스 모두 적용(공유)됨.
     →   즉,  하나의 인스턴스에서 값을 변경하면 모든 인스턴스가 영향을 받음.
   실행 결과

더보기

s1.a : 99, s2.a : 99 
s1.b : 999, s2.b : 20 

 

s2.a = 1000;
System.out.printf("s1.a : %d, s2.a : %d \n", s1.a, s2.a);

–   s2.a로 Heap영역에서 Method영역으로 가는 방법 (인스턴스 생성으로 참조변수에서 접근하는 방법)보다는 
     StaticMember.a로 한 번에 Method영역으로 가는 방법을 추천하므로 경고 (노란 줄) 발생.
     →   틀린건 아니지만 클래스명.변수명 (또는 메서드명) 이 형식을 추천함.

–   StaticMember 클래스의 멤버 a는 static 키워드가 붙었으므로 하나의 인스턴스 값만 변경해도 모든 인스턴스 값이 바뀜.
   실행 결과

더보기

s1.a : 1000, s2.a : 1000 

 

StaticMember.a = 500;
// StaticMember.b = 500; 
System.out.printf("s1.a : %d, s2.a : %d \n", s1.a, s2.a);

–   static 멤버는 참조변수명 대신 클래스명만으로 접근 가능.
–   static 멤버가 아닌 일반 멤버는 클래스명만으로 접근 불가,  컴파일 에러 발생.
   실행 결과

더보기

s1.a : 500, s2.a : 500 

 

–   Static 멤버변수의 대표적인 예
     →   java.lang.Math 클래스의 PI 변수 (이미 만들어져있음)

–   자동완성 화면에서 초록색은 메서드를 뜻하고,  s는 static 키워드가 붙었다는 것을 의미함.

     →   따라서 Math.PI는 new 키워드로 인스턴스를 생성하지 않고도 바로 클래스명.변수명으로 접근 가능.
   실행 결과

더보기

PI 값 : 3.141592653589793

 

 

 

 

 

 

 

 

 

▶   static 메서드

–   정적 메서드.
–   메서드 정의 시 리턴타입 앞에 static키워드를 붙여서 정의.
–   클래스가 메모리에 로딩될 때 static 변수와 함께 메모리에 로딩되므로 인스턴스 생성과는 무관함.
–   클래스명만으로 접근 가능

     →   클래스명.메서드명( ) 형태로 호출.

 

 

 

 

 

 

 

▸   static 메서드 정의 시 주의 사항

–   인스턴스 생성 시점에서 생성되는 것은 static 메서드 내에서 접근 불가.

      →   static 멤버가 먼저 생성 된 후 인스턴스가 생성되므로,  인스턴스 생성 시점에서 생성되는 것은 실행 불가.

 

 

 

1.   인스턴스 변수 사용 불가

•   원인
–   static 메서드가 로딩되는 시점은 클래스가 로딩되는 시점이며,

     인스턴스 변수는 new 키워드를 통해서 인스턴스 생성 시점에 로딩되므로, 

     static 메서드가 로딩되는 시점에서는 존재하지 않음.

 

2.   레퍼런스 this 또는 super 사용 불가

•   원인
–   static 메서드가 로딩되는 시점은 클래스가 로딩되는 시점이며 레퍼런스 this는 인스턴스 생성 시점에 생성되므로,
     static 메서드가 로딩되는 시점에서는 존재하지 않음.
•   해결책
–   this.xxx 또는 super.xxx 대신 클래스명.xxx 형식으로 접근.

 

3.   메서드 오버라이딩 불가

–   이유는 상속 단원에서 자세히 배움.


EX   )

 

•   클래스 생성하기

class StaticMethod {
	
	private int normalVar = 10;
	private static int staticVar = 20;

–   인스턴스 멤버변수 normalVar 선언,  10으로 초기화.

–   static (정적) 멤버변수 staticVar 선언,  20으로 초기화.

→   둘 다 접근제한자는 private로 설정함.

 

 

•   일반 메서드 정의하기

	// 일반 메서드 normalMethod() 정의
	public void normalMethod() {
		System.out.println("인스턴스 변수에 접근 : " + normalVar);	// 가능
		System.out.println("static 변수에 접근 : " + staticVar);		// 가능
		
		// static 메서드 호출
		staticMethod();							// 가능
	}

–   일반 메서드는 인스턴스가 생성되는 시점에 메모리에 로딩됨.
     →   따라서 일반 메서드에서 인스턴스 변수 & static 변수 모두 접근 가능.
     →   바로 밑에 정의한 static 메서드도 호출 가능

 

 

•   static 메서드 정의하기

	// static 메서드 staticMethod() 정의
	public static void staticMethod() {
		System.out.println("정적 메서드 staticMethod()");
//		System.out.println("인스턴스 변수에 접근 : " + normarVar);	// 불가
		System.out.println("static 변수에 접근 : " + staticVar);		// 가능

–   static 변수에는 접근 가능하나 인스턴스 변수에는 접근 불가.
    →   static 메서드는 클래스가 메모리에 로딩되는 시점에 함께 로딩되는데,
          인스턴스 변수는 아직 메모리에 로딩되기 전이므로 접근 불가능.

//		normalMethod();
	}

–   static 메서드에서 일반 메서드 호출 불가.
     →   변수와 원인 동일함.  static 메서드 생성 시점에 일반 메서드는 생성되지 않았음.
     →   미리 만들어져있지 않아서 new 키워드와 함께 사용해야 함.

 

 

•   set 메서드 정의하기

	// 인스턴스 변수의 set 메서드 정의
	public void setNormalVar(int normalVar) {
		this.normalVar = normalVar;
	}

–   set 메서드정의 시 this키워드를 사용하므로 set 메서드만 정의해봄.
–   파라미터로 전달받은 데이터를 클래스의 멤버변수에 저장하는 코드가 문제없이 실행됨.

	// static 변수의 set 메서드 정의
	public static void setStaticVar(int staticVar) {		
//		this.staticVar = staticVar;
		StaticMethod.staticVar = staticVar;
	}
}

–   static 메서드 내에서는 레퍼런스 this 사용 불가.
     →   레퍼런스 this는 인스턴스 생성 시점에 생성되지만,
            static 메서드가 로딩되는 시점에는 레퍼런스 this가 존재하지 않으므로 에러 발생.
–   레퍼런스 this 대신 클래스명으로 static 변수 접근 가능함.

 

 

 

 

 

 

 

 

▶   static 멤버와 인스턴스 멤버의 메모리 할당 순서

–   아래의 예제를 통해 각 멤버의 메모리 할당 순서를 알아보겠음.


EX   )

public class Ex3 {
	

	int b = check("인스턴스 변수 b");
	static int a = check("static 변수 a");


	// check 메서드
	public static int check(String str) {
		System.out.println("check() 호출 :" + str);
		return 0;
}


	// main 메서드
	public static void main(String[] args) {
		System.out.println("main() 메서드 호출");

		Ex3 ex = new Ex3();
		System.out.println("Ex3 인스턴스 생성 완료");
	}

	
	static int c = check("static 변수 c");
	

} // Class Ex3

0.   Ex3 클래스가 메모리에 로딩됨.


1.   static 키워드가 선언된 모든 멤버가 메모리에 로딩됨.

      →   static int a   /   static check( )   /   main( )   /   static int c


2.   static 멤버가 메모리에 로딩될 때,  static 변수 a와 c에 check( ) 메서드 리턴값이 전달되어야 하므로
      static 메서드인 check( ) 메서드 호출됨.
    2-1.   "static 변수 a" 출력 (static 변수 a 로딩됨)
    2-2.   "static 변수 c" 출력 (static 변수 c 로딩됨)


3.   main() 메서드 자동으로 호출됨.
      →   "main( ) 메서드 호출" 출력


4.   main( ) 메서드 내에서 Ex3 인스턴스 생성됨.


5.   Ex3 인스턴스 생성 시 인스턴스 변수 b가 메모리에 로딩되며,
     인스턴스 변수 b가 로딩될 때,  이미 method area 메모리 영역에 등록되어 있는 check() 메서드가 호출됨.
      →   "인스턴스 변수 b" 출력


6.   Ex3 인스턴스 생성 후 main() 메서드의 다른 코드 실행.
      →   "Ex3 인스턴스 생성 완료" 출력

 

   실행 결과

더보기

check() 호출 :static 변수 a
check() 호출 :static 변수 c
main() 메서드 호출
check() 호출 :인스턴스 변수 b
Ex3 인스턴스 생성 완료

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

📍   싱글톤 디자인 패턴 (Singleton Design Pattern)

–   단 하나의 '유일한 인스턴스' 만을 생성하여 공유하도록 하는 기법.
–   외부에서 인스턴스 생성을 하지 못하도록 차단하고,  클래스 내에서 직접 인스턴스를 생성 후 외부로 리턴하는 기법.
–   누구나 단 하나의 인스턴스만을 공유하여 사용해야 함.
     →   즉,  외부에서 new 키워드를 통해 새 인스턴스 생성 불가능.

–   외부에서 함부로 접근하지 못하게 하는 역할private 접근제한자

     외부에서 인스턴스 생성 없이 접근할 수 있게 하는 역할static 키워드

 

 

 

 

 

 

▸   패턴 규칙

1.   외부에서 인스턴스 생성이 불가능하도록 생성자에 private 접근제한자를 선언하여 정의.
2.   자신의 클래스에서 직접 인스턴스를 생성하여 참조변수에 저장.
      →   외부에서 인스턴스 생성없이도 접근 가능하도록 static 키워드 사용.
      →   외부에서 함부로 접근하지 못하도록 private 접근제한자를 선언함.
3.   생성된 인스턴스를 외부로 리턴하는 Getter 메서드 정의
      →   private 접근제한자로 선언된 멤버변수에 접근하여, 생성된 인스턴스를 리턴하는 용도로 정의.
      →   인스턴스 생성없이 호출해야하므로 static 키워드 사용.


 

EX   )

 

•   클래스 생성하기

class SingletonClass{
	
	// 임시 확인용 변수
	String str;		
	
	private SingletonClass(){}
	
	private static SingletonClass instance = new SingletonClass();

	public static SingletonClass getInstance() {
		return instance;
	}
    
}
코드 분석
6
  –   외부 (다른 클래스)에서 생성자 호출이 불가능하도록 기본 생성자에 private 접근제한자 선언.

8
  –   자신의 클래스 내에서 인스턴스를 직접 생성 후 참조변수(instance)에 저장.
        →   외부에서 인스턴스 생성없이 접근 해야하므로 static 키워드 사용.
        →   외부에서 함부로 접근하지 못하도록 private 접근제한자를 선언함.

10, 11
  –   생성된 인스턴스를 외부로 리턴하는 get 메서드 정의.
        →   인스턴스를 저장하는 참조변수 instance가 private 접근제한자이므로 접근이 불가능한 대신,
               인스턴스를 리턴하는 목적으로 정의.
        →   인스턴스 생성없이 접근해야하므로 static 키워드 사용.
        →   return타입이 SingleonClass이므로 같은 주소값을 지닌 instance를 리턴해줌.

 

 

•   main 메서드에서 실행하기

// 인스턴스 생성 불가
// SingletonClass sc = new SingletonClass();
// SingletonClass sc2 = new SingletonClass();
	
// 참조변수
SingletonClass sc;

//	SingletonClass sc = instance;
	SingletonClass sc = SingletonClass.instance;
	SingletonClass sc = SingletonClass.getInstance();

// 리턴받은 인스턴스
sc.str = "싱글톤 패턴으로 생성한 인스턴스의 str";
		
// sc2에 인스턴스를 리턴받아 저장
SingletonClass sc2 = SingletonClass.getInstance();
		
System.out.println("sc.str : " + sc.str);
System.out.println("sc2.str : " + sc2.str);
		
// 두 인스턴스(sc, sc2)가 같은지 비교
if(sc == sc2) {
	System.out.println("sc와 sc2는 같은 인스턴스");
}else {
	System.out.println("sc와 sc는 다른 인스턴스");
}
코드 분석
8, 9
  –   클래스 내에서 인스턴스 생성 시, 
       static 키워드를 사용하면 instance에 담긴 주소값을 sc 변수에도 저장 가능.
        →   클래스 내에서 인스턴스를 생성하여 멤버변수 instance에 주소값을 저장했기 때문에,
               해당 변수 (instance)를 통해 인스턴스의 주소값을 가져올 수 있음.
        →   get 메서드를 통해 instance를 외부로 리턴하였음.
        →   이 때, 해당 변수는 static 변수이므로 클래스명만으로 접근 가능함.

10
  –   SingletonClass 내에서 싱글톤 디자인 패턴으로 생성된 인스턴스를 변수 instance에 저장했으나,
       private 접근제한자로 인해 접근 불가하므로 클래스에 정의해 놓은 getInstance() 메서드 호출을 통해 가져옴.
        →   이 때,  getInstance() 메서드는 static 메서드이므로,  클래스명.메서드명() 형태로 호출 가능.
        →   즉,  인스턴스 생성 없이도 클래스 내의 메서드 호출 가능.

13
  –   리턴받은 인스턴스는 참조변수 (sc)를 통해 접근 가능.

16
  –   singletonClass타입 변수 sc2에 인스턴스를 리턴받아 저장.

18, 19
  –   리턴받은 인스턴스는 참조변수 (sc2)를 통해 접근 가능함.

22~26
  –   if문을 통해 두 인스턴스(sc, sc2)가 같은지 비교 → 같은 인스턴스.
       →   싱글톤 패턴으로 생성하기 위해 클래스에서 미리 인스턴스를 생성하고,

             새로운 인스턴스를 생성하지 못하도록 생성자에 pivate 접근제한자를 사용했으므로,
             기존에 있던 클래스의 인스턴스를 사용하는 것.
             따라서,  둘은 같은 주소값을 가지며 동등비교연산자 (==)를 사용하였을 시 같다는 결과가 나옴.

   실행 결과

더보기

sc.str : 싱글톤 패턴으로 생성한 인스턴스의 str
sc2.str : 싱글톤 패턴으로 생성한 인스턴스의 str
sc와 sc2는 같은 인스턴스