Java 프로그래밍에서final 키워드의 작용을 깊이 있게 해석하다

10517 단어 Javafinal
final class
클래스가finalclass로 정의되면, 이 클래스는 다른 클래스에 계승될 수 없고, extends 이후에 사용할 수 없음을 나타냅니다.그렇지 않으면 컴파일하는 동안 오류가 발생할 수 있습니다.

package com.iderzheng.finalkeyword;
 
public final class FinalClass {
}
 
// Error: cannot inherit from final
class PackageClass extends FinalClass {
}
자바 지원은class를final로 정의하는데 대상을 대상으로 프로그래밍하는 기본 원칙에 위배되는 것 같지만 다른 한편, 폐쇄된 클래스는 이 클래스의 모든 방법이 고정불변하고 하위 클래스의 덮어쓰기 방법이 동적으로 불러올 필요가 없다는 것을 보장한다.이것은 컴파일러가 최적화를 할 때 더 많은 가능성을 제공한다. 가장 좋은 예는 String이다. 이것은 바로final 클래스이다. 자바 컴파일러는 문자열 상량(더블 인용부호에 포함된 내용)을 String 대상으로 직접 바꾸고 연산자 +의 조작을 새로운 상량으로 직접 최적화할 수 있다. 왜냐하면final 수식은 하위 클래스가 결합 조작에 대해 다른 값을 되돌려 주지 않기 때문이다.
모든 다른 클래스에 대한 정의인 맨 윗부분 클래스(전역 또는 패키지 보기), 플러그인 클래스(내부 클래스 또는 정적 플러그인 클래스)는final로 수식할 수 있습니다.그러나 일반적으로final은 전역(public)으로 정의된 클래스를 수식하는 데 많이 사용된다. 비전역 클래스에 대해 액세스 수식자가 그들을 제한하고 가시성을 제한하기 때문에 이런 클래스를 계승하는 것은 매우 어렵다. 더 이상final 제한을 하지 않아도 된다.
또 언급할 것은 익명류 (Anonymous Class) 는 똑같이 계승될 수 없다고 하지만 컴파일러에 의해 final로 제한되지 않았다는 것이다.

import java.lang.reflect.Modifier;
 
public class Main {
 
  public static void main(String[] args) {
    Runnable anonymous = new Runnable() {
      @Override
      public void run() {
      }
    };
 
    System.out.println(Modifier.isFinal(anonymous.getClass().getModifiers()));
  }
}
출력:

 false
final Method
계승 관념과 밀접한 관계를 가진 것은 다태적(Polymorphism)인데 그 중에서 덮어쓰기(Overriding)와 숨기기(Hiding)의 개념 차이와 관련이 있다(편의를 위해 다음과 같이 이 두 개념에 대해'다시 쓰기'라고 통일한다).그러나 C++의 방법 정의와 달리virtual 키워드를 추가하면 하위 클래스가 같은 방법으로 서명하는 방법이 덮어쓰느냐 숨기느냐에 영향을 미친다. 자바에서 하위 클래스는 같은 방법으로 서명하고 상위 클래스를 다시 쓴다. 클래스 방법(정적 방법)에 대해서는 숨기고 대상 방법(비정적 방법)에는 덮어쓰기만 한다.Java는 대상을 통해 클래스 방법에 직접 접근할 수 있기 때문에 Java는 같은 클래스에서 클래스 방법과 대상 방법에 같은 서명을 할 수 없습니다.
final류는 전체 클래스가 계승될 수 없도록 한정하고 이 클래스의 모든 방법이 이불류에 덮여 숨길 수 없다는 것을 나타낸다.클래스가final에 의해 수식되지 않을 때, 여전히 일부 방법에 대해final을 사용하여 수식하여 이런 방법들이 다시 쓰여지는 것을 방지할 수 있다.
마찬가지로 이런 디자인은 대상을 향한 다태성을 파괴하지만final 방법은 그 집행의 확정성을 확보하고 방법 호출의 안정성을 확보할 수 있다.일부 프레임워크 디자인에서 추상적인 유형의 이미 실현된 방법들이final로 제한되는 것을 자주 볼 수 있다. 프레임워크에서 일부 구동 코드는 이런 방법에 의존하여 이미 정해진 목표를 실현하기 때문에 하위 클래스가 그것을 덮어쓰는 것을 원하지 않는다.
다음 예는 final 수식이 서로 다른 유형의 방법에서 작용하는 역할을 보여 준다.

