DEVELOP/CONCEPT

final, static, static final 차이

콘순이 2025. 4. 11. 17:13

1️⃣ final

final은 '최종인', '결정적인'이라는 뜻으로 값이 한 번 저장되면 수정이 불가능하다는 뜻이다.

그래서 흔히 생성자 주입을 받을 때 해당 변수를 final로 선언하여 해당 인스턴스의 변경을 막아준다.

또한, 값을 받아오기 전까지는 어떤 값도 final 변수에 할당이 가능하므로 완전한 상수라고 보긴 어렵다.

 

  1. variable에 사용했을 때
    • 값이 한 번 저장되면 이후, 변수를 수정하지 못하게 한다.
  2. method에 사용했을 때
    • 메서드에 final을 붙이면 override를 제한한다.
  3. class 에 사용했을 때
    • 상속 불가능 클래스가 된다.
class Parent { 
	public final void greet() { 
    	System.out.println("Hello"); 
    } 
} 

class Child extends Parent { 
	@Override
    public void greet() { 
    	// ❌ 컴파일 에러 
    	System.out.println("Hi"); 
    }
}

 

💡final 키워드의 이점?

변경 불가능을 보장해 코드의 안정성과 최적화 가능성을 높인다.
컴파일 시점에 불변임이 명확하므로 컴파일러가 최적화 처리 가능하다.

 

public class PostController {
    private final PostService postService = new PostService();

    public void createPost(final String title) {
        postService.createPost(title);
    }
}

 

❓왜 controller에서 postService를 final로 선언할까?

PostController는 PostService를 항상 같은 객체로 써야하기 때문이다. 만약 final로 선언하지 않으면 누가 다시 객체를 재생성할 가능성이 생기고, 그렇게 되면 시스템 동작에 예측 불가능한 문제가 생길 수 있다. 따라서 final을 사용해 객체 재할당을 금지하고, 의도치 않은 변경을 방지하는 것이다.


❓왜 메서드 인자 title에 final을 붙일까?

메서드 안에서 title을 실수로 바꾸지 않도록 final 키워드를 붙인다. 예를 들어, title = title.trim()처럼 값을 재할당하는 실수를 막을 수 있다. 물론 실제로 메서드 인자는 대부분 그냥 값을 읽기만하고 변경하는 경우는 드물다. 다만, 코드 안정성을 증가시키고 가독성 면에서도 이 값은 바뀌지 않는다는 의도가 명확해진다.

 

2️⃣ static

static은 '정적인', '고정된'이라는 의미로 전역이라고 이해하면 쉽다. 즉, 객체 생성 없이 사용할 수 있는 필드와 메서드를 생성하고자 할 때 활용한다.

예를 들어, Error 메세지를 일관되게 반환하고자 할 경우 

  • return "[Error] 오류 발생" 이런 식으로 하드 코딩하고 싶지 않고
  • 따로 클래스를 만들어서 사용하고 각 클래스마다 ErrorMessage 객체를 생성하고 싶지 않다면

다시 말해, 공용 데이터라면 활용 가능하다.

 

생성된 클래스의 메서드들을 모두 static으로 선언해주면 된다.

단, 메서드를 static으로 선언하면 외부에서 불러오는 변수(class 변수)는 모두 static으로 선언되어야 한다.

public class PostManager {
    private static int totalPosts = 0; // static 변수
    private int id;                    // 인스턴스 변수

    public static void addPost() {
        totalPosts++;        // static이라 사용 가능
        // id++;            // 컴파일 에러, static이 아닌 변수라 사용 불가
    }
}
PostManager.addPost();

 

위와 같이 호출해 줄 수 있다.

또한, 정적 메서드는 객체 참조 없이(객체 생성 없이) 바로 사용할 수 있어

인스턴스 필드나 메서드, this 키워드를 사용할 수 없다.

static은 또한 메서드 영역에 저장되기 때문에 가비지 컬렉터가 작동하지 않아 시스템 종료 시까지 계속 메모리에 남게 된다.

 

  1. 인스턴스 변수 vs static 변수
    • 인스턴스 변수
      • 객체가 생성될 때마다 새로 만들어지는 변수
      • 객체마다 독립적인 값 가짐
      • 객체가 소멸되면 함께 사라짐
      • 클래스 로딩 시에는 존재하지 않음
    • static 변수 (클래스 변수)
      • 클래스에 딱 하나만 존재하는 변수 → 모든 객체가 공유하는 변수
      • static 키워드 사용
      • 클래스 로딩 시 메모리에 올라감
      • 모든 인스턴스가 같은 값을 참조
      • 객체 없이도 클래스명.변수로 접근 가능
      public class Person {
          static int count = 0; // static 변수: 모든 객체가 공유
      
          public Person() {
              count++;
          }
      }
      
      Person p1 = new Person();
      Person p2 = new Person();
      
      System.out.println(Person.count); // 2 (두 객체가 같은 변수 사용)
      
  2. static 메서드
    • 클래스 소속의 메서드로, 객체를 생성하지 않고도 사용할 수 있는 메서드
    • 객체의 상태(멤버 변수)에 의존하지 않고, 오직 입력값으로만 동작하는 경우
public class Person { 
	String name; // 인스턴스 변수 
    
    public Person(String name) { this.name = name; } 
} 


Person p1 = new Person("Alice"); 
Person p2 = new Person("Bob"); 
System.out.println(p1.name); // Alice 
System.out.println(p2.name); // Bob

 

3️⃣ static final

static final = '고정된'+'최종'이라는 뜻으로 상수를 선언하고자 할 때 사용된다.

상수 = 변하지 않는 값

 

final과 달리 애초에 빈값으로 둘 수 없다.

static final을 선언한 순간 값을 할당하여야 오류가 발생하지 않는다.

 

예를 들어 아래와 같이 하드코딩이 아닌 의미 있는 변수를 할당하여 return 값을 날리고 싶을 때 사용해주면 된다.

public class Message {
    private static final String startMessage = "게임을 시작합니다.";
	 
    public static void printStartMessage() {
        System.out.println(startMessage);
    }
}