이번 글에서는 자바로 개발을 하다보면 자주 사용하게 되는 static, final 키워드에 대해 알아보도록 하겠습니다.
🧐 1. static
일반적으로 클래스의 멤버를 다른 클래스 내에서 사용하기 위해서는 가장 먼저 클래스의 객체를 생성해야하는데, 이렇게 객체 안에 있을 때 사용할 수 있는 상태가 되는 멤버를 인스턴스 멤버라하고, Static이 앞에 붙어 있는 멤버를 정적 멤버라고 부릅니다. Static(정적)은 고정된이라는 의미를 가지고 있으며, Static 키워드를 통해 정적 변수와 정적 메서드를 만들 수 있습니다.
정적(Static) 멤버 선언
static int num = 0; //타입 필드 = 초기값
public static void static_method(){} //static 리턴 타입 메소드 {}
Java에서 Static 키워드를 사용한다는 것은 메모리에 한 번 할당되어 프로그램이 종료될 때 해제되는 것을 의미합니다. 이를 정확히 이해하기 위해서는 메모리 영역에 대한 이해가 필요합니다.
일반적으로 우리가 만든 클래스는 Static 영역에 생성되고, new 연산을 통해 생성한 객체는 메모리의 Heap 영역에 생성됩니다. 객체의 생성 시에 할당된 Heap영역의 메모리는 자바의 Garbage Collector(GC)를 통해 수시로 관리를 받습니다.
반면에 Static 키워드를 통해 Static 영역에 할당된 메모리는 모든 객체가 공유하는 메모리라는 장점을 지니지만, Garbage Collector의 관리 영역 밖에 존재하므로 Static을 자주 사용하면 프로그램의 종료시까지 메모리가 할당된 채로 존재하기 때문에 자주 사용하게 되면 시스템의 성능에 좋지 않습니다.
Static의 특징을 정리하면 다음과 같습니다.
1. 메모리에 고정적으로 할당된다.
2. 객체 생성 없이 바로 사용할 수 있다.
3. 프로그램이 시작되면 메모리의 Static 영역에 할당되고, 프로그램이 종료될 때 해제된다.
4. Static 메서드 내에서는 인스턴스 변수를 사용할 수 없다.
위 Static의 특징들을 하나 하나 살펴보겠습니다.
1. 메모리에 고정적으로 할당된다.
Static이 붙지 않은 메서드나 변수의 경우 객체가 생성될 때마다 호출되어 서로 다른 값을 가지고 있을 수 있습니다. 그렇기 때문에 각 객체들에서 공통적으로 하나의 값이 유지되어야 할 경우 static을 유용하게 사용할 수 있습니다.
2. 정적(static) 멤버는 객체 생성 없이 '클래스명.멤버명'만으로 바로 사용이 가능
class A{
static String n; // static 변수
static void print() { //static 메서드
System.out.println("static 메서드입니다.");
}
void print2() { // 인스턴스 메서드
System.out.println("인스턴스 메서드입니다.");
}
}
public class Staitc_Test {
public static void main(String[] args) {
System.out.println(A.n) # 인스턴스를 생성하지 않고 static 변수 바로 호출 가능
A.print(); //인스턴스를 생성하지 않아도 호출이 가능
A a = new A(); //인스턴스 생성
a.print2(); //인스턴스를 생성하여야만 호출이 가능
}
}
3. 프로그램이 시작되면 메모리의 Static 영역에 할당되고, 프로그램이 종료될 때 해제된다.
위에서 언급했던 내용이 다소 겹칠 수 있지만, 중요한 내용이므로 한 번 더 설명하도록 하겠습니다.
프로그램이 시작되어 클래스가 메모리에 올라가게 되면 static이 붙은 변수나 메서드는 클래스와 함께 자동으로 메모리의 static 영역에 생성됩니다. 자동으로 메모리에 올라가기 때문에 객체를 생성할 필요 없이 사용이 가능한 것입니다. Garbage Collector에 의해 관리를 받지 못하기 때문에 static을 과도하게 사용할 경우 메모리에 과부하가 올 수 있습니다. 따라서 무분별하게 static을 남발하는 것은 지양하면서 용도에 맞게 사용해야합니다.
4. Static 메서드 내에서는 인스턴스 변수를 사용할 수 없다.
Static 메서드는 프로그램 실행과 동시에 메모리에 올라가기 때문에 인스턴스 변수는 사용할 수 없습니다. 인스턴스 변수는 객체를 생성해야만 사용이 가능하기 때문에 객체를 생성하기 전에 먼저 메모리에 올라가는 static 메서드에서는 사용할 수 없는 것입니다. 그래서 메서드에 static을 붙여주고 싶다면, 해당 메서드 내부에 인스턴스 변수나 인스턴스 메서드를 사용하는 부분이 있는지 먼저 확인해주어야 합니다.
반대로 인스턴스 메서드에서 static 변수를 사용하는 것은 가능합니다.
** Main 메서드에 Static을 사용하는 이유
static은 java 프로그램이 실행하기 전에 static 함수나 static 변수를 첫 단계로 메모리에 올려 프로그램을 실행시킵니다.
main 메서드는 자바 프로그램이 실행될 때 가장 먼저 실행되어야 하기 때문에 메모리에 미리 올라가 있어야 합니다. 메모리에 올라가있지 않으면, 시작점인 main() 메서드를 호출하려고 하는데 메모리에는 main이 없기 때문에 실행을 할 수가 없습니다. 따라서 main 메서드에 static을 붙여 main 메서드가 미리 메모리에 올라가도록 하는 것입니다.
🧐 2. final
Java 프로그램에서 final은 오직 한 번만 할당할 수 있는 Entity를 정의할 때 사용됩니다. final로 선언된 변수가 할당되면 항상 같은 값을 가지고, final은 "최종인", "결정적인"이라는 뜻으로 값이 한 번 할당되면 수정이 불가능합니다.
그래서 흔히 스프링에서 생성자 주입을 받을 때, 해당 변수를 final로 선언하여 임의로 해당 인스턴스 변수 값의 변경을 막아줍니다.
또한 값을 받아오기 전까지는 어떤 값도 final 변수에 할당이 가능하므로 완전한 상수라고 보긴 어렵습니다.
무슨 의미인지 아래 코드를 통해 이해해보도록 하겠습니다.
public class Shop{
final int closeTime = 21;
final int openTime;
public Shop(int openTime){
this.openTime = openTime;
}
}
final 필드에 값을 저장하는 방법은 다음 두 가지가 있습니다.
하나는 closeTime과 같이 선언과 동시에 값을 주는 방법이 있고
openTime과 같이 생성하고, 객체를 생성할 때 생성자 public Shop에 의해 값을 주는 방법이 있습니다.
모든 가게가 오픈 시간은 자유롭지만 한 번 정한 오픈 시간을 바꿀 수 없고, 21시에는 모두 닫아야 할 때
위와 같이 final 키워드를 이용하면, 오픈 시간은 객체마다 다르게 설정이 가능하지만, 닫는 시간은 고정되도록 코딩이 가능합니다.
final 키워드가 활용되는 경우들에 대해 알아보겠습니다.
final 변수
final이 부여되면 변수는 상수가 되기 때문에 한 번 할당된 후에는 값을 변경할 수 없습니다.
final String GREETING_MSG = "Hello World! Java.";
final 객체 변수인 경우
객체 변수의 경우 필드를 변경할 수 있지만, 객체 변수에 객체를 새로 생성하여 할당할 수 없습니다.
class SampleClass {
int a;
}
public final class FinalSample {
public static void main(String[] args) {
final int RESULT = 10;
RESULT++;
final SampleClass SAMPLE = new SampleClass();
SAMPLE.a = 10;
SAMPLE.a = 15;
SAMPLE = new SampleClass(); // 객체를 새로 생성하여 할당할 수 없다.
}
}
final arguments (메서드 인자)
인자에도 final을 선언할 수 있습니다. 선언된 final 인자는 값을 다시 할당할 수 없습니다.
public final class FinalSample {
public void finalTestMethod(final int a) {
a = 10; // Error: 값을 다시 할당 할 수 없다.
}
}
final 메서드
final 메서드는 상속된 자식 클래스에서 다시 정의(overriding)을 할 수 없다. final 메서드는 주로 구현된 코드의 변경을 막고자할 때 사용합니다.
class FinalMethodTest {
int result = 10;
public final void printResult() {
System.out.println(result);
}
}
public class ExampleCalss extends FinalMethodTest {
@Override
public final void printResult() { // Error: 오버라이딩을 할 수 없다.
}
}
final 클래스
클래스를 final을 부여해 작성하면 그 내용을 수정할 수 있으므로 상속을 할 수 없습니다. 주로 Util 형식의 클래스나, 여러 상수 값을 모아둔 상수 클래스를 final로 선언합니다.
final class ExtendTest {
int a;
}
public class ExampleCalss extends ExtendTest { // Error: ExtendTest를 상속할 수 없다.
public static void main(String[] args) {
}
}
🧐 3. static final
위에서 다룬 static과 final 개념을 활용해서 두 키워드를 함께 활용할 수 있습니다.
"고정된"+"최종"이라는 뜻으로 상수를 선언하고자 할 때 사용됩니다. 선언한 순간 값을 할당하여야 오류가 발생하지 않습니다.
final만으로 충분히 상수가 되지 않느냐? 라는 질문이 나올 수 있지만, 활용에 따라 객체마다 값을 다르게 설정이 가능하기 때문에 final 자체만으로는 완전한 상수를 의미할 수는 없습니다.
static final로 변수를 선언하면, 모든 영역에서 고정된 값으로 사용하는 상수를 의미하고 static final field는 객체마다 저장되지 않고, 클래스에만 포함됩니다. 따라서 값을 변경할 수 없으면서 클래스의 모든 인스턴스에 대해 동일한 값을 설정할 때는 static final을 사용해야 합니다.
static final double PI = 3.141592;
결론
- 모든 곳에서 하나의 값을 일관되게 사용하기 위해 상수를 선언할 때는 static final로 선언하는 것이 관례이다.
- static final로 상수를 선언하면 인스턴스가 만들어질 때마다 새로운 메모리에 초기화하지 않고, 하나의 메모리 공간만을 사용할 수 있어 효율적이다.
- 만약 상수가 각 개체에 대해 다른 값을 가질 수 있는 경우, static으로 선언하지 않는다.
📕 Reference
Java final 키워드 - 상수 선언
final 키워드 Java 프로그램에서 final의 의미는 마지막으로 선언했기 때문에 수정할 수 없다는 의미이다. 즉, final가 부여된 요소는 초기화 이후에 수정할 수 없다. final은 변수, 메서드, 클래스에 부
www.devkuma.com
'Java' 카테고리의 다른 글
| 김영한의 자바 입문 정리 (1) | 2025.05.02 |
|---|---|
| [Java] 변수 Scope는 왜 존재할까? (0) | 2024.01.22 |
| [Java] 람다 표현식(Lambda Expression) (0) | 2024.01.17 |
| [Java] Java Garbage Collection (GC) (0) | 2023.08.27 |
| [Java] JDBC, DBCP, JNDI (0) | 2023.08.25 |