본문 바로가기
프로그래밍/Java

[Java] final 키워드에 대해서 알아보자

by 사바라다 2021. 3. 3.

안녕하세요. 오늘은 여러분들과 java의 final 키워드에 대해서 알아보려고 합니다.

final

final 키워드는 변수(variable), 메서드(method), 또는 클래스(class)에 사용될 수 있습니다. 이 final 키워드는 어떤 곳에 사용되냐에 따라 다른 의미를 가집니다. 하지만 final 키워드를 붙이면 무언가를 제한한다는 의미를 가지는 것은 공통적이 성격입니다.

변수(variable)

변수에 final을 붙여보겠습니다. 변수에 final을 붙이면 이 변수는 수정할 수 없다는 의미를 가집니다. 수정될 수 없기 때문에 초기화 값은 필수적입니다. 만약에 객체안의 변수라면 생성자, static 블럭을 통한 초기화까지는 허용합니다.

수정 할 수 없다는 범위는 그 변수의 값에 한정합니다. 즉, 다른 객체를 참조하거나 할 때 참조하는 객체의 내부의 값은 변경할 수 있다라는 의미입니다. 코드를 한번 보고 이해해보도록 합시다. 코드를 보시면 value라는 기본형 변수와 person 이라는 참조형 변수를 하나씩 선언하고 값을 변경해보는 예제입니다. 결과적으로 기본형 변수는 값이 변경하지 못하고 참조형 변수는 그 객체 내부의 값은 변경할 수 있지만 가리키는 객체를 변경하지 못한다는 사실을 알 수 있습니다.

public class Service {

  public void variableFinal() {

    final int value = 2;
    final Person person = new Person("사바라다", 29);

    System.out.println("value = " + value);
    System.out.println("person_1 = " + person);

    // value = 2; // 컴파일 에러
    person.setAge(10);
    person.setName("사바라");

    System.out.println("person_2 = " + person);
    // person = new Person("염광호", 29); // 컴파일 에러
  }
}

class Person {

  private String name;

  private int age;

  //.. get, set, toString 메서드 존재하나 길이상 생략
}
value = 2
person_1 = Person{name='사바라다', age=29}
person_2 = Person{name='사바라', age=10}

이미지로 나타내면 아래와 같습니다. 메서드에 있는 변수들은 stack 메모리에 저장됩니다. 이 변수들은 각각 값을 가지는데 기본형 변수(primitive)는 내부에 값을 가지고 참조형 변수는 객체의 주소를 가집니다. 따라서 참조형 변수는 객체에 있는 값을 알 수 있는 것이죠. final 키워드를 변수에 사용하면 이 영역에만 변경할 수 없다는 영향을 미칩니다. 그렇기때문에 기본형 변수라면 값을 변경하지 못하고 참조형 변수라면 가리키는 객체를 변경하지 못하는 것입니다. 후자의 경우 현재 가리키고 있는 객체의 내부의 값은 final의 영향 밖에 있기 때문에 변경이 가능합니다.

위 예제는 final을 메서드 내부 변수에 선언했기 때문에 stack 메모리를 기점으로 하지만 만약 객체 내부 변수를 final로 둔다면 이미지는 person 메모리 내부에서 제한하는 것으로 달라집니다. 그리고 배열 역시 객체이기 때문에 내부의 값을 변경하는것은 final이 제한하지 못합니다.

변수를 final로 선언하면 초기화 전에 사용한다면 컴파일 에러가 발생합니다. 각 변수 타입별 일반적인 초기화 방법은 아래와 같습니다.

  • 메서드 내부 변수
    • 선언시 초기화
    • 사용하기 전 초기화
public void variableFinal() {

  final int value = 2;
  final int value_2;

  System.out.println("value = " + value);

  value_2 = 3;
  System.out.println("person_2 = " + value_2);
}

 

  • 객체 멤버 변수
    • 선언시 초기화
    • 생성자를 이용한 초기화
    • 초기화 block을 이용한 초기화

 

public void variableFinal() {
  Person person = new Person("사바라다");
  System.out.println("person = " + person);
}

class Person {

  private final String name;

  private final int age;
  {
    age = 10;
  }

  private final String email = "koangho93@naver.com";

  public Person(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", email='" + email + '\'' +
        '}';
  }
}

 

  • static 변수
    • 선언시 초기화
    • static 초기화 block을 이용한 초기화
class Person {

  private static final String DEFAULT_NAME = "염광호";

  private static final int DEFAULT_AGE;
  static {
    DEFAULT_AGE = 10;
  }

  ... person 객체 내부 후략

메서드(method)

메서드에 final을 붙이면 override를 제한하게 됩니다. 클래스를 상속하게되면 우리는 해당 클래스의 protected, public의 접근 제어자(access-modifier)를 가진 메서드를 상속해서 재구현을 할 수 있습니다. 즉, 동일한 메서드지만 다르게 동작하게 만들 수 있다는 것입니다. 아래 예제를 보겠습니다. 아래를 보시면 Person 클래스에 speech라는 메서드가 있습니다. 본인을 소개하는 메서드입니다. 그리고 이를 상속받은 Korean이라는 클래스가 있습니다. 해당 클래스는 speech에 나이까지 함께 소개하고 있습니다. 사실 Person 클래스를 만들때 자기소개하는 speech 메서드는 모든 나라언어에서 공통으로 사용하고 싶을 수 있습니다. Korean 클래스 처럼 각 나라별로 바꿔버리는것이 문제가 될 수 있습니다. 이렇게 상속 받은 클래스에서 해당 메서드를 수정해서 사용하지 못하도록 할 수 있는것이 메서드에 final을 붙이는 것입니다.

class Person {

  private final String name;

  private final int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public void speech() {
    System.out.println("나는 " + name + " 입니다.");
  }
}

class Korean extends Person {

  public Korean(String name, int age) {
    super(name, age);
  }

  @Override
  public void speech() {
    System.out.println("나는 " + name + " 이며, " + age + " 입니다.");
  }
}

클래스(Class)

final 키워드를 클래스에 붙이면 상속 불가능 클래스가 됩니다. 즉, 다른 클래스에서 상속하여 재정의를 할 수 없는 것입니다. 대표적인 클래스로 Integer와 같은 랩퍼(Wrapper) 클래스가 있습니다. 클래스 설계시 재정의 여부를 생각해서 재정의 불가능하게 사용하고 싶다면 final로 등록하는게 추후 유지보수차원에서 좋습니다.

public final class Integer extends Number implements Comparable<Integer> {

}

마무리

오늘은 이렇게 final 키워드에 대해서 알아보았습니다.

사실 final 키워드는 추후 유지보수를 생각해야 잘 사용하게 되는 키워드입니다. 왜냐하면 해당 클래스가 설계대로 돌아가느냐 마느냐는 기능이 잘 돌아가느냐보다는 덜 중요하기 때문입니다. 하지만 제가 생각하는 좋은 개발자는 당장의 직면한 문제도 문제지만 추후에 내 손을 벗어나서 누군가가 인수인계를 받더라도 설계한 대로 따를 수 있도록 하는 개발자라고 생각합니다. 이런 측면에서 볼 때 final의 제한조건은 좋은 개발자가 되기위한 조건중에 하나가 아닐까 합니다.

여기서 마무리 하겠습니다.

감사합니다.

참조

geeksforgeeks_final-keyword-java

geeksforgeeks_access-and-non-access-modifiers-in-java

댓글