안루피취뽀일기

static 변수 본문

Java

static 변수

안루피 2023. 10. 3. 19:07
728x90

변수를 여러 클래스에서 공통으로 사용하려면?

 

학생 클래스를 보자.

public class Student {
	int studentID;
	String studentName;
	int grade;
	String address;
}

 

학생 클래스를 사용하면 여러 학생의 인스턴스를 만들 수 있다. 그리고 학생마다 고유한 학번(studentID)를 가지는데, 학생이 입학하면(클래스가 생성되면) 학번이 자동으로 생성되도록 만들고 싶다. 생성된 인스턴스는 학번을 순서대로 가져와야 한다. 이때 어떻게 학생에게 학번을 부여할 수 있을까? 이 경우에 각 인스턴스마다 따로 생성되는 변수가 아닌, 클래스 전반에서 공통으로 사용할 수 있는 기준 변수가 있어야 한다. 그리고 학생이 한 명 생성될 때마다 기준 변수 값을 하나씩 증가시켜 각 학생 인스턴스의 학번 변수에 대입해 주면 된다. 이때 클래스에서 공통으로 사용하는 변수를 'static 변수'로 선언한다.

 

static 변수의 정의와 사용 방법

 

static 변수란 다른 용어로 '정적 변수'라고도 한다. static 변수는 자바뿐만 아니라 다른 언어에서도 비슷한 개념으로 사용하고 있는 변수로서 자바에서는 다른 멤버 변수처럼 클래스 내부에 선언한다. 변수를 선언할 때 다음과 같이 자료형 앞에 static 예약어를 사용한다. 

 

static 변수는 클래스 내부에 선언하지만, 다른 멤버 변수처럼 인스턴스가 생성될 때마다 새로 생성되는 변수가 아니다. static 변수는 프로그램이 실행되어 메모리에 올라갔을 때 딱 한 번 메모리 공간이 할당된다. 그리고 그 값은 모든 인스턴스가 공유한다.

 

다시 말하면 일반 멤버 변수는 인스턴스가 생성될 때마다 새로 생성되어 각각 다른 studentName을 가지게 되지만, static으로 선언한 변수는 인스턴스 생성과 상관없이 먼저 생성되고 그 값을 모든 인스턴스가 공유하게 되는 것이다. 이런 이유 때문에 static 변수를 클래스에 기반한 변수라고 해서 '클래스 변수(class variable)'라고도 한다. 

 

그러면 학생이 새로 생성되면 학번을 차례로 부여하는 예제를 통해 static 변수를 사용하는 법을 살펴보자. 예제에서 사용할 static 변수는 serialNum이다. 변수의 값은 학생이 될 때마다 순서대로 증가할 것이다. 이 증가된 값을 각 학생의 학번에 대입해 주면 학생에게 새로운 학번이 부여된다. 

 

package staticex;

public class Student {
	public static int serialNum = 1000; //static 변수는 인스턴스 생성과 상관 없이 먼저 생성됨
	public int studentID;
	String studentName;
	int grade;
	String address;
	
	public String getStudentName() {
		return studentName;
	}
	
	public void setStudentName(String name) {
		studentName = name;
	}
}

 

Student 클래스에서 기준 값 역할을 하는 serialNum 변수를 선언하고 기준 값으로 1000을 대입한다. 다음의 테스트 코드에서 학생을 두 명 생성하고 serialNum이 증가했을 때 두 인스턴스에서 증가된 값이 공유되는 지 확인해보자.

 

package staticex;

public class StudentTest1 {

	public static void main(String[] args) {
		Student studentLee = new Student();
		studentLee.setStudentName("이강인");
		System.out.println(studentLee.serialNum); //serialNum 변수의 초깃값 출력 
		studentLee.serialNum++; //static 변수값 증가
		
		Student studentSon = new Student();
		studentSon.setStudentName("손흥민");
		System.out.println(studentSon.serialNum); //증가된 값 출력 
		System.out.println(studentLee.serialNum); //증가된 값 출력

	}

 

 

StudentLee를 먼저 생성하고 이 참조 변수를 사용하여 전체 인스턴스에서 공통으로 사용하는 serialNum 변수 값을 1 증가시킨다. 그리고 studentSon을 생성한다. 생성된 studentSon과 studentLee로 serialNum 변수 값을 출력해보면 둘 다 1001로 증가된 serialNum 값이 출력되는 것을 알 수 있다. static으로 선언한 serialNum 변수는 모든 인스턴스가 공유하기 때문이다. 즉 두 개의 참조 변수가 동일한 변수의 메모리를 가리키고 있다는 것을 알 수 있다. 

 

학번 생성하기

 

이제 원래 구현하려 했던 학생이 한 명 생성될 때마다 학번을 자동으로 부여하는 프로그램을 완성해보자.

 

package staticex;

public class Student1 {
	public static int serialNum = 1000;
	public int studentID;
	String studentName;
	int grade;
	String address;
	
	//생성자 
	public Student1() {
		serialNum++; //학생이 생성될 때마다 증가 
		studentID = serialNum; //증가된 값을 학번 인스턴스 변수에 부여 
		
	}

