메소드 매개 변수 및 로컬 변수에 final을 언제 사용해야합니까?


171

가능한 한 많이 사용하도록 제안 하는 몇 가지 참조 ( :)를 찾았 final으며 그것이 얼마나 중요한지 궁금합니다. 이것은 주로 최종 메소드 나 클래스가 아니라 메소드 매개 변수 및 로컬 변수의 컨텍스트에 있습니다. 상수의 경우 분명히 의미가 있습니다.

한편으로, 컴파일러는 최적화를 수행 할 수 있으며 프로그래머의 의도를보다 명확하게합니다. 반면에 자세한 정보를 추가하면 최적화가 쉽지 않을 수 있습니다.

내가 기억하려고 노력해야합니까?


다음은 관련 게시물입니다 : stackoverflow.com/questions/137868/…
mattlant


1
나는 이것을 읽기 전에 매개 변수의 수정 자로 final을 사용할 수 있다는 것을 몰랐기 때문에 upvoting하고 있습니다. 감사!
Kip

답변:


178

집착 :

  • 최종 필드-필드를 최종으로 표시하면 구성이 끝날 때 필드를 강제 설정하여 해당 필드 참조를 변경할 수 없습니다. 이렇게하면 필드를 안전하게 게시 할 수 있으며 나중에 읽을 때 동기화 할 필요가 없습니다. (객체 참조의 경우 필드 참조 만 변경할 수 없습니다. 객체 참조가 참조하는 항목은 계속 변경 될 수 있으며 불변성에 영향을줍니다.)
  • 최종 정적 필드-정적 최종 필드를 사용했던 많은 경우에 지금 열거 형을 사용하지만.

신중하게 사용하십시오.

  • 최종 수업-프레임 워크 / API 디자인은 내가 생각하는 유일한 경우입니다.
  • 최종 방법-기본적으로 최종 클래스와 동일합니다. 미친 것과 같은 템플릿 방법 패턴을 사용하고 물건을 최종으로 표시하는 경우 상속에 너무 의존하고 위임에 충분하지 않을 수 있습니다.

항문 느낌이 없으면 무시하십시오.

  • 메소드 매개 변수 및 로컬 변수-게으르고 코드가 복잡해지기 때문에 거의하지 않습니다. 수정하지 않을 마킹 매개 변수와 로컬 변수가 "보다 옳다"는 것을 완전히 인정합니다. 이것이 기본값이기를 바랍니다. 그러나 코드가 아니며 최종 코드로 이해하기가 더 어렵다는 것을 알았습니다. 다른 사람의 코드를 사용하는 경우 코드를 꺼내지 않지만 새 코드를 작성하는 경우 입력하지 않을 것입니다. 예외는 최종적으로 무언가를 표시하여 액세스 할 수있는 경우입니다. 익명의 내부 클래스 내에서.

  • 편집 : @ adam-gent 가 언급 한 것처럼 최종 로컬 변수가 실제로 매우 유용한 유스 케이스 는 값이 if/ else분기 의 var에 할당되는 경우 입니다.


8
Joshua Bloch는 상속을 위해 설계된 것이 아니라면 모든 클래스가 최종 클래스로 정의되어야한다고 주장합니다. 나는 그에게 동의한다. 인터페이스를 구현하는 모든 클래스에 final을 추가합니다 (단위 테스트를 만들 수 있도록). 또한 재정의되지 않는 모든 보호 / 클래스 메소드를 최종으로 표시하십시오.
rmaruszewski

61
Josh Bloch (그리고 상당한 금액)와 관련하여 모든 경우에, 나는 일반적인 경우에 동의하지 않습니다. API를 빌드하는 경우 반드시 잠그십시오. 자신의 코드 안에있는 Bui는 나중에 분해해야하는 벽을 세우는 것은 시간 낭비입니다.
Alex Miller

9
특별히 시간이 전혀 걸리지 않기 때문에 "시간 낭비"가 아닙니다. 응용 프로그램에서는 일반적으로 final기본적으로 거의 모든 클래스 를 만듭니다 . 그러나 최신 Java IDE (예 : IDEA)를 사용하지 않으면 이점을 느끼지 못할 수 있습니다.
Rogério

