티스토리 뷰

 

final을 어느 시점에 적절하게 사용할 것인가?

 

 

코드 리뷰를 받던 리뷰어분께 아래와 같은 코멘트를 남겨주셨다. 이에 대해 명확히 답변하기가 어려웠다. 내 기준이 없었기 때문에

이제까지 final을 개념적으로만 알고 있었지, 어느 시점에 적절하게 사용해야 하는지에 대한 그때그때 기분에 따라 적용해 왔었다. 

그래서 이번기회에 다른 개발자들은 어떤 기준으로 final을 적용하고 final 적용 시 어떠한 장단점이 있는지 정리해보고자 한다.

(관련 참고 링크를 공유해 주신 오유근 님, 고민할 수 있는 기회를 주신 박유진 님께 감사의 말씀드립니다.)

 

 

 

 

final의 역할과 적용 예시

  • final 키워드는 다양한 contexts에 사용할 수 있고, 각 contexts에서 다른 역할을 수행한다.

 

1. final class

  • final 선언된 클래스는 하위 클래스에서 extends(확장) 불가
final class SomeClass{
}

 

2. final method

  • final 선언된 메서드는 하부클래스에서 @Override 불가
Class SomeClass{
	public final void tempMethod(){
    }
}

 

 

3. final method parameters 

  • final 선언된 메서드 파라미터는 메서드 내에서 재할당 불가
class SomeClass{
	public void tempMethod(final int age){
    }
}

 

 

4. final fields

  • final 선언된 필드는 생성자(constructor)를 통한 객체 생성 시 설정되며, 이후 재할당 불가
class Student{
	final int age = 42;
}

 

 

5. final local variables

  • final 선언된 지역 변수는 한번 선언된 이후에 재할당 불가
class SomeClass{
	public void tempMethod() {
    	final int finalVariable = 20;
    }
}

 

 

Interesting Point

