문제

Java의 다음 인터페이스를 고려하십시오.

public interface I {
    public final String KEY = "a";
}

그리고 다음 수업 :

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY;
    }
}

클래스 A가 와서 인터페이스 I의 최종 상수를 무시할 수있는 이유는 무엇입니까?

스스로 시도하십시오 :

A a = new A();
String s = a.getKey(); // returns "b"!!!
도움이 되었습니까?

해결책

당신이 변수를 섀도우하고 있다는 사실에도 불구하고 당신이 읽을 수있는대로 Java의 최종 필드를 변경할 수 있다는 것을 아는 것은 매우 흥미 롭습니다. 여기:

Java 5- "Final"은 더 이상 최종적이지 않습니다.

노르웨이의 Machina Networks의 Narve Saetre는 어제 저에게 메모를 보냈습니다. 핸들을 최종 배열로 바꿀 수 있다는 것은 유감이라고 언급했습니다. 나는 그를 오해하고 배열을 일정하게 만들 수 없으며 배열의 내용을 보호 할 방법이 없다고 참을성있게 설명하기 시작했습니다. "아니오"라고 그는 말했다.

Narve의 샘플 코드를 시도했는데 믿을 수 없을 정도로 Java 5는 최종 핸들, 원시 필드에 대한 핸들조차 수정할 수있었습니다! 나는 그것이 어느 시점에서 허용되었음을 알았지 만, 허용되지 않았기 때문에 오래된 버전의 Java를 사용하여 일부 테스트를 실행했습니다. 먼저 최종 필드가있는 클래스가 필요합니다.

public class Person {
  private final String name;
  private final int age;
  private final int iq = 110;
  private final Object country = "South Africa";

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

  public String toString() {
    return name + ", " + age + " of IQ=" + iq + " from " + country;
  }
}

JDK 1.1.x

JDK 1.1.x에서는 반사를 사용하여 개인 필드에 액세스 할 수 없었습니다. 그러나 우리는 공공 분야를 가진 다른 사람을 만들고, 그에 대한 수업을 편집하고, 사람 수업을 교환 할 수 있습니다. 우리가 우리가 컴파일 한 것과 다른 클래스와 다른 클래스에 대해 실행중인 경우 런타임에 액세스 점검이 없었습니다. 그러나 클래스 스와핑 또는 반사를 사용하여 런타임에 최종 필드를 다시 반드 할 수 없었습니다.

java.lang.reflect.field의 jdk 1.1.8 javadocs는 다음과 같은 말을했습니다.

  • 이 필드 객체가 Java 언어 액세스 제어를 시행하고 기본 필드가 접근 할 수없는 경우,이 방법은 불법 행위 지출을 던진다.
  • 기본 필드가 최종 인 경우 메소드는 불법 행위 지출을 던집니다.

JDK 1.2.x

JDK 1.2.x에서 이것은 약간 바뀌 었습니다. 이제 SetAccessible (True) 메소드로 개인 필드에 액세스 할 수 있습니다. 필드의 액세스는 이제 런타임에 확인되었으므로 클래스 스왑 트릭을 사용하여 개인 필드에 액세스 할 수 없었습니다. 그러나 우리는 이제 갑자기 최종 필드를 다시 반드 할 수있었습니다! 이 코드를보십시오.

import java.lang.reflect.Field;

public class FinalFieldChange {
  private static void change(Person p, String name, Object value)
      throws NoSuchFieldException, IllegalAccessException {
    Field firstNameField = Person.class.getDeclaredField(name);
    firstNameField.setAccessible(true);
    firstNameField.set(p, value);
  }
  public static void main(String[] args) throws Exception {
    Person heinz = new Person("Heinz Kabutz", 32);
    change(heinz, "name", "Ng Keng Yap");
    change(heinz, "age", new Integer(27));
    change(heinz, "iq", new Integer(150));
    change(heinz, "country", "Malaysia");
    System.out.println(heinz);
  }
}

JDK 1.2.2_014에서 이것을 실행했을 때 다음과 같은 결과를 얻었습니다.

Ng Keng Yap, 27 of IQ=110 from Malaysia    Note, no exceptions, no complaints, and an incorrect IQ result. It seems that if we set a

선언 시간에 프리미티브의 최종 필드는 유형이 원시적이거나 문자열 인 경우 값이 상감됩니다.