9
IDEA는 수백 가지의 코드 검사를 제공하며, 그 중 일부는 final클래스 / 방법 에서 사용되지 않거나 불필요한 코드를 감지 할 수 있습니다 . 예를 들어, 최종 메소드가 확인 된 예외를 처리한다고 선언했지만 실제로는이를 절대로 throw하지 않으면 IDEA에서이를 알려주며 해당 throws절 에서 예외를 제거 할 수 있습니다 . 때로는 사용하지 않는 전체 메소드를 찾을 수도 있는데,이를 대체 할 수 없을 때 감지 할 수 있습니다.
Rogério

8
@rmaruszewski 클래스를 final로 표시하면 대부분의 조롱 프레임 워크가 조롱 할 수 없으므로 코드를 테스트하기가 더 어려워집니다. 수업이 연장되지 않는 것이 매우 중요한 경우에만 수업을 마무리합니다.
Mike Rylander

44

내가 기억하기 위해 노력해야 할 일입니까?

아니오, Eclipse를 사용하는 경우 저장 조치를 구성하여 이러한 최종 수정자를 자동으로 추가 할 수 있습니다. 그러면 적은 노력으로 혜택을 얻을 수 있습니다.


3
저장 조치에 대한 유용한 팁은 그것에 대해 몰랐습니다.
sanity

1
필자는 final 이 발생하거나 발생하지 않을 수있는 최적화가 아닌 실수로 잘못된 변수에 실수로 코드를 할당하여 버그로부터 코드를 안전하게 만드는 이점을 고려하고 있습니다 .
피터 힐튼

4
이것이 정말로 당신에게 문제입니까? 실제로 얼마나 자주 버그가 발생 했습니까?
Alex Miller

1
Eclipse에서 유용한 팁은 +1입니다. 나는 버그를 피하기 위해 가능한 한 final을 사용해야한다고 생각합니다.
emeraldhieu

IntelliJ 에서도이 작업을 수행 할 수 있습니까?
Koray Tugay

15

"최종"의 개발 시간 이점은 최소한 런타임 혜택만큼 중요합니다. 그것은 미래의 편집자들에게 당신의 의도에 관한 코드를 알려줍니다.

클래스를 "최종"으로 표시하면 클래스를 디자인하거나 구현하는 동안 확장을 정상적으로 처리하기 위해 노력하지 않았 음을 나타냅니다. 독자가 클래스를 변경하고 "최종"수정자를 제거하려는 경우 독자적인 위험 부담이 있습니다. 수업이 확장을 잘 처리하도록하는 것은 그들에게 달려 있습니다.

변수 "final"을 표시하고 생성자에서 변수를 지정하면 종속성 주입에 유용합니다. 변수의 "협업 자"특성을 나타냅니다.

메소드를 "final"로 표시하는 것은 추상 클래스에서 유용합니다. 확장 점이 어디에 있는지 명확하게 묘사합니다.


11

finalJava를보다 표현 기반으로 만들기 위해 항상 사용 합니다. Java의 조건 ( if,else,switch)은 표현식 기반이 아니므로 특히 함수형 프로그래밍 (ML, Scala 또는 Lisp)에 익숙한 경우 항상 싫어했습니다.

따라서 조건을 사용할 때 항상 (IMHO) 최종 변수를 사용해야합니다.

예를 들어 보겠습니다.

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

이제 다른 case명령문을 추가 하고 설정하지 않으면 name컴파일러가 실패합니다. 모든 경우에 변수를 설정하지 않으면 컴파일러가 실패합니다. 이를 통해 Java를 Lisp와 매우 유사하게 만들 수 있습니다.let 표현식과 하게 만들 수 있으며 (어휘 범위 변수 때문에) 코드가 크게 들여 쓰기되지 않도록합니다.

그리고 @Recurse가 지적했듯이 (그러나 분명히 -1 me) String name final컴파일러 오류를 얻지 않고 위의 작업을 수행 할 수는 있지만 (전혀 말할 수는 없었습니다) 스위치 후에 컴파일러 오류를 쉽게 설정할 수 있습니다. break을 사용하지 않고 표현 의미론을 버리거나 오류를 유발할 수없는 것을 잊어 버리는 문장 (@Recurse의 말에도 불구하고) final:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