다음 코드의 결과는?

  1. 컴파일 에러가 발생한다.
  2. `pass'가 출력된다.
 public class SomeClass{
 
    public static void main(String[] args) {
        int condition = 200;
        testMethod(condition);
    }

    static void testMethod(int condition) {
        final String answer;
        if (condition == 200) {
            answer = "pass";
        } else {
            answer = "non-pass";
        }
        System.out.println(answer);
    }
}

 

정답은 2번 `pass`가 출력된다. final 지역 변수의 제약조건은 재할당을 할 수없다는 것인데, 위 코드는 조건문에서 처음으로 answer 변수에 값을 할당했기 때문에 에러가 발생하지 않고, pass 가 출력된다. 에러가 발생하는 경우는 아래 첨부 사진과 같이 final 지역변수에 재할당시 컴파일 에러가 발생한다.

 

 

 

 

 

final 적용에 관한 고찰

  권장 상황
final local variables - 값이 변경되지 않는 상수를 나타낼 때
- 익명 클래스나 람다 표현식에서 로컬 변수를 참조할 때
- 변수의 값이 변경되지 않음을 명확히 표현하고자 할 때
- 코드 블록 내에서 임시 변수를 사용할 때
final fields - 불변객체(domain entity) 일 경우
(dto 와 같은 변경이 필요한 경우는 비권장)
final method parameters - 필요한 경우를 제외하고는 대부분 비권 장
final method & final class - extends(확장)하지 못하게 강제할 경우
- @Override 못하게 강제할 경우

 

0. final 관련 style guide 

google, twitter, Oracle 등 대부분의 style guide에서는 final 사용 관련하여 언급하지 않고 오직 Openjdk에서만 언급하고 있는데,

guide 내용은 아래와 같다. 

Method parameters and local variables should not be declared final unless it improves readability or documents an actual design decision.
메서드 매개변수와 로컬 변수는 가독성을 향상하거나 실제 디자인 결정을 문서화하는 경우를 제외하고 final로 선언해서는 안됩니다.

Fields should be declared final unless there is a compelling reason to make them mutable.
필드는 변경 가능한 필드를 만들어야 하는 강력한 이유가 없는 한 반드시 final로 선언되어야 합니다.

Motivation
Writing out modifiers where they are implicit clutters the code and learning which modifiers are implicit where is easy.
암시적인 경우에만 수식어를 작성하는 것은 코드를 혼란스럽게 만들며 어디에서 어떤 수식어가 암시적인 지 학습하는 것은 쉽습니다.

Although method parameters should typically not be mutated, consistently marking all parameters in every methods as final is an exaggeration.
일반적으로 메서드 매개변수는 일반적으로 변경되지 않지만, 모든 메서드의 모든 매개변수를 final로 지정하는 것은 과장입니다.

Making fields immutable where possible is good programming practice. Refer to Effective Java, Item 15: Minimize Mutability for details.
필드를 변경할 수 없으면 불변으로 만드는 것은 좋은 프로그래밍 실천입니다. 자세한 내용은 Effective Java의 항목 참고

 

위 내용을 정리해 보면

  • Method parameters와 local variablesfinal을 선언하지 않는다.(가독성 향상 등 일부 상황 제외)
  • Fields는 반드시final로 선언되어야 한다.(변경 가능한 fields 필요한 경우 제외)

 

1. final fields에 관한 고찰

필드를 Final 선언 시 Setter를 생성할 수 없다. 오직 생성자를 이용해 처음 인스턴스 생성 시점에 초기화된 필드값은 재할당할 수 없다는 얘기다(필드 변경 불가). 이 경우 생성된 객체를 어느 곳에서든 호출하더라도 일관된 필드 값을 유지하는 불변 객체가 된다. 불변 객체를 통해 멀티 스레드 환경에서 스레드 안정성(Thread Safety)을 보장할 수 있고, 객체 상태가 불변이기에 동기화 또한 필요치 않게 된다.

public class Student {
    private final long id;
    private final String name;
    private final int age;
    private final int grade;

    public Student(long id, String name, int age, int grade) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.grade = grade;
    }

 	// Setter 생성 불가
    
}

 

- 번외 : 회원 id로 조회한 회원의 이름을 변경해야 하는 요구사항이 있다면?

조회한 회원 인스턴스는 불변객체 임으로 이름을 변경할 수 없다. 이를 해결하려면 생성자를 이용해 변경된 회원 이름을 파라미터로 넣어줘 새로운 회원 인스턴스를 생성해야 한다. 

// entity class
public final class Member {
    private final Long id;
    private final String name;
    private final String email;

    public Member(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public Member withName(String newName) {
        return new Member(this.id, newName, this.email);
    }
}

// MemberService class
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public void updateMemberName(Long id, String newName) {
        Member member = memberRepository.findMemberById(id);
        Member updatedMember = member.withName(newName);
        memberRepository.updateMember(updatedMember);
    }
}

 

2. final method parameter에 관한 고찰

처음에는 method의 parameter 에 final을 적용하는 게 좋다고 생각했었다. method body에서 parameter의 값을 변경하는 것은 향후 버그 발생가능성이 높다고 생각했고, 예상했던 로직과는 달리 다른 변수가 발생할 수 있고, 필요시 지역변수로 값을 복사해 로직을 수행하는 게 더 나은 방법이라고 생각했다. 하지만, 대부분의 개발자들은 parameter에 final 사용을 권장하지 않는다. 이유는 다음과 같다.

  • 코드 가독성 저하
  • 유연성 저하(parameter를 재할당할 경우)
    • parameter를 이용해 method body에서 값을 변경하는 것이 불가
  • 일반적으로 parameter를 재할당 하지 않음(암묵적 관행)
  • parameter 보단 local variables에 final 적용 권장
  • Non-primitive type 인 경우 heap 메모리의 주소를 참조하고 있는 Call by Reference 이기에 final 적용이 의미가 없음

 

3. final method 및 final class에 관한 고찰

  • final method와 final class는 명확한 목적을 가지고 적용하기에 extends(확장) 또는 @Override 되지 않을 경우에만 사용

 

 

Effectively final  정리

java8 부터 제공된 기능으로 변수에 final을 명시적으로 선언하지 않고, 람다와 익명클래스에서 사용하면 해당 변수는 final 로(effectively final) 인식한다. 예시 코드는 다음과 같다.

public class test {

    public static void main(String[] args) {
        
        int x = 10;
        String message = "hi";

        Runnable r = () -> {
            System.out.println(x);
            System.out.println(message);
        };

        r.run();
    }
}

 

람다식 이후에 사용한 변수를 재할당할 경우 compile error 가 발생한다.

 

 

참고 링크

 

Best practices for using final keyword in Java | beBit Engineering Blog

Role of final in JavaThe same final keyword can be used in various contexts and play completely d...

www.wantedly.com

 

 

marking parameters and local variables as final

In a few projects at work everything is declared final. Even parameters and local variables. I believ...

dev.to

 

 

Final. A Java Keyword.

Final. Go nuts or Don’t bother?

medium.com

 

 

Understanding Java’s Effectively Final Variables

In the world of Java programming, you might have come across the term “effectively final.” It’s a concept that plays a crucial role in the…

medium.com

 

 

Final vs Effectively Final in Java | Baeldung

Learn the difference between Final and Effectively Final in Java.

www.baeldung.com

 

 

The Java final Keyword – Impact on Performance | Baeldung

Explore if there are any performance benefits from using the final keyword on a variable, method, and class

www.baeldung.com

 

 

Java Programming Guidelines (Sun Java System Application Server 9.1 Performance Tuning Guide)

Java Programming Guidelines This section covers issues related to Java coding and performance. The guidelines outlined are not specific to Application Server, but are general rules that are useful in many situations. For a complete discussion of Java codin

docs.oracle.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함