	public String getStudentName() {
		return studentName;
	}

	public void setStudentName(String studentName) {
		this.studentName = studentName;
	}
	
}

 

static 변수(serialNum)를 하나 선언한다. 학생이 생성될 때마다 이 변수 값이 증가한다. 그런데 여기에서 주의할 점은 static 변수를 그냥 바로 학번으로 사용하면 안 된다는 것이다. 왜내하면 static 변수는 모든 인스턴스가 공유하는 변수이므로 이 변수를 바로 학번으로 사용하면 모든 학생이 동일한 학번 값을 가지게 되기 떄문이다. 학번은 학생의 고유 번호이므로 학생의 멤버 변수로 선언해주고, 학생이 한 명 생성될 때마다 증가한 serialNum 값을 studentID에 대입해주면 이 문제를 해결할 수 있다. Student 클래스에 생성자를 추가하고 생성자에서 serialNum 값을 증가시키고 증가된 값을 studentID 변수에 대입한다. 

 

그러면 StudentTest2 클래스에서 실제로 학생이 생성될 때마다 증가된 다른 학번을 가지는지 확인해 보자.

 

package staticex;

public class StudentTest2 {

	public static void main(String[] args) {
		Student1 studentLee = new Student1();
		studentLee.setStudentName("이강인");
		System.out.println(studentLee.serialNum);
		System.out.println(studentLee.studentName + " 학번: " + studentLee.studentID);
		
		Student1 studentSon = new Student1();
		studentSon.setStudentName("손흥민");
		System.out.println(studentSon.serialNum);
		System.out.println(studentSon.studentName + " 학번: " + studentSon.studentID);

	}

}

 

학생 인스턴스를 생성할 때마다 serialNum 변수의 값은 증가한다. 그리고 새로 생성되는 학생마다 가지는 studentID 변수에 증가한 serialNUm 값을 복사해 주었으므로, 두 학생의 학번은 다르다. 이처럼 static 변수는 같은 클래스에서 생성된 인스턴스들이 같은 값을 공유할 수 있으므로, 인스턴스 간에 공통으로 사용할 값이 필요한 경우 유용하게 사용할 수 있다. 

 

클래스 변수

 

앞 예제에서 살펴본 것처럼 static 변수는 인스턴스를 생성할 때마다 만들어지는 것이 아니고 클래스를 선언할 때 특정 메모리에 저장되에 모든 인스턴스가 공유하는 변수이다. static 변수는 인스턴스 생성과는 별개이므로 인스턴스보다 먼저 생성된다. 그러므로 인스턴스가 아닌 클래스 이름으로도 참조하여 사용할 수 있다. 따라서 자바에서는 static 변수를 클래스 변수라고도 하며 studentTest2.java 코드는 다음처럼 변경할 수 있다. 

 

package staticex;

public class StudentTest2 {

	public static void main(String[] args) {
		Student1 studentLee = new Student1();
		studentLee.setStudentName("이강인");
		System.out.println(Student1.serialNum); //serialNum 변수를 직접 클래스 이름으로 참조 
		System.out.println(studentLee.studentName + " 학번: " + studentLee.studentID);
		
		Student1 studentSon = new Student1();
		studentSon.setStudentName("손흥민");
		System.out.println(Student1.serialNum); //serialNum 변수를 직접 클래스 이름으로 참조 
		System.out.println(studentSon.studentName + " 학번: " + studentSon.studentID);

	}

}

 

StudentTest2.java 파일처럼 static 변수 serialNum을 studentLee.serialNum과 같이 인스턴스로 참조할 수도 있다. 하지만 static 변수는 인스턴스가 생성되지 않아도 사용할 수 있기 때문에 보통은 Student.serialNum과 같이 클래스 이름과 함께 사용한다.

 

지금까지 static 변수, 정적 변수, 클래스 변수라는 세 가지 용어를 사용했다. 셋 모두 자바에서 static 변수를 의미한다. 자바에서 static 변수라고 하는 이유는 인스턴스마다 생성되는 변수가 아니라 클래스에 속해 한 번만 생성되는 변수이고 이를 여러 인스턴스가 공유하기 때문이다.

 

클래스 메서드

 

일반 멤버 변수를 위한 메서드가 존재하듯이 static 변수를 위한 메서드도 있다. 이런 메서드를 'static 메서드' 또는 '클래스 메서드(class method)'라고 한다. 여기에서는 serialNum 변수를 사용하는 메서드를 만들어 보겠다. 외부 클래스에서 serialNum 변수를 직접 참조하지 못하도록 일단 private으로 선언하고 이 변수에 대한 get() 메서드와 set() 메서드를 생성한다. Student 클래스의 serialNum 변수를 private으로 변경하면 기존의 StudentTest1, StudentTest2, StudentTest3에서는 직접 참조할 수 없어 오류가 발생하므로 Student2 클래스를 새로 생성한다. 

 

package staticex;

public class Student2 {
	private static int serialNum = 1000; //private 변수로 변경 
	public int studentID;
	String studentName;
	int grade;
	String address;
	