버그 설정 이름으로 인해 ( break다른 버그를 잊어 버리는 것 외에도 ) 우연히 이것을 할 수 있습니다.

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

최종 변수는 어떤 이름이어야하는지에 대한 단일 평가를 강제합니다. 반환 값이있는 함수가 항상 예외를 무시하고 값을 반환해야하는 방식과 유사하게 이름 스위치 블록은 이름을 확인해야하므로 해당 스위치 블록에 바인딩되어 코드 덩어리를 쉽게 리팩토링 할 수 있습니다 (예 : Eclipe refactor : extract 메소드) .

위의 OCaml :

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

match ... with ...함수 즉, 표현과 같은 평가합니다. switch 문처럼 보이는지 확인하십시오.

다음은 스킴 (라켓 또는 치킨)의 예입니다.

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
Java 컴파일러가 이미 "이름이 초기화되지 않은 이름"오류를 발생시키고 최종 결과 유무에 관계없이 컴파일을 거부한다는 점을 제외하고.
같이 Recurse

4
예. 그러나 최종 결과가 없으면 언제든지 재설정 할 수 있습니다. 실제로 이것은 개발자가 a else if (...)로 바뀌어 if(...)변수를 재설정하는 경우 발생합니다. 나는 최종 변수로 결코 일어나지 않았 음을 보여주었습니다. 기본적으로 final변수를 한 번만 할당하도록 강요합니다 ... so : P
Adam Gent

9

final문제의 방법이 몇 페이지 길이의 이해할 수없는 혼란 일 때 리팩토링 보조 도구로 유용한 메소드 매개 변수 및 지역 표시를 발견했습니다 . 소량final자유롭게 , 컴파일러 (또는 IDE)에서 발생하는 "최종 변수에 할당 할 수 없음"오류가 무엇인지 확인하고, "데이타"라는 변수가 여러 개의 (오래된) 주석이 맹세하더라도 왜 끝나는지를 발견 할 수 있습니다. 일어나지 않습니다.

그런 다음 재사용 된 변수를 사용 지점에 더 가깝게 선언 된 새 변수로 대체하여 일부 오류를 수정할 수 있습니다. 그런 다음 괄호 범위를 지정하여 방법의 전체 부분을 감쌀 수 있으며 갑자기 "추출 방법"에서 IDE 키를 한 번 누르면 괴물이 더 이해하기 쉬워집니다.

만약 당신의 방법이 아직 유지 불가능한 난파선 이 아니라면 , 사람들이 그 난파선으로 변하는 것을 막기 위해 물건을 최종적으로 만드는 것이 가치가있을 것입니다. 그러나 그것이 짧은 방법이라면 (유지 불가능한 참조), 당신은 많은 정보를 추가 할 위험이 있습니다. 특히 Java 함수 시그니처는 인수 당 6 개를 더 추가하지 않고도 80 자로 들어갈 수있을만큼 어렵습니다!


1
지난 10 년 동안 화면 해상도가 약간 변경되어 오래 전에 80 자 제한을 포기했지만 마지막으로 매우 유효합니다. 스크롤하지 않고도 화면에 300 개의 문자 줄을 쉽게 넣을 수 있습니다. 그럼에도 불구하고 가독성은 물론 final모든 매개 변수가 없으면 더 좋습니다 .
brimborium

8

글쎄, 이것은 모두 당신의 스타일에 달려 있습니다 ... 변수를 수정하지 않을 때 최종 결과를 보는 것을 좋아한다면 그것을 사용하십시오. 보고 싶지 않다면 ... 그대로 두십시오.

나는 개인적으로 가능한 한 작은 표현을 좋아하므로 실제로 필요하지 않은 추가 키워드를 사용하지 않는 경향이 있습니다.

나는 동적 언어를 선호하므로, 자세한 표현을 피하고 싶어하는 것은 놀라운 일이 아닙니다.

