- 멤버변수로 선언한 것은 인스턴스가 새롭게 생성될 때마다 새롭게 사용을 합니다.
- 하지만 고유한 값(유일한 식별자)여야 하는 멤버변수도 있습니다.
- 예를 들어, 주민번호나 학생번호 이런 것들입니다.
- 주민번호를 나와 같은 번호를 가진 다른 사람이 있으면 안 되겠죠?
- 이때 static이라는 키워드를 붙여 모든 인스턴스가 공통으로 사용하게 처리해줘야 합니다.
- 이번 강좌에서는 static을 이용해서 멤버변수나 메서드가 고유한 값을 갖도록 처리하는 방법을 공부해 보겠습니다.
1. Static 변수
- static 변수란 다른 용어로 '정적 변수'라고도 합니다.
- 다른 멤버변수처럼 클래스 내부에 쓰지만 앞에 static 키워드를 붙입니다.
//일반 멤버변수
(접근제어자) 자료타입 멤버변수명;
//static 멤버변수
(접근제어자) static 자료타입 멤버변수명;
- 클래스 내부에 선언하지만, 다른 멤버 변수처럼 인스턴스가 생성될 때마다 새로 생성되는 변수가 아닙니다.
- static 변수는 프로그램이 실행되어 메모리에 올라갔을 때, 딱 한번 메모리 공간이 할당됩니다.
- 그리고 모든 인스턴스가 그 값을 공유하게 됩니다.
- 이런 이유 때문에 static 변수를 클래스에 기반한 변수라고 해서 '클래스 변수'라고도 합니다.
1) static으로 공유할 때 발생되는 문제
- 그럼 static 키워드로 멤버변수를 처리하면 값을 어떻게 유지하는지 보도록 하겠습니다.
- 학생 클래스를 생성하고 인스턴스들을 만들어 보겠습니다.
- 패키지(ch17_static)과 클래스(Student.java)를 추가해 주세요.
- Student.java에는 main () 함수를 실행시키지 않습니다.
## Student.java
package ch17_static;
public class Student {
public static int serialNum = 1000; //고유한 값으로 유지할 시리얼넘버
public int studentId; //학생ID
public String studentName; //학생이름
//작성하진 않았지만 기본 생성자가 실행됨
//학생이름을 가져오는 메서드(게터)
public String getStudentName() {
return studentName;
}
//학생이름을 변경하는 메서드(세터)
public void setStudentName(String studentName) {
this.studentName = studentName;
}
}
- 멤버변수는 시리얼넘버, 학생ID, 학생이름으로 3개를 선언했습니다.
- 그중 serialNum을 static으로 고정해서 모든 인스턴스가 공유할 수 있게 처리했습니다.
- 생성자는 작성하지 않으면 기본 생성자가 실행됩니다.
- studentName을 이용한 게터/세터를 생성했습니다.
- 직접 작성해도 되고, 이클립스 기능을 이용해도 됩니다.
- 그럼 StudentMain.java에 인스턴스를 생성해 보겠습니다.
- 이때는 main() 함수를 체크해서 생성해 주세요.
## StudentMain.java 코드
package ch17_static;
public class StudentMain {
public static void main(String[] args) {
//학생1 생성
Student st01 = new Student();
st01.setStudentName("김석진"); //이름 변경
System.out.println(st01.getStudentName() + "의 시리얼넘버 : " + st01.serialNum);
st01.serialNum++; //static변수인 serialNum에 1증가
//학생2 생성
Student st02 = new Student();
st02.setStudentName("김태형"); //이름 변경
System.out.println(st02.getStudentName() + "의 시리얼넘버 : " + st02.serialNum);
//학생1을 다시 출력
System.out.println(st01.getStudentName() + "의 시리얼넘버 : " + st01.serialNum);
}
}
- 학생1을 생성 후 이름을 변경하고, serialNum를 출력했습니다.
- 그 후에 serialNum의 값을 1을 증가했습니다.
- 학생2를 생성 후 이름을 변경하고, serialNum를 출력했습니다.
- 그 후에 학생1의 serialNum를 다시 출력해 보겠습니다.
## StudentMain.java 결과
- 학생1의 첫 번째 시리얼 넘버는 1000인데, 마지막은 1001로 변경되어 있습니다.
- 이것은 모든 인스턴스가 serialNum을 공유하기 때문입니다.
- serialNum은 고정돼서 학생 한 명 추가될 때마다 1이 증가되어야 하는 것은 맞지만, 바뀔 때마다 인스턴스의 고유한 값이 변경되면 안 됩니다.
2) 생성자를 통해 자동 변경 처리
- static으로 고정시킨 값은 모든 인스턴스가 공유하기 때문에 바로 사용하면 안 됩니다.
- 그래서 멤버변수를 처음부터 studentId를 따로 선언해 두었습니다.
- serialNum은 매번 1씩 증가하지만 그 값을 studentId에 담아 각각의 인스턴스는 고유한 학생ID를 갖도록 처리하겠습니다.
## Student.java
package ch17_static;
public class Student {
public static int serialNum = 1000;
public int studentId;
public String studentName;
//serialNum을 바꾸고 studentId에 담는 생성자 생성
//생성자가 생성될 때마다 serialNum이 1증가
public Student() {
serialNum++;
studentId = serialNum;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
}
- 추가된 생성자 부분에만 주석을 달았습니다.
- 생성자가 생성될 때마다 자동으로 serialNum이 1 증가됩니다.
- 그리고 증가된 serialNum을 studentId 담아 해당 인스턴스만 사용하도록 처리합니다.
## StudentMain.java 코드
package ch17_static;
public class StudentMain {
public static void main(String[] args) {
//학생1 생성
Student st01 = new Student();
st01.setStudentName("김석진");
System.out.println("현재 serialNum : " + st01.serialNum);
System.out.println(st01.getStudentName() + "의 학생ID : " + st01.studentId);
//학생2 생성
Student st02 = new Student();
st02.setStudentName("김태형");
System.out.println("현재 serialNum : " + st02.serialNum);
System.out.println(st02.getStudentName() + "의 학생ID : " + st02.studentId);
//학생1을 다시 출력
System.out.println("현재 serialNum : " + st01.serialNum);
System.out.println(st01.getStudentName() + "의 학생ID : " + st01.studentId);
}
}
- 이번에는 먼저 serialNum을 출력합니다.
- 그리고 아래는 studentId를 출력해 보겠습니다.
## StudentMain.java 결과
- 그럼 마지막 serialNum은 여전히 바뀌어 있습니다.
- 하지만 studentId에 담은 값은 변경되지 않고 담겨 있는 것을 확인할 수 있습니다.
- 이렇게 고유한 값을 유지할 때 static변수를 사용하면 좋습니다.
3) 클래스 변수
- 위의 코드들이 틀린 건 아니지만, 노란색으로 경고가 뜹니다.
- 왜냐면 인스턴스명을 쓰고 serialNum값을 가져왔기 때문입니다.
- 틀린 건 아니지만, static으로 선언한 클래스 변수는 인스턴스 생성과는 별개이므로 먼저 생성입니다.
- 그래서 클래스 변수라고 부릅니다.
- 클래스명.멤버변수로 교체를 해보겠습니다.
## StudentMain.java 코드
package ch17_static;
public class StudentMain {
public static void main(String[] args) {
//학생1 생성
Student st01 = new Student();
st01.setStudentName("김석진");
System.out.println("현재 serialNum : " + Student.serialNum);
System.out.println(st01.getStudentName() + "의 학생ID : " + st01.studentId);
//학생2 생성
Student st02 = new Student();
st02.setStudentName("김태형");
System.out.println("현재 serialNum : " + Student.serialNum);
System.out.println(st02.getStudentName() + "의 학생ID : " + st02.studentId);
//학생1을 다시 출력
System.out.println("현재 serialNum : " + Student.serialNum);
System.out.println(st01.getStudentName() + "의 학생ID : " + st01.studentId);
}
}
- 그럼 경고도 사라진 것을 확인할 수 있습니다.
- 결과는 같으니까 따로 보진 않겠습니다.
- static 변수, 정적변수, 클래스 변수 모두 자바에서는 static을 앞에 붙인 변수라고 생각하면 됩니다.
2. Static 메서드
- 일반 멤버변수를 위한 메서드가 존재하듯이, static 변수를 위한 메서드도 있습니다.
- 이런 메서드들을 static 메서드 혹은 클래스 메서드라고 부릅니다.
1) 클래스 메서드 처리
- 그럼 위에서 한 예제에서 serialNum을 외부클래스에서 직접 참조하지 못하도록 private로 처리하겠습니다.
- private로 처리된 멤버변수의 값을 가져오거나 변경하려면 게터와 세터를 만들어줘야 합니다.
## Student.java
package ch17_static;
public class Student {
private static int serialNum = 1000;
public int studentId;
public String studentName;
//serialNum을 바꾸고 studentId에 담는 생성자 생성
//생성자가 생성될 때마다 serialNum이 1증가
public Student() {
serialNum++;
studentId = serialNum;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public static int getSerialNum() {
return serialNum;
}
public static void setSerialNum(int serialNum) {
Student.serialNum = serialNum;
}
}
- 변경한 것만 체크해서 보겠습니다.
- 이클립스에서 마우스 우클릭 - source를 통해 게터세터를 만들어도 static은 자동으로 붙습니다.
- 그럼 serialNum은 private 해졌기 때문에 외부 클래스인 StudentMain에서 가져다 쓸 수 없기 때문에 벌써 옆의 파일이 에러가 난 것을 확인할 수 있습니다.
- 그래서 게터와 세터를 처리한 것이므로, 게터를 통해서 가져오면 됩니다.
## StudentMain.java 코드
package ch17_static;
public class StudentMain {
public static void main(String[] args) {
//학생1 생성
Student st01 = new Student();
st01.setStudentName("김석진");
System.out.println("현재 serialNum : " + Student.getSerialNum());
System.out.println(st01.getStudentName() + "의 학생ID : " + st01.studentId);
//학생2 생성
Student st02 = new Student();
st02.setStudentName("김태형");
System.out.println("현재 serialNum : " + Student.getSerialNum());
System.out.println(st02.getStudentName() + "의 학생ID : " + st02.studentId);
//학생1을 다시 출력
System.out.println("현재 serialNum : " + Student.getSerialNum());
System.out.println(st01.getStudentName() + "의 학생ID : " + st01.studentId);
}
}
- 변경한 것만 체크해서 보겠습니다.
- 그럼 에러가 사라지는 것을 확인할 수 있습니다.
2) 클래스 메서드 내부에 멤버변수 사용X
- 클래스 메서드, 즉 static으로 만든 메서드 내부에는 다른 멤버변수를 가져올 수 없습니다.
- 이번에는 그냥 제가 이미지로만 보여드리겠습니다.
- 다른 멤버변수를 클래스 메서드에 담았더니, 에러가 뜹니다.
- 클래스 변수나 메서드들은 인스턴스가 생성되기 전에 생성이 되기 때문에 클래스 내부에서 선언한 멤버변수를 읽을 수 없습니다.
- 그래서 변수의 유효 범위를 아는 것도 중요한 공부가 됩니다.
3) 변수의 유효범위
- 지금까지 변수는 세 가지 종류를 배웠습니다.
- 지역변수(로컬변수) : 함수나 메서드 안에서만 사용할 수 있는 변수
- 멤버변수(인스턴스변수) : 클래스 안에 작성해서 인스턴스가 사용하는 변수
- static변수(클래스변수) : 여러 인스턴스가 공통적으로 사용할 수 있는 변수
## 변수에 따른 용도
변수 유형 | 선언 위치 | 사용 범위 | 메모리 | 생성과 소멸 |
지역 변수 (로컬 변수) |
함수 내부에 선언 | 함수 내부에서만 사용 | 스택 | 함수가 호출될 때 생성되고 함수가 끝나면 소멸 |
멤버 변수 (인스턴스 변수) |
클래스 멤버 변수로 선언 | 클래스 내부에서 사용하고 private이 아니면 참조 변수로 다른 클래스에서 사용가능 | 힙 | 인스턴스가 생성될 때 힙에 생성되고, 가비지 컬렉터가 메모리를 수거할 때 소멸함 |
static 변수 (클래스 변수) |
static 예약어를 사용해여 클래스 내부에 선언 | 클래스 내부에서 사용하고 private이 아이면 클래스이름으로 다른 클래스에서 사용가능 | 데이터영역 | 프로그램이 처음 시작할 때 상수와 함께 데이터 영역에 생성되고 프로그램이 끝나고 메모리를 해제할 때 소멸됨 |
3. Static 응용 - 싱글톤 패턴
- 프로그램을 구현하다 보면 인스턴스가 여러 개가 필요한 경우도 있고, 한 번만 필요한 경우도 있습니다.
- 객체지향 프로그램에서 인스턴스를 한 개만 생성하는 디자인 패턴을 싱글톤 패턴(singleton pattern)이라고 합니다.
- 디자인패턴이란 프로그램을 어떻게 구현해야 더 유연하고 재활용성이 높은 프로그램을 만들 수 있는지 정리한 내용입니다.
- 우리가 지금 사용할 싱글톤 패턴은 static을 응용하여 프로그램 전반에서 사용하는 인스턴스를 하나만 구현하는 방식입니다.
- 실무에서 많이 사용하는 패턴이므로 잘 익히 두면 좋습니다.
- 그럼 회사가 있고, 직원들의 데이터를 관리하는 프로그램을 만든다고 보도록 하겠습니다.
- 직원 관련 클래스를 만들면 당연히 인스턴스를 여러 개로 만들어야 되겠죠?
- 그럼 직원 클래스는 싱글톤으로 만들지는 않습니다.
- 하지만 회사는 어떤가요? 하나만 생성해야 되겠죠?
- 그래서 회사 객체를 싱글톤 패턴으로 제작해 보겠습니다.
- 물론 여러 자회사를 가지고 있을 수도 있겠지만, 하나의 회사를 갖고 있다는 가정으로 보도록 하겠습니다.
1) 생성자를 private로 제작
- 보통 생성자는 public으로 많이 만듭니다.
- 다른 파일에서 해당 클래스를 인스턴스로 많이 생성할 것이기 때문입니다.
- 하지만 싱글톤은 인스턴스를 하나만 생성한다고 했죠?
- 그렇기 때문에 private으로 제작하겠습니다.
- 그리고 보통 클래스에서는 기본생성자가 들어가고, 그것을 사용해도 돼서 안 만드는 경우가 있습니다.
- 하지만 싱글톤 패턴에서는 생성자를 반드시 생성해줘야 합니다.
- 그럼 예제를 통해서 보도록 하겠습니다.
- 패키지(ch17_static)에 클래스(Company.java)를 main() 없이 생성해 줍니다.
## Company.java
package ch17_static;
public class Company {
//생성자 생성 - 반드시 생성
private Company() { }
}
2) 클래스 내부에 static으로 유일한 인스턴스 생성
- private 하게 생성자를 생성했으므로 외부에 인스턴스를 생성할 수 없습니다.
- 그래서 Company클래스 내부에 하나의 인스턴스를 생성합니다.
- 이 인스턴스가 프로그램 전체에서 사용할 유일한 인스턴스가 될 겁니다.
- 인스턴스 역시 private으로 선언해서, 외부에서 사용하지 못하게 접근 제한을 해줘야 합니다.
- 잘못해서 외부에서 인스턴스를 생성해서 오류가 발생되는 것을 방지해야 합니다.
## Company.java
package ch17_static;
public class Company {
//유일한 인스턴스 생성 - 앞에 private static 붙여주기
private static Company company = new Company();
//생성자 생성 - 반드시 생성
private Company() { }
}
3) 외부 참조 public 메서드 생성
- 인스턴스를 하나를 만들지만, 인스턴스를 외부에서도 사용할 수 있게 해줘야 합니다.
- 그래서 public을 통한 메서드로 인스턴스를 얻을 수 있도록 게터로 처리해 줍니다.
- 역시 이 게터도 static으로 선언해줘야 합니다.
- 왜냐하면 메서드는 인스턴스 생성과는 상관없이 호출되어야 하기 때문입니다.
## Company.java
package ch17_static;
public class Company {
//유일한 인스턴스 생성 - 앞에 private static 붙여주기
private static Company company = new Company();
//생성자 생성 - 반드시 생성
private Company() { }
//public 게터 메서드 생성
public static Company getCompany() {
if(company == null) { //혹시 인스턴스를 생성하지 않았다면
company = new Company(); //다시 인스턴스를 생성
}
return company; //해당 메서드는 유일한 인스턴스를 반환
}
}
4) 실제로 구현하는 클래스 파일 생성
- 싱글톤 패턴으로 만든 인스턴스를 불러올 수 있는 파일을 만들겠습니다.
- CompanyMain.java로 main() 함수를 체크해서 생성해 줍니다.
## CompanyMain.java
package ch17_static;
public class CompanyMain {
public static void main(String[] args) {
//Company company00 = new Company(); //인스턴스를 또 생성할 수 없음 - 에러
//인스턴스를 각각의 변수에 담기
Company company01 = Company.getCompany();
Company company02 = Company.getCompany();
//하지만 인스턴스는 하나라 같은 주소를 참조하는 것
System.out.println(company01 == company02); //true
}
}
- 첫 번째 줄은 에러라 앞에 주석을 달았습니다.
- 싱글톤 패턴으로 만들었기 때문에 new 키워드를 사용해 새로운 인스턴스를 만들 수 없습니다.
- 메서드를 통해 인스턴스를 각각의 변수에 담을 수는 있습니다.
- 하지만 비교해 보면 같은 주소를 참조하기 때문에 true의 결과를 받습니다.
'컴퓨터언어 > JAVA(자바)' 카테고리의 다른 글
[JAVA강좌] 27강 향상된 for문과 배열 (0) | 2024.01.26 |
---|---|
[JAVA강좌] 26강 자바 배열 (0) | 2024.01.26 |
[JAVA강좌] 24강 여러 클래스 생성 후 객체 협력 (0) | 2024.01.17 |
[JAVA강좌] 23강 접근제어자와 정보은닉 (0) | 2024.01.17 |
[JAVA강좌] 22강 메서드와 게터세터 (0) | 2024.01.17 |