본문 바로가기

Java/Study

Ch6 클래스 - 정적 멤버와 Static

정적멤버와 Static

- Static 이 붙어있는 것들 

- 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드(정적 필드, 정적 메소드), 클래스 멤버라고도 한다.

- 정적멤버는 객체 내부가 아닌 메소드영역에 존재한다

- 정적 멤버는 객체를 생성하지 않고 클래스로 바로 접근해 사용한다.

  컴파일 시에 클래스로더가 바이트코드를 로딩해서 메소드 메모리 영역에 적재하기 때문에 컴파일만 하면 자동으로 메모리에 올라간다.

 

 

메모리 생성 방법

"어떤 변수든 어디든 메모리에 올라가야 한다."

1. 힙메모리영역 : new

2. 스택 메모리: 변수선언 int a; Student s;                (참조변수도 스택에 올라간다.)

3. 메소드영역 : 메소드호출시, 컴파일시(static @) ; 컴파일이 끝나면 클래스로더가 바로  static이 붙은 멤버 필드/메소드를 메소드 영역에 올린다.

cf. ApplicationEx.java --컴파일→ApplicationEx.class 바이트코드파일 →ApplicationEx.exe

 

메모리 생성순서와 초기화

정적멤버 → 인스턴스멤버(→생성자)

1. 정적멤버 : 기본값(default) → 명시적 초기화 → 정적블록

2. 인스턴스멤버 : 기본값(default)  → 명시적 초기화 → 인스턴스블록 → 생성자

정적블록과 인스턴스블록은 초기화블록이라고 부르며, { } 중괄호를 이용해 클래스 안에 선언한다. 생성자보다 먼저 실행된다.

 

*실험 결과 :

메인메소드 안의 코드순으로 실행

처음으로 Ex2클래스를 사용하려 할때, 해당클래스의 정적블록이 먼저 실행 → 인스턴스블록 실행 해당 명령(생성자 호출 or Ex.a출력)이 실행됨

(Static멤버들은 메모리에 먼저 올라가있지만 실행은 해당 클래스를 터치해야 실행된다)

package BookExampleHomeWork.carRun;

public class practice2 {
	public static void main(String[] args) {
		int local = 1;
		System.out.println("<<초기화순서>>");
		System.out.println(local);
		System.out.println("생성자 호출 전 Ex2.2a: " + Ex2.a);
		System.out.println(local);
		new Ex2(); //생성자호출시
		System.out.println("생성자가 호출 후 Ex2.2a: " + Ex2.a);
			}
		}
		class Ex2{
			static String a;
		
			static { System.out.println("정적블록 안 초기화 전 기본값: " + a);
				a = "정적블록을 사용한 초기화"; System.out.println(a); } 
			{a = "인스턴스블록을 통한 초기화"; System.out.println(a);}	  
			
		public Ex2() {
			a  = "생성자를 통한 초기화";
			System.out.println(a);
			}
		}

 

 

메모리에서 소멸되는 시점

1.힙메모리 영역 : 스택메모리와 연결된 변수가 끊어질 때

int[] a = {1,2}; //@100
int[] b = {3,4,5}; //@200
a=b; //200번지를 a변수에 넣기 100번지 int[]a는 기존의 값을 잃는다

 

2.스택메모리 : 해당 {  }벗어나면 소멸

