[Java] final, static이 뭘까...

구본탁·2025년 4월 9일
2

Java

목록 보기
1/2
post-thumbnail

🔥 final??

자바에서 final 키워드는 '마지막', '변경 불가'를 의미합니다.
final은 객체지향의 캡슐화와 불변성을 지켜주고, 상속과 오버라이딩을 고정하여 예측 가능한 동작을 유지하도록 돕습니다.
변수, 메서드, 클래스에 따라 미세하게 다를 수 있지만, 크게는 더이상 변경하지는 못하도록 설정하는 것이라고 이해하면 될 것 같습니다.

#️⃣ final 키워드의 3가지

  1. final 변수 (원시 타입, 객체 타입, 클래스 필드, 메서드 인자)
  2. final 메서드
  3. final 클래스

1. final 변수

final 변수는 단 한 번만 값을 할당할 수 있으며, 그 후에는 값을 변경할 수 없습니다. 상수를 정의하거나, 객체의 불변성을 보장하는 데 사용됩니다.

1.1 원시 타입 final 변수

원시 타입 final 변수는 선언 시 또는 생성자에서 초기화해야 하며, 이후에는 값을 변경할 수 없습니다.

final int age = 30;
// age = 31; // 컴파일 에러!
final double PI = 3.14159;

1.2 객체 타입 final 변수

객체 타입 final 변수는 참조하는 객체를 변경할 수 없지만, 객체 내부의 상태는 변경할 수 있습니다. 즉, person 변수가 다른 Person 객체를 참조하도록 할 수는 없지만, 객체의 age 속성은 변경할 수 있습니다.

불변 객체는 객체 타입 final 변수와 함께 사용하면 객체의 불변성을 완벽하게 보장할 수 있습니다. 불변 객체는 생성 후 내부 상태를 변경할 수 없는 객체를 의미합니다.

final String name = "John Doe";
// name = "Jane Doe"; // 컴파일 에러!

final Person person = new Person("Alice", 25);
// person = new Person("Bob", 30); // 컴파일 에러!
person.setAge(26); // final 변수가 참조하는 객체의 상태는 변경 가능합니다.

1.3 클래스 필드 final 변수

클래스 필드에 final을 붙이면 클래스의 모든 인스턴스에서 해당 필드의 값이 동일하게 유지됩니다. 인스턴스 final 변수는 각 인스턴스마다 다른 값을 가질 수 있지만, 한 번 초기화된 후에는 변경할 수 없습니다.

class Circle {
    final double PI = 3.14159; // 인스턴스 상수

    double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getArea() {
        return PI * radius * radius;
    }
}

1.4 메서드 인자 final 변수

메서드 내에서 인자의 값이 변경되지 않음을 보장하며, 코드의 가독성을 높이는 데 도움이 됩니다.

void printName(final String name) {
    // name = "Jane Doe"; // final 인자의 값을 변경할 수 없습니다.
    System.out.println("Name: " + name);
}

2. final 메서드

final 메서드는 하위 클래스에서 오버라이드할 수 없습니다.
메서드의 구현이 변경되는 것을 방지하고, 상속 구조에서 특정 메서드의 동작을 고정하는 데 사용됩니다.

class Animal {
    final void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    // @Override
    // void eat() { // final 메서드를 오버라이드할 수 없습니다.
    //     System.out.println("Dog is eating");
    // }
}

3. final 클래스

final 클래스는 상속할 수 없으며, 클래스의 기능 확장을 제한하고, 클래스의 구현이 변경되는 것을 방지하는 데 사용됩니다. String 클래스가 대표적인 final 클래스입니다.

final class MathUtils {
    // 수학 관련 유틸리티 메서드
}

// class AdvancedMathUtils extends MathUtils { // final 클래스를 상속할 수 없습니다.
// }

🔥 static??

static은 클래스의 멤버(변수, 메서드, 블록)에 적용되어, 해당 멤버가 클래스의 인스턴스가 아닌 클래스 자체에 속하도록 만듭니다.

#️⃣ static 키워드의 3가지

1. static 변수 (클래스 변수)

static 변수는 클래스의 모든 인스턴스에서 공유하는 변수입니다. 클래스가 메모리에 로드될 때 한 번 초기화되며, 모든 인스턴스에서 동일한 값을 갖습니다.

  • 객체의 생성 없이도 클래스 이름으로 직접 접근할 수 있습니다. (예: Counter.count).
  • 프로그램 실행 동안 단 한 번만 초기화됩니다.