package com.iderzheng.other;
 
public class FinalMethods {
  public static void publicStaticMethod() {
 
  }
 
  public final void publicFinalMethod() {
  }
 
  public static final void publicStaticFinalMethod() {
  }
 
  protected final void protectedFinalMethod() {
  }
 
  protected static final void protectedStaticFinalMethod() {
  }
 
  final void finalMethod() {
  }
 
  static final void staticFinalMethod() {
  }
 
  private static final void privateStaticFinalMethod() {
  }
 
  private final void privateFinalMethod() {
  }
}
package com.iderzheng.finalkeyword;
 
import com.iderzheng.other.FinalMethods;
 
public class Methods extends FinalMethods {
 
  public static void publicStaticMethod() {
  }
 
  // Error: cannot override
  public final void publicFinalMethod() {
  }
 
  // Error: cannot override
  public static final void publicStaticFinalMethod() {
  }
 
  // Error: cannot override
  protected final void protectedFinalMethod() {
  }
 
  // Error: cannot override
  protected static final void protectedStaticFinalMethod() {
  }
 
  final void finalMethod() {
  }
 
  static final void staticFinalMethod() {
  }
 
  private static final void privateStaticFinalMethod() {
  }
 
  private final void privateFinalMethod() {
  }
}
우선 위의 예에서 FinalMethods와 Methods는 서로 다른 패키지(package)에 정의되어 있음을 주의하십시오.첫 번째publicStaticMethod에 대해 하위 클래스는 상위 클래스의 정적 방법을 다시 쓰는 데 성공했지만 정적 방법이기 때문에 발생하는 것은 사실'숨김'이다.구체적인 표현은 호출 방법입니다.publicStaticMethod()는 Methods 클래스의 구현을 실행하고 FinalMethods를 호출합니다.publicStaticMethod () 를 실행할 때 다중 로드 하위 클래스의 구현이 발생하지 않고 FinalMethods의 구현을 직접 사용합니다.그래서 하위 클래스로 접근할 때 상위 클래스와 같은 방법으로 서명하는 방법의 가시성을 숨깁니다.
전역 방법publicFinalMethod는final 수식 방법에서 설명한 것처럼 하위 클래스 정의와 같은 방법으로 덮어쓰는 것을 금지하고 컴파일할 때 이상을 던집니다.그러나 하위 클래스에 정의된 방법의 이름은 같지만 매개 변수가 있습니다. 예를 들어publicFinalMethod(Stringx)는 동기화된 방법으로 서명할 수 있기 때문입니다.
Intellij에서 IDE는 public Static Final Method에 대해 "static"method declared "final"이라는 경고를 표시합니다.이것은 여분의 것으로 보이지만, 실례에서 알 수 있듯이final 역시 하위 클래스의 정의와 같은 정적 방법으로 그것을 숨기는 것을 금지한다.실제 개발에서 하위 클래스와 상위 클래스가 같은 정적 방법을 정의하는 행위는 매우 추천하지 않는다. 왜냐하면 숨겨진 방법은 개발자가 서로 다른 클래스 이름 제한을 사용하면 서로 다른 효과가 있기 때문에 오류를 가져오기 쉽기 때문이다.그리고 클래스 내부에서는 클래스 이름 제한을 사용하지 않고 정적 방법을 직접 호출할 수 있습니다. 개발자가 다시 계승할 때 숨겨진 존재를 눈치채지 못할 수도 있습니다. 기본적으로 부류의 방법을 사용할 때 예상치 못한 결과를 발견할 수 있습니다.그래서 정적 방법에 대해 기본적으로final이고 숨기지 말아야 하기 때문에 IDE는 불필요한 수식이라고 생각한다.
부류에서protected수식과public수식의 방법은 자류에 대해 모두 볼 수 있기 때문에final수식protected방법의 상황은public방법과 같다.실제 개발에서 보호된 정적 방법을 정의하는 경우는 드물다. 왜냐하면 이런 방법은 실용성이 너무 낮기 때문이다.
상위 패키지 방법에 대해 서로 다른 패키지 아래에 있는 하위 클래스는 보이지 않습니다.private 방법은 상위 클래스만 접근할 수 있도록 사용자 정의되었습니다.그래서 컴파일러는 하위 클래스가 같은 방법을 정의할 수 있도록 허용한다.그러나 이것은 덮어쓰거나 숨기지 않는다. 왜냐하면 부류는 수식자를 통해 이러한 방법을 숨겼기 때문이다. 부류의 재쓰기가 아니라.물론 하위 클래스와 상위 클래스가 같은 패키지에 있다면 이전의public,protected와 마찬가지입니다.
final 메서드가 효율적인 이유:
final 방법은 컴파일하는 과정에서 내장 메커니즘을 이용하여 inline 최적화를 한다.인라인 최적화란 컴파일할 때 함수 코드를 직접 호출하는 것이지 실행할 때 함수를 호출하는 것이 아니다.인라인은 컴파일할 때 마지막으로 어떤 함수를 사용해야 하는지 알아야 한다. 분명히final이 아니면 안 된다.비final 방법은 하위 클래스에서 다시 쓸 수 있습니다. 다태적인 상황이 발생할 수 있기 때문에 컴파일러는 컴파일러 단계에서 장래 호출 방법의 대상의 진정한 유형을 확정할 수 없고 도대체 어떤 방법을 호출할지 확정할 수 없습니다.
final Variable
간단하게 말하면 자바에 있는final 변수는 한 번만 초기화되어야 하며, 그 다음에 이 변수는 이 값과 귀속됩니다.그러나 이 값이 반드시 변수가 정의될 때 즉시 초기화되는 것은 아니다. 자바도 조건 문장을 통해final 변수에 다른 결과를 주는 것을 지원하지만, 어쨌든 이 변수는 한 번만 변경할 수 있다.
그러나 자바의final 변수는 절대적인 상수가 아니다. 자바의 대상 변수는 인용 값이기 때문에final은 이 인용이 바뀔 수 없고 대상의 내용은 여전히 수정할 수 있음을 나타낸다.C/C++ 포인터를 비교하면 type * const variable이 아니라 type * const variable와 같습니다.
Java의 변수는 로컬 변수(Local Variable)와 클래스 구성원 변수(Class Field)로 나눌 수 있습니다.다음은 코드로 그것들의 초기화 상황을 각각 소개한다.
Local Variable
국부 변수는 주로 방법에 정의된 변수를 가리키며, 방법이 나오면 접근할 수 없는 변수가 사라진다.그중에는 함수 매개 변수로 나눌 수 있는 특수한 상황이 있다.이런 경우, 함수가 호출될 때 전송된 매개 변수와 초기화됩니다.
다른 로컬 변수의 경우 메서드에 정의되어 있으며 값은 조건부 초기화됩니다.