그래서, 나는 당신이 기울고있는 방향을 고르고 그냥 따라 가십시오 (어쨌든 일관되게 노력하십시오).


부수적으로, 나는 그러한 패턴을 사용하고 사용하지 않는 프로젝트를 연구했으며 버그 나 오류의 양에는 차이가 없었습니다 ... 버그 수 또는 기타 사항을 개선하지만 다시 한 번 스타일입니다. 수정하지 않으려는 의도를 표현하고 싶다면 계속 사용하십시오.


6

실수로 매개 변수 값을 변경하지 않고 미묘한 버그를 유발하는 것이 매개 변수에 유용합니다. 이 권장 사항을 무시하지만 약 4 시간을 소비 한 후 사용합니다. 끔찍한 방법 (수백 줄의 코드와 여러 개의 for, 중첩 된 if 및 모든 종류의 나쁜 습관)을 사용하는 것이 좋습니다.

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

물론 완벽한 세상에서는 이런 일은 일어나지 않을 것입니다.하지만 .. 때로는 다른 코드를 지원해야합니다. :(


2
이 방법은 결승전보다 더 심각한 문제가 있습니다. 불가능하지는 않지만, 이러한 종류의 오류가 발생하기 위해 메소드가 복잡해 져야 할 이유가 있기는 매우 드문 일입니다. 변수 이름에 대한 작은 생각은 이와 같은 사고로 먼 길을 갈 것입니다.
ykaganovich

2
단일 메소드에 "수백 줄의 코드"가있는 경우 여러 개의 작은 메소드로 나눌 수 있습니다.
Steve Kuo 2013

5

누군가 1 년 후에 코드를 읽어야하는 응용 프로그램을 작성하는 경우 항상 수정해서는 안되는 final 변수를 사용하십시오. 이렇게하면 코드가 "자체 문서화"가되고 다른 상수가 로컬 상수를 로컬 임시 변수로 사용하는 것과 같은 어리석은 일을 할 가능성이 줄어 듭니다.

버리기 코드를 작성한다면 모든 상수를 식별하고 최종적으로 신경 쓰지 마십시오.


4

최대한 많이 final을 사용하겠습니다. 실수로 필드를 변경하면 플래그가 지정됩니다. 또한 Method 매개 변수를 final로 설정했습니다. 그렇게하면 값으로 Java 패스를 잊어 버리는 매개 변수를 '설정'하려고 할 때 인수 한 코드에서 몇 가지 버그가 발생했습니다.


2

이것이 명백한 지 여부는 확실하지 않지만 메소드 매개 변수를 final로 만드는 것은 메소드 본문에만 영향을 미칩니다. 메소드의 의도에 대한 흥미로운 정보를 호출자에게 전달 하지 않습니다 . 전달되는 객체는 메소드 내에서 여전히 변경 될 수 있으며 (최종은 const가 아닙니다) 변수의 범위는 메소드 내에 있습니다.

정확한 질문에 대답하기 위해 코드에서 필요하지 않은 경우 (예 : 변수가 내부 클래스에서 참조되는 경우) 인스턴스 또는 로컬 변수 (메소드 매개 변수 포함)를 최종적으로 만들거나 실제로 복잡한 논리를 명확히하지 않아도됩니다.

예를 들어 변수가 논리적으로 일정하면 변수를 최종으로 만들 것입니다.


2

변수에는 여러 가지 용도가 있습니다 final. 여기 몇 가지가 있습니다

최종 상수

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

그런 다음 코드의 다른 부분에 사용하거나 다른 클래스에서 액세스 할 수 있습니다. 그러면 값을 변경하면 하나씩 변경할 필요가 없습니다.

최종 변수

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

이 클래스에서는 environmentKey 매개 변수에 접 두부를 추가하는 범위가 지정된 최종 변수를 빌드합니다. 이 경우, final 변수는 실행 범위 내에서만 final이며 메소드의 각 실행마다 다릅니다. 분석법을 입력 할 때마다 최종 내용이 재구성됩니다. 생성 되 자마자 메소드 실행 범위 중에는 변경할 수 없습니다. 이를 통해 메소드 지속 기간 동안 메소드에서 변수를 수정할 수 있습니다. 아래를보십시오 :

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

최종 상수

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

이것들은 정말 긴 줄의 코드가있을 때 특히 유용하며 컴파일러 오류를 생성하므로 누군가 실수로 변경해서는 안되는 변수를 변경할 때 논리 / 비즈니스 오류가 발생하지 않습니다.

최종 컬렉션

컬렉션에 대해 이야기 할 때와 다른 경우, 컬렉션을 수정할 수없는 것으로 설정해야합니다.

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

그렇지 않으면 수정할 수없는 것으로 설정하지 않은 경우 :

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

최종 수업최종 방법 은 각각 확장하거나 덮어 쓸 수 없습니다.

편집 : 캡슐화와 관련된 최종 클래스 문제를 해결하려면 :

수업을 마무리하는 두 가지 방법이 있습니다. 첫 번째는 클래스 선언에서 final 키워드를 사용하는 것입니다.

public final class SomeClass {
  //  . . . Class contents
}

클래스를 final로 만드는 두 번째 방법은 모든 생성자를 private으로 선언하는 것입니다.

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

마지막으로 표시하면이 테스트 클래스를 살펴 보는 것이 실제로 최종적인 것으로 밝혀지면 문제가 해결됩니다. 언뜻보기에 공개적으로 보입니다.

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

불행히도 클래스의 유일한 생성자는 private이므로이 클래스를 확장 할 수 없습니다. Test 클래스의 경우 클래스가 최종적인 이유는 없습니다. Test 클래스는 암시 적 최종 클래스가 어떻게 문제를 일으킬 수 있는지에 대한 좋은 예입니다.

따라서 암시 적으로 클래스를 생성자 전용으로 만들어서 클래스를 마지막으로 만들 때는이를 마지막으로 표시해야합니다.


1

언급 한 바와 같이 다소 상충되지만 암시 적 사용보다 명시적인 사용을 선호합니다. 이렇게하면 미래의 코드 관리자가 자신 만이라도 모호성을 제거 할 수 있습니다.


1

내부 (익명) 클래스가 있고 메소드가 포함 메소드의 변수에 액세스해야하는 경우 해당 변수를 final로 설정해야합니다.

그 외에는 당신이 말한 것이 맞습니다.


이제 java 8은 효과적인 최종 변수로 유연성을 제공합니다.
Ravindra babu

0

final변수를 다음과 같이 만들려면 변수에 키워드를 사용하십시오.immutable

변수를 최종 변수로 선언함으로써 개발자는 다중 스레드 환경에서 변수의 가능한 수정 문제를 배제 할 수 있습니다.

Java 8 릴리스에서는 " effectively final variable" 라는 개념이 하나 더 있습니다. 최종 변수가 아닌 변수는 최종 변수로 분류 될 수 있습니다.

람다 식에서 참조되는 지역 변수는 최종적이거나 효과적으로 최종적이어야합니다

로컬 블록에서 초기화 후 변수가 수정되지 않으면 변수는 유효 최종 으로 간주됩니다 . 즉, 익명 클래스 나 람다 식 내에서 final 키워드없이 로컬 변수를 사용할 수 있습니다 (단, 최종 변수 여야 함).

Java 7까지는 익명 클래스 내에서 최종이 아닌 로컬 변수를 사용할 수 없지만 Java 8부터는

기사를보십시오


-1

우선, final 키워드는 변수 상수를 만드는 데 사용됩니다. 상수는 변경되지 않음을 의미합니다. 예를 들면 다음과 같습니다.

final int CM_PER_INCH = 2.54;

인치당 센티미터가 변하지 않기 때문에 변수 final을 선언합니다.

최종 값을 무시하려고하면 변수가 먼저 선언 된 것입니다. 예를 들면 다음과 같습니다.

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

다음과 같은 컴파일 오류가 있습니다.

local variable is accessed from inner class, must be declared final

변수를 final로 선언 할 수 없거나 final로 선언하지 않으려면 다음을 시도하십시오.

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

인쇄됩니다 :

Hello World!
A String
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.