JDK 1.3.x 및 1.4.x

JDK 1.3.x에서 Sun은 액세스를 조금 강화하고 반사로 최종 필드를 수정하지 못했습니다. 이것은 또한 JDK 1.4.x의 경우도 마찬가지입니다. 반사를 사용하여 런타임에서 최종 필드를 리딩하기 위해 FinalFieldChange 클래스를 실행하려고하면 다음을 얻을 수 있습니다.

Java 버전 "1.3.1_12": 예외 스레드 "Main"ImpalAccessException : FINSTILDCHANGE.MAIN의 FinalFieldChange.Change (FinalFieldChange.java:8)의 java.lang.reflect.field.set (avative method)에서 FINST (Final)에서 Final입니다. 자바 : 12)

Java 버전 "1.4.2_05"예외 스레드 "Main"EllegalAccessException : Finalfieldchange.change (FinalfieldChange.java:8)에서 java.lang.reflect.field.set (field.java:519)에서 Finalfieldchange.main (Finalfieldchange.main) (Finalfieldchange.java:8)에서 Final IS Final입니다. Finalfieldchange.java:12)

JDK 5.X

이제 우리는 JDK 5.X에 도착합니다. FinalfieldChange 클래스는 JDK 1.2.x에서와 동일한 출력을 갖습니다.

Ng Keng Yap, 27 of IQ=110 from Malaysia    When Narve Saetre mailed me that he managed to change a final field in JDK 5 using

반사, 나는 버그가 JDK에 삐걱 거리는 것을 바라고있었습니다. 그러나 우리는 둘 다, 특히 근본적인 버그가 될 가능성이 없다고 생각했습니다. 검색 후 JSR-133 : Java 메모리 모델 및 스레드 사양을 찾았습니다. 대부분의 사양은 어려운 독서이며, 제 대학의 시대를 상기시켜줍니다 (저는 그렇게 썼습니다 ;-) 그러나 JSR-133은 모든 Java 프로그래머에게 읽어야하는 것이 매우 중요합니다. (행운을 빕니다)

25 페이지의 9 장 최종 필드 시맨틱으로 시작하십시오. 구체적으로, 9.1.1 최종 필드의 구축 후 수정을 읽으십시오. 최종 필드에 대한 업데이트를 허용하는 것이 합리적입니다. 예를 들어, 우리는 JDO에서 필드 필드를 가야한다는 요구 사항을 완화 할 수 있습니다.

섹션 9.1.1을 신중하게 읽으면 건설 과정의 일부로 최종 필드 만 수정해야합니다. 유스 케이스는 객체를 사로화 한 다음 객체를 구성한 후에는 최종 필드를 초기화하기 전에 초기화합니다. 객체를 다른 스레드에 사용할 수있게 한 후에는 반사를 사용하여 최종 필드를 변경해서는 안됩니다. 결과는 예측할 수 없습니다.

최종 필드가 필드 선언에서 컴파일 타임 상수로 초기화되면 최종 필드의 사용이 컴파일 타임 상수와 컴파일 시간에 교체되므로 최종 필드에 대한 변경 사항은 관찰되지 않을 수 있습니다. 이것은 우리의 IQ 필드가 동일하게 유지되는 이유를 설명하지만 국가가 바뀌는 이유를 설명합니다.

이상하게도 JDK 5는 정적 최종 필드를 수정할 수 없다는 점에서 JDK 1.2.x와 약간 다릅니다.

import java.lang.reflect.Field;

public class FinalStaticFieldChange {
  /** Static fields of type String or primitive would get inlined */
  private static final String stringValue = "original value";
  private static final Object objValue = stringValue;

  private static void changeStaticField(String name)
      throws NoSuchFieldException, IllegalAccessException {
    Field statFinField = FinalStaticFieldChange.class.getDeclaredField(name);
    statFinField.setAccessible(true);
    statFinField.set(null, "new Value");
  }

  public static void main(String[] args) throws Exception {
    changeStaticField("stringValue");
    changeStaticField("objValue");
    System.out.println("stringValue = " + stringValue);
    System.out.println("objValue = " + objValue);
    System.out.println();
  }
}

JDK 1.2.x 및 JDK 5.X로 이것을 실행하면 다음과 같은 출력을 얻습니다.

Java 버전 "1.2.2_014": StringValue = 원래 값 objvalue = 새 값