public String method(final boolean finalParam) {
  // Error: final parameter finalParam may not be assigned
  // finalParam = true;
 
  final Object finalLocal = finalParam ? new Object() : null;
 
  final int finalVar;
  if (finalLocal != null) {
    finalVar = 21;
  } else {
    finalVar = 7;
  }
 
  // Error: variable finalVar might already have been assigned
  // finalVar = 80;
 
  final String finalRet;
  switch (finalVar) {
    case 21:
      finalRet = "me";
      break;
    case 7:
      finalRet = "she";
      break;
    default:
      finalRet = null;
  }
 
  return finalRet;
}
상술한 예에서 알 수 있듯이final에 수식된 함수 매개 변수는 새로운 값을 부여할 수 없지만 다른final의 국부 변수는 조건 문장에 값을 부여할 수 있다.이렇게 하면final에도 일정한 유연성을 제공한다.
물론 조건 문장의 모든 조건에final 국부 변수에 대한 값을 포함해야 합니다. 그렇지 않으면 변수가 초기화되지 않은 오류를 얻을 수 있습니다

public String method(final Object finalParam) {
  final int finalVar;
  if (finalParam != null) {
    finalVar = 21;
  }
 
  final String finalRet;
 
  // Error: variable finalVar might not have been initialized
  switch (finalVar) {
    case 21:
      finalRet = "me";
      break;
    case 7:
      finalRet = "she";
      break;
  }
 
  // Error: variable finalRet might not have been initialized
  return finalRet;
}
이론적으로 국부 변수는final로 정의될 필요가 없기 때문에 합리적으로 설계하는 방법은 국부 변수를 잘 유지할 수 있을 것이다.다만 Java 메서드에서 익명 함수를 사용하여 패키지를 닫을 때 Java에서 참조할 로컬 변수는 final로 정의되어야 합니다.