void sum(){
int a;}
for (int i=0 ~~~{
}

 

3.메소드영역

메소드 실행하고 리턴하면 소멸됨

메소드(){
}

 

4.static 멤버변수들

프로그램이 끝나야 소멸됨

*컴파일할 때 생성되고 프로그램 끝날때까지 메모리를 할당한다.

용도 : 자료를 계속 유지해야 되는 것에 사용 (메모리를 먹기 때문에 임시로 쓰는 것들은 heap에 올려야 한다)


정적멤버 선언과 사용

정적멤버의 선언

//정적필드

static 타입 필드 [= 초기값];

//정적메소드

static 리턴타입 메소드( 매개변수선언, ... ) { ... }

 

정적멤버의 사용

클래스.필드;

클래스.메소드( 매개값, ... );

cf. 원칙적으로 클래스 이름으로 접근해야 하지만 객체 참조변수(new)로도 접근이 가능하다. 

고정되어 공용으로 사용하기 위해 정적 필드를 선언하는데 객체 생성시 독립된 객체가 힙메모리에 생성되기 때문이다.

 

필드를 인스턴스 필드로 선언할 것인가, 아니면 정적 필드로 선언할 것인가?

객체마다 가지고 있어야 할 데이터라면 인스턴스 필드

ex.객체별 color

객체마다 가지고 있을 필요가 없는 공용적인 데이터라면 정적 필드

ex. pi값

 

메소드를 인스턴스 메소드로 선언할 것인가, 아니면 정적 메소드로 선언할 것인가?

인스턴스 필드를 이용해서 실행해야 한다면 인스턴스 메소드

ex. 인스턴스의 색깔을 변경하는 메소드

인스턴스 필드를 이용하지 않는다면 정적 메소드

ex.sum, minus method는 보통 외부에서 주어진 매개값들을 가지고 수행

why? 정적메소드가 메모리에 올라가는 순서가 항상 최우선이기 때문에 인스턴스 필드와 섞어쓴다면 컴파일에러

 

오류 원인

a는 인스턴스 변수인데,

static이 먼저 실행되기 때문에 그 시점에선  a가 선언되지 않았음

 

 


정적초기화 블록

인스턴스 필드는 생성자에서 초기화하지만, 정적 필드는 객체 생성 없이도 사용해야 하므로 생성자에서 초기화 작업을 할 수 없다.

정적 필드의 단순한 초기화는 1.필드 선언과 동시에 이루어지고, 복잡한 초기화 작업을 위해서는 2.정적블록을 사용한다.

package BookExampleHomeWork;

public class Television {
	static String company = "Samsung"; //초기화방법1 
	static String model = "LCD";
	static String info;

	static {
		info = company + "-" + model; //초기화방법2 : 정적블록을 이용
	}
}

 

정적메소드와 블록 선언 시 주의할 점

정적 메소드와 정적 블록은 객체가 없어도 실행된다는 특징 때문에,  내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 

또한 객체 자신의 참조인 this 키워드 사용이 불가능하다. 

그래도 인스턴스 멤버를 사용하고 싶다면 객체를 먼저 생성하고 참조 변수로 접근해야 한다.

package BookExampleHomeWork;

public class Car5 {
	int speed; //인스턴스필드
	
	void run() {
		System.out.println(speed + "으로 달립니다.");
	}
	public static void main(String[] args) {
//		speed = 60; (x) 컴파일 에러: 정적 메소드(main())에서 인스턴스멤버를 쓰고싶다면, 객체생성이 필수
//		void run();
		Car5 myCar = new Car5();
		myCar.speed = 60;
		myCar.run();
	}
}

 

정적 메모리의 이해

package p1;

public class ClassEx01 {

	double rate = 0.15; //인스턴스 필드. 메모리에 올려줘야함 임시로 쓰고 말거면 heap에 올리는 게 나음
	
	public static void main(String[] args) {
		ClassEx01 ce = new ClassEx01(); //rate은 static mian()밖에서 선언된 인스턴스 필드. 메모리에 따로 올려줘야함.
		double result1 = 10*10*Calculator.pi * ce.rate; //pi: 정적 필드로 이미 메모리에 pi가 올라가있음 "클래스명.필드명" 바로 쓰면 됨
		System.out.println(result1); 
		
		int result2 = Calculator.plus(10, 5);
		System.out.println(result2);
		
		int result3 = Calculator.minus(10, 5);
		System.out.println(result3);
	}
}


package p1;

//238 정적멤버사용
public class Calculator {

		static double pi  = 3.14159; //정적필드 생성과 초기화
		
		static int plus(int x, int y) { //정적메소드선언
			return x + y;
		}
		static int minus(int x, int y) {
			return x - y;
		}
	}

 


static 메모리의 사용은 최대한 자제해야 한다. 각 메모리의 변수를 혼합해 사용할 때 주의점을 알아보자.

메모리의 생성 순서 

package p1;

//238 정적멤버사용
public class Calculator {

	double pp = 3.1; // 인스턴스멤버(변수)
	
	static double pi = 3.14159;

	void pMethod() { //인스턴스멤버 (메소드)
		pp += 100;
		pi += 100; //인스턴스 멤버(New하면 올라감)에 정적 멤버(컴파일 하면 올라감)를 사용할 수 있다
	}

	static int plus(int x, int y) { //정적 멤버
//		return x + y * (int) pi + (int) pp; // (int) pp; 인스턴스 멤버는 정적멤버에 사용못함
			//프로그램 실행시 plus가 정적메모리에 올라가야 하는데, 같이 있는 pp가 인스턴스 멤버라서 아직 메모리에 올라가지 않았기 때문에 함께 쓸 수 없다.
		return x + y * (int) pi; //정적멤버 사용할 수 있다.
//		return x + y;
	}

	static int minus(int x, int y) {
		return x - y;
	}
}

 

자바가 실행되는 순서

static {     }        -->       {          }   -->   생성자 호출

a=0                                  a=100             a=10000 

네트워크 설정          로그인 입력          패스워드

//instance는 다 쓸 수 있지만 , static 멤버 안에 쓸수 없음

cf.{   }같이 아무것도 없는 걸 인스턴트 블록이라고 한다.

package p2;

public class ClassEx02 {

	public static void main(String[] args) {
		new Television();
	}
}
================================================

class Television{ //원래대로라면 Television()호출되었을 때 기본 생성자가 호출되어야하는데, static block → instance block → 그다음 기본
	static int a;
	
	public Television() {
		a=1000;
		System.out.println("기본생성자 영역" + a);
	}
	{ //아무것도 없이 괄호만 : 인스턴스 블록
		a=100;
		System.out.println("인스턴스 블록 영역" + a);
	}
	static {
		a = 0;
		System.out.println("정적 블록 영역" + a);
	}
}

 

생성자와 별개로 인스턴스영역, 정적블록영역이 존재한다고 할지라도 수행문이 실행된다.

자바가 실행되는 순서 2

package p1;

public class ClassEx01 {

	public static void main(String[] args) {
		//정적멤버에 200 넣고 메소드 호출
		//정적멤버 사용법은 클래스명.멤버명 	//이미 메모리에 올라가 있기 때문에 바로 호출할 수 있음
		StaticEx.stVar1 = 200;
		StaticEx.stMethod();
		
		//인스턴스 멤버에 300을 넣고 메소드 호출하기
		//인스턴스 멤버를 힙메모리에 올린다
		StaticEx st = new StaticEx();
		//인스턴스  멤버들 사용법 참조변수.멤버명
		st.inVar1 =300;
		st.inMethod();
		
//		st.stVar1 = 300; //참조변수또한 st.stVar1로 접근가능하지만 올바른 방법은 아니다.
	}

}
===========================================================
package p1;

public class StaticEx {
	//인스턴스멤버 : 인스턴스 자료 + 정적자료
	int inVar1;
	void inMethod() {
		inVar1 = inVar1 + 100;
		System.out.println(inVar1);
		stMethod(); //정적자료인데 쓸수있음
	}

	//정적멤버
	static int stVar1;
	static void stMethod() {
		stVar1 = 100 + stVar1; //+inVar1 쓸 수 없음. 
		System.out.println(stVar1);
//		inMethod(); //호출 불가. 정적메모리가 항상 앞에 있기 때문에
	}
}

 

인스턴스멤버와 정적멤버의 이해

package p1;

public class StaticEx {
	//인스턴스멤버 : 인스턴스 자료 + 정적자료
    int sum1;
	void inMethod() {
	}

	//정적멤버 : 정적자료(0) (인스턴스자료X)
	static int sum; //각 클래스 자료를 누적+합해서 쓰고 싶을 때
	static void stMethod() {
   
	}
}
=======================================
package p1;
public class ClassEx01 {
	public static void main(String[] args) {
    	//클래스 인스턴스 변수에 값을 넣는다
		InstanceClass1 ic1 = new InstanceClass1();
		ic1.iC1 = 100;
		
		InstanceClass2 ic2 = new InstanceClass2();
		ic2.iC2 = 200; //독립된 객체
		
		//공용으로 사용되는 sum에 각 자료를 누적해서 넣는다.
		StaticEx.sum += ic1.iC1; //sum은 당연히 같은 공간
      		StaticEx.sum += ic2.iC2;
		System.out.println(StaticEx.sum); //300
        
        
        //sum1 변수를 인스턴스 변수로 만들어서 각 자료를 누적해서 넣는다.
		StaticEx s1 = new StaticEx();
		s1.sum1 += ic1.iC1;		
        //객체화를 하면 다른 메모리 공간에 sum1이 만들어진다.
		//그러므로 누적할 수 없게 된다.
		
        //s1과 s2는 다른 공간임. 두 개의 클래스에 공통이 아닌 다른 변수를 넣는 것.
		StaticEx s2 = new StaticEx(); 
		s2.sum1 += ic2.iC2;
        
		System.out.println(s1.sum1); //100
		System.out.println(s2.sum1); //200
	}

}

 

관계설정 전 (메모리의 생성 및 실행 순서)

package p1;

public class StaticBlockEx {

	public static void main(String[] args) {
	}
}
	//실행시 컴파일러가 하는 일 : 메인과의 관계설정이 안되어 있기 때문에 아직은 무관하지만
    							static멤버를 메모리에 올린다.
class Ex{
	static {System.out.println("aaa");} //1.static 탐색 : 메모리에는 올린다. 아직은 수행하지 않음
	{System.out.println("bbb");}	    //2.{} 인스턴스 탐색 : 메인과의 관계 설정이 되어있지 않다. 
	{System.out.println("ddd");}
	
	public Ex() { //기본생성자	    //3.기본생성자 탐색 : 마찬가지로 객체가 생성되기 전이다
		System.out.println("ccc");
	}
	
	public Ex(int a) {	//명시적 생성자
		System.out.println("fff");
	}
	static {System.out.println("eee");} //1.static 탐색
}

관계설정 후 (메모리의 생성 및 실행 순서)

package p1;

public class StaticBlockEx {

	public static void main(String[] args) {
public static void main(String[] args) {
		System.out.println("메인이 빠를까 정적멤버가 빠를까"); //0.가장먼저나옴
				new Ex(); //생성자호출시 객체가 생성되며 인스턴스블록이 메모리에 올라감
			}

		}
		 //탐색순서동일
		class Ex{
			static {System.out.println("aaa");}  //4.static부터 실행
			{System.out.println("bbb");}	     //5.instance실행
			{System.out.println("ddd");}
			
			public Ex() { //기본생성자는 하나만.  //6.기본생성자 실행
				System.out.println("ccc");
			}
			
			public Ex(int a) { //오버로딩은 가능 //메인에서 호출하지 않았기 때문에 안찍힌다.
		    					//원하면 찍거나 기본 생성자에서 호출 가능 ex. this(10); 
				System.out.println("fff");
			}
			static {System.out.println("eee");} //4.static부터 실행
		}

 

 

 

싱글톤(Singleton)

전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우, 단 하나만 생성된 객체를 싱글톤이라고 한다. 싱글톤을 만들기 위해서는 클래스 외부에서 new연산자로 생성자를 호출할 수 없도록 막아야 하는데, 생성자 앞에 private 접근 제한자를 붙여주면 된다.  참고로 클래스 내부에서는 new연산자로 생성자 호출이 가능하다.

 

싱글톤을 만드는 코드

1. private static 자신의 타입(해당 class) 필드 선언

2. 자신의 객체(해당 class) 생성해 초기화(주소값을 필드에 넣어준다.)

3. 정적메소드 getInstance() 선언 - 4.참조하고 있는 자신의 객체(주소값)을 리턴

cf.getInstance는 username이지만 통상적으로 getInstance()라고 쓴다

 

더보기

public class Singleton {
//정적필드
private static Singleton singleton = new Singleton();

//생성자
private 클래스( ) { }

//정적메소드
static Singleton getInstance( ){
         return singleton;
      }
}

=======================

Singleton.getInstance(); //실행 클래스에서 Singleton객체에 접근하기 위한 유일한 통로

 

싱글톤1

더보기
package BookExampleHomeWork;

public class Singleton {
	//클래스 내부에서는 new연산자로 생성자 호출이 가능하다.
	private static Singleton singleton = new Singleton();
	//1.자신의 타입인 정적 필드를 선언한다. 이 때 private접근제한자로 외부 접근을 막는다. 2.자신의 객체를 생성해 초기화한다. 
	private Singleton() {} 
	
	static Singleton getInstance() {//3.단하나의 연결구 정적 메소드 getInstance()를 선언한다. 리턴타입은 접근을 열어줄 클래스(Singleton)
		return singleton; 			//4.정적 필드에서 참조하고 있는 자신의 객체를 리턴해준다. (그림 참조)
	}								//private 필드이기 때문에 내부의 메소드를 통해서만 접근 가능하다. 따라서 return값으로 정적 필드의 주소값을 알려준다.
}
package BookExampleHomeWork;

public class SingletonExample {

	public static void main(String[] args) {
//		Singleton obj1 = new Singleton(); 컴파일 에러 why? 생성자가 private으로 선언되었기 때문에 싱글톤을 통해서만 접근가능하다.
//		Singleton obj2 = new SingletonExample(); //사실상 외부 생성자 호출을 막는대신 내부 클래스에서 생성자를 미리 만들어 getInstance()를 통해서만 알려주는 것.
		
		Singleton obj1 = Singleton.getInstance(); //정적 메소드 getInstance메소드의 리턴값은 곧 Singleton객체의 주소이다 
		Singleton obj2 = Singleton.getInstance(); //즉 클래스 내부에서 객체를 생성하고 그 주소값을 메소드를 통해 리턴함으로써 외부에서 우회적으로 접근가능하게 한다.
		
		if(obj1 ==  obj2) {
			System.out.println("같은 Singleton 객체입니다.");
		} else {
			System.out.println("다른 Singleton 객체입니다.");
		}
	}
}

 

싱글톤2

package p3;

import java.util.Calendar;

public class SingleTonEx {

	public static void main(String[] args) {

		SingTon st = SingTon.getInstance();
		st.name = "홍길동";
		st.age = 20;
		st.age += 20;
		System.out.println(st.age);
	}
}

==============================================

package p3;

public class SingTon {
	private static SingTon st = new SingTon(); //이걸로만 접근 가능
	
	//기본생성자
	private SingTon() {}
	
	//다른 객체와 연결되는 통로(메소드) ; 메소드명은 username이긴 한데 통상적으로 getInstance라고 씀.
	static SingTon getInstance() {
		return st;
	}
}

 

싱글톤3

package p3;

import java.util.Calendar;

public class SingleTonEx {

	public static void main(String[] args) {
		SingTon2 st2 = SingTon2.getInstance(); //s2(힙메모리의 번지)를 리턴함 > SingTon2의 클래스주소 st2에 담음
		st2.name="이순신";
		st2.age = 10;
		System.out.println(st2);
		
		SingTon2 st3 = SingTon2.getInstance(); // st2 =st3이기 때문에 출력값 같음
		System.out.println(st3);
		st3.age += 50;
		System.out.println(st3.age);
		System.out.println(st2.age);
        
        }
}
=================================================
package p3;

//싱글톤 객체의 구조
//객체 생성을 단 한번만 하게 보장해줌. 싱글톤 객체의 인스턴스 자료를 캡슐화해서 이용함
//즉 객체생성이 1번만 되므로 주소 변동이 없고 싱글톤 안에 인스턴스자료를 공용화해서 이용할 수 있음 cf.new는 할때마다 힙메모리안 주소가 계속 바뀜

public class SingTon2 {
	
	String name;
	int age;
	
	//정적 필드 (외부접근 불허)
	private static SingTon2 s2 = new SingTon2(); //링크 로더단계(컴파일)에서 메모리가 올라갈 때, 바로 객체화해서 메모리에 올려주고 s2에 주소 담긴다.
	
	//생성자
	private SingTon2() {
		
	}
	
	static SingTon2 getInstance() { //패키지 내에서 접근 가능. s2 리턴하게
		return s2;
		
		//s2, SingTon2 모두 prviate이기 때문에 getInstance()를 통해서만 접근가능하다.
	}
}

1. new 클래스명(); - 이게 오류나면

2. 클래스명.getInstance(); - 2를 시도 후 되면 싱글톤으로 만들어진 클래스라고 생각하면 됨 ex.Calendar 클래스

 

 

 

'Java > Study' 카테고리의 다른 글

Ch6 - 패키지, 접근제한자  (0) 2021.03.05
Ch6 클래스 - final 필드와 상수  (0) 2021.03.05
Ch6 객체지향프로그래밍 - 메소드  (0) 2021.03.04
Ch6 객체지향프로그래밍 - 필드, 생성자  (0) 2021.03.02
Ch5 - 열거타입  (0) 2021.03.02