Java 버전 "1.5.0"예외 스레드 "Main"ImpalAccessException : FinalStaticfieldchange.Main (16)의 FinalStaticfieldChange.Changestaticfield (12)의 java.lang.reflect.field.set (field.java:656)에서 필드가 최종입니다.

JDK 5는 JDK 1.2.x와 같습니까?

결론

JDK 1.3.0이 언제 출시되었는지 알고 있습니까? 나는 알아 내기 위해 고군분투했다. 그래서 나는 그것을 다운로드하고 설치했다. readme.txt 파일에는 날짜 2000/06/02 13:10이 있습니다. 그래서 그것은 4 살이 넘습니다 (Goodness Me, 어제처럼 느껴집니다). JDK 1.3.0은 Java (TM) 전문가 뉴스 레터를 작성하기 시작하기 몇 달 전에 발표되었습니다! 나는 Java 개발자가 Pre-JDK1.3.0의 세부 사항을 기억할 수있는 소수의 Java 개발자라고 말하는 것이 안전하다고 생각합니다. 아, 향수는 예전이 아니었다! Java를 처음으로 실행 하고이 오류를 얻은 것을 기억하십니까?

다른 팁

당신은 그것을 숨기고 있습니다. 그것은 "범위"의 기능입니다. 더 작은 범위에있을 때마다 원하는 모든 변수를 재정의 할 수 있으며 외부 범위 변수는 "그림자"됩니다.

그건 그렇고, 당신이 원한다면 다시 범위를 틀 수 있습니다.

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        String KEY = "c";
        return KEY;
    }
}

이제 키는 "C"를 반환합니다.

원본이 다시 읽을 때 빨려 들었 기 때문에 편집되었습니다.

수업은 단순히 변수를 숨기고 덮어 쓰지 않는 것 같습니다.

public class A implements I {
    public String   KEY = "B";

    public static void main(String args[])
    {
        A t = new A();
        System.out.println(t.KEY);
        System.out.println(((I) t).KEY);
    }
}

발견 한대로 "B"와 "A"가 인쇄됩니다. A.key 변수가 최종으로 정의되지 않으므로 할당 할 수도 있습니다.

 A.KEY="C" <-- this compiles.

하지만 -

public class C implements I{

    public static void main (String args[])
    {
        C t = new C();
        c.KEY="V"; <--- compiler error ! can't assign to final

    }
}

이러한 방식으로 상수에 액세스해서는 안되며 대신 정적 참조를 사용하십시오.

I.KEY //returns "a"
B.KEY //returns "b"

설계 고려로

public interface I {
    public final String KEY = "a";
}

정적 메소드는 항상 상위 키를 반환합니다.

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY; // returns "b"
    }

    public static String getParentKey(){
        return KEY; // returns "a"
    }
}

Jom이 눈에 띄는 것처럼. 다시 정의 된 인터페이스 멤버를 사용하는 정적 메소드의 설계는 큰 문제가 될 수 있습니다. 일반적으로 상수의 동일한 이름을 사용하지 마십시오.

정적 필드와 메소드는 클래스/인터페이스에 첨부되어 있습니다 (인터페이스는 구현 해야하는 전적으로 추상적 인 클래스이므로 정적 메소드를 선언 할 수는 없지만).

따라서 공개 정적 (Vartype) (Varname)과의 인터페이스가있는 경우 해당 필드가 해당 인터페이스에 연결됩니다.

해당 인터페이스를 구현하는 클래스 구현이있는 경우 컴파일러 트릭이 varname을 interfaceName.varname으로 변환합니다. 그러나 클래스가 varname을 재정의하면 Varname이라는 새로운 상수가 클래스에 첨부되고 컴파일러는 이제 varname을 NewClass.varname으로 번역하는 것을 알고 있습니다. 메소드에 대해 동일하게 적용됩니다. 새 클래스가 메소드를 다시 정의하지 않으면 (this.) 메소드 이름이 superclass.methodName으로 변환됩니다. 그렇지 않으면 (this.) 메소드 이름은 currentClass.MethodName으로 변환됩니다.

이것이 바로 "x 필드/메소드에 정적으로 액세스해야한다"는 경고를 겪게됩니다. 컴파일러는 트릭을 사용할 수 있지만 가독성 목적으로 더 명확하기 때문에 ClassName.Method/FieldName을 사용하는 것이 선호한다고 말합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top