class Counter {
    static int count = 0; // 클래스 변수

    public Counter() {
        count++; // 인스턴스가 생성될 때마다 count 증가
    }

    public static int getCount() {
        return count; // static 메서드에서는 static 변수에 접근할 수 있습니다.
    }
}

public class Main {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();

        System.out.println(Counter.getCount()); // 3 (모든 인스턴스가 공유하는 count 값)
    }
}

2. static 메서드 (클래스 메서드)

클래스의 인스턴스 없이 호출할 수 있는 메서드입니다. static 메서드 내부에서는 인스턴스 변수나 인스턴스 메서드에 접근할 수 없습니다.

  • 클래스 이름으로 직접 호출할 수 있습니다 (예: MathUtils.add(5, 3)).
  • 인스턴스 변수에 접근할 수 없으므로, 객체의 상태에 의존하지 않는 기능을 구현하는 데 사용됩니다.
  • 유틸리티 클래스나 헬퍼 클래스에서 주로 사용됩니다.
class MathUtils {
    static int add(int a, int b) {
        return a + b; // static 메서드에서는 static 변수만 사용할 수 있습니다.
    }
}

public class Main {
    public static void main(String[] args) {
        int sum = MathUtils.add(5, 3); // 객체 생성 없이 static 메서드 호출
        System.out.println("Sum: " + sum); // Sum: 8
    }
}

3. static 블록

static 블록은 클래스가 메모리에 로드될 때 한 번 실행되는 코드 블록입니다. static 변수를 초기화하거나, 클래스 로딩 시 필요한 작업을 수행하는 데 사용됩니다.

  • 클래스가 로드될 때 단 한 번만 실행됩니다.
  • 클래스 로딩 시 필요한 초기화 작업을 수행하는 데 유용합니다.
class MyClass {
    static int value;

    static {
        System.out.println("Static block is executed");
        value = 10; // static 변수 초기화
    }

    public MyClass() {
        System.out.println("Constructor is executed");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass(); // "Static block is executed"가 먼저 출력됨
        System.out.println("Value: " + MyClass.value); // Value: 10
    }
}

👻 static과 객체지향 설계

static 키워드는 객체지향 프로그래밍의 기본적인 원칙과 충돌할 수 있기 때문에, 사용이 적합한 경우에만 쓰는 것이 좋습니다.

✅ static 사용이 적합한 경우

  • 객체의 상태에 의존하지 않는 유틸리티 함수를 구현하는 경우
  • 클래스의 모든 인스턴스에서 공유하는 상수를 정의하는 경우
  • 클래스 로딩 시 필요한 초기화 작업을 수행하는 경우

🔥 static과 final을 함께 사용하는 경우

클래스 레벨(static)의 상수를 정의하고(final), 모든 인스턴스가 동일한 값을 공유(static)해야 할 때

public class Constants {
    // 클래스 상수 (static final)
    static final int MAX_USERS = 1000;

    // 인스턴스 상수 (final)
    final String instanceId;

    public Constants(String id) {
        this.instanceId = id; // 생성자에서 초기화
    }
}

📌 메모리 관점에서의 final, static, static final

1. final
메모리 위치는 힙 영역(Heap)입니다.
인스턴스가 생성될 때마다 새로운 메모리가 할당되고, 객체마다 별도로 저장되므로 메모리 효율성이 낮을 수 있습니다.

2. static
메모리 위치는 메서드 영역(Method Area)이고, 클래스 로딩 시 한 번만 저장되니다. 프로그램 종료 시까지 메모리에 유지되며, Garbage Collector의 관리 대상이 아닙니다.

3. static final
메모리 위치는 메서드 영역(Method Area)이고, 클래스 로딩 시 한 번만 저장됩니다. 메모리 효율성과 데이터 일관성을 보장합니다.


🌱 마지막 한마디

final, static, static final..... 어렵지 않다!!

profile
성공의 반대는 실패가 아니라 포기다

1개의 댓글

comment-user-thumbnail
2025년 4월 10일

전 어렵던데... 대단하시네요 ^^ static에 대해 알 수 있어서 좋았습니다 ^^ 감사합니다 😊👍

답글 달기