	public Student2() {
		serialNum++;
		studentID = serialNum;
	}

	public String getStudentName() {
		return studentName;
	}

	public void setStudentName(String name) {
		studentName = name;
	}
	
	//serialNum의 get() 메서드 
	public static int getSerialNum() {
		int i = 10;
		return serialNum;
	}
	//serialNum의 set() 메서드 
	public static void setSerialNum(int serialNum) {
		Student2.serialNum = serialNum;
	}
	
}
}

 

이제 외부 클래스에서 serialNum 값을 사용하려면 get() 메서드를 호출하고, serialNum 변수 값을 변경하려면 set() 메서드를 사용해야 한다. get() 메서드와 set() 메서드를 사용하도록 변경했을 때 프로그램이 제대로 실행되는지 확인해보자.

 

package staticex;

public class StudentTest4 {

	public static void main(String[] args) {
		Student2 studentLee = new Student2();
		studentLee.setStudentName("이강인");
		//serialNum 값을 가져오기 위해 get() 메서드를 클래스 이름으로 직접 호출 
		System.out.println(Student2.getSerialNum()); 
		System.out.println(studentLee.studentName + " 학번: " + studentLee.studentID);
		
		Student2 studentSon = new Student2();
		studentSon.setStudentName("손흥민");
		//serialNum 값을 가져오기 위해 get() 메서드를 클래스 이름으로 직접 호출 
		System.out.println(Student2.getSerialNum());
		System.out.println(studentSon.studentName + " 학번: " + studentSon.studentID);


	}

}

 

 

StudentTest4.java의 7행과 12행 코드는 serialNum을 직접 참조하지 않고 getSerialNum() 메서드를 호출하여 참조한다. static 메서드 또한 static 변수처럼 인스턴스 참조 변수가 아닌 클래스 이름으로 직접 호출할 수 있다. 

 

클래스 메서드와 인스턴스 변수

 

클래스 메서드 내부에서는 인스턴스 변수를 사용할 수 없다. 아래 코드를 살펴보겠다.

 

public class Student2 {
private static int serialNum = 1000;  //private 변수로 변경 
public int studentID;
String studentName;
int grade;
String address;

...

public static int getSerialNum() {
int i = 10;
return serialNum;
}

...

 

getSerialNum() 메서드는 static 예약어를 붙인 클래스 메서드이다. 이 메서드는 세 종류의 변수를 사용하고 있다. 일단 가장 먼저 선언한 int i를 보자. 이 변수는 메서드 내부에서 선언하였다. 이렇게 메서드 내부에서 선언한 변수를 그 지역에서만 사용한다고 해서 지역 변수(local variable)라고 한다. 지역 변수는 메서드가 호출될 때 메모리에 생성되어 메서드가 끝나면 사라지는 변수이다. 따라서 이 변수는 getSerialNum() 메서드 내부에서만 사용할 수 있다. 마지막 return serialNum; 문장을 보면 serialNum 변수는 static 변수이다. 그러므로 클래스 메서드인 getSerialNum() 메서드 내부에서도 사용할 수 있다.

 

그런데 메서드 내부의 두 번째 줄에 사용한 studentName 변수는 오류가 발생한다. 이 변수는 Student2 클래스의 멤버 변수로, 인스턴스가 생성될 때 만들어지는 인스턴스 변수이기 때문이다. 

 

클래스 메서드와 클래스 변수는 인스턴스가 생성되지 않아도 사용할 수 있다. 실제로 다음 코드로 확인해보자.

 

package staticex;

public class StudentTest5 {

	public static void main(String[] args) {
		System.out.println(Student2.getSerialNum());
		//인스턴스없이 생성가능 

	}

}

 

5행을 보면 클래스 메서드는 Student2.getSerialNum()과 같이 인스턴스가 생성되지 않아도 언제든 호출할 수 있다. 따라서 studentName처럼 인스턴스가 생성되어야 메모리가 할당되는 인스턴스 변수는 클래스 메서드에서 사용할 수 없다. 

 

정리하자면, 클래스 매서드 내부에서 지역 변수와 클래스 변수는 사용할 수 있지만, 인스턴스 변수는 사용할 수 없다. 또한 클래스 메서드에서 인스턴스 변수를 사용할 수는 없지만, 반대로 일반 메서드에서 클래스 변수를 사용하는 것은 전혀 문제가 되지 않는다. 왜냐하면 일반 메서드는 인스턴스가 생성될 때 호출되는 메서드이고, 클래스 변수는 이미 만들어진 변수이기 때문에 일반 메서드애서도 클래스 변수를 호출할 수 있기 때문이다. 

 

 

728x90

'Java' 카테고리의 다른 글

싱글톤 패턴(singleton pattern)  (0) 2023.10.09
변수 유효 범위  (1) 2023.10.09
객체 간 협력  (0) 2023.10.03
this 예약어  (1) 2023.10.03
정보 은닉  (0) 2023.10.03