public Runnable method(String string) {
  int integer = 12;
  return new Runnable() {
    @Override
    public void run() {
      // ERROR: needs to be declared final
      System.out.println(string);
      // ERROR: needs to be declared final
      System.out.println(integer);
    }
  };
}
Class Field
클래스 구성원 변수도 사실 두 가지로 나눌 수 있는데 그것이 바로 정태와 비정태이다.정적 클래스 구성원 변수는 클래스와 관련이 있기 때문에 정의할 때 직접 초기화하는 것 외에 static block에 넣을 수 있고 후자를 사용하면 더 복잡한 문장을 실행할 수 있다.

package com.iderzheng.finalkeyword;
 
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
 
public class StaticFinalFields {
  static final int STATIC_FINAL_INIT_INLINE = 7;
  static final Set<Integer> STATIC_FINAL_INIT_STATIC_BLOCK;
 
  /** Static Block **/
  static {
    if (System.currentTimeMillis() % 2 == 0) {
      STATIC_FINAL_INIT_STATIC_BLOCK = new HashSet<>();
    } else {
      STATIC_FINAL_INIT_STATIC_BLOCK = new LinkedHashSet<>();
    }
    STATIC_FINAL_INIT_STATIC_BLOCK.add(7);
    STATIC_FINAL_INIT_STATIC_BLOCK.add(21);
  }
}
자바에도 비정적 블록은 비정적 구성원 변수를 초기화할 수 있지만 이 변수에 대해서는 구조 함수(constructor)에 넣어 초기화할 때가 더 많다.물론 모든final 변수가 구조 함수에서 한 번 초기화되었음을 보증해야 한다.this () 를 통해 다른 구조 함수를 호출했다면, 이final 변수는 더 이상 이 구조 함수에 값을 부여할 수 없다.

package com.iderzheng.finalkeyword;
 
public class FinalFields {
 
  final long FINAL_INIT_INLINE = System.currentTimeMillis();
  final long FINAL_INIT_BLOCK;
  final long FINAL_INIT_CONSTRUCTOR;
 
  /** Initial Block **/
  {
    FINAL_INIT_BLOCK = System.nanoTime();
  }
 
  FinalFields() {
    this(217);
  }
 
  FinalFields(boolean bool) {
    FINAL_INIT_CONSTRUCTOR = 721;
  }
 
  FinalFields(long init) {
    FINAL_INIT_CONSTRUCTOR = init;
  }
}

final이 클래스 (Class) 와 방법 (Method) 을 수식할 때 대상을 대상으로 하는 계승성에 영향을 주고 계승성이 없으면 클래스가 부류에 대한 코드 의존이 없기 때문에 유지보수할 때 코드를 수정하면 클래스의 실현을 파괴할 수 있는지를 고려하지 않아도 더욱 편리하다.그러나 변수(Variable)에 사용될 때 자바는 변수 값이 수정되지 않고 보증 클래스의 구성원도 수정할 수 없다면 전체 변수는 상량으로 사용할 수 있어 다중 루틴 프로그래밍에 매우 유리하다.그래서final은 코드 유지보수에 매우 좋은 역할을 한다.

좋은 웹페이지 즐겨찾기