Java: "실효"private 수정자

10953 단어 Javaprivate
자바 프로그래밍에서private 키워드를 사용하여 어떤 구성원을 수식합니다. 이 구성원이 있는 클래스와 이 클래스만 사용할 수 있고 다른 클래스는 이private 구성원에 접근할 수 없습니다.
위에서private 수식자의 기본 기능을 묘사하였는데, 오늘은private 기능이 효력을 상실한 상황을 연구하고자 합니다.
Java 내부 클래스
자바에서 많은 사람들이 내부 클래스를 사용한 적이 있다고 믿는다. 자바는 한 클래스에서 다른 클래스를 정의할 수 있다. 클래스 안의 클래스는 내부 클래스이고 플러그인 클래스라고도 부른다.간단한 내부 유형의 실현은 다음과 같다.

class OuterClass {
  class InnerClass{
  }
}
오늘의 문제는 자바 내부류와 관련이 있는데 본고의 연구와 관련된 내부류 지식만 언급하고 자바 내부류의 후속적인 글은 소개하겠습니다.
첫 번째 실효?
우리가 프로그래밍에서 자주 사용하는 장면은 내부 클래스에서 외부 클래스에 접근하는private 구성원 변수나 방법이다. 이것은 가능하다.아래의 코드와 같이 실현됩니다.

public class OuterClass {
 private String language = "en";
 private String region = "US";
 
 
 public class InnerClass {
   public void printOuterClassPrivateFields() {
     String fields = "language=" + language + ";region=" + region;
     System.out.println(fields);
   }
 }
 
 public static void main(String[] args) {
   OuterClass outer = new OuterClass();
   OuterClass.InnerClass inner = outer.new InnerClass();
   inner.printOuterClassPrivateFields();
 }
}
이것은 왜일까요? 프라이빗 장식이 아닌 구성원은 구성원이 말한 클래스에만 접근할 수 있습니까?프라이빗이 정말 효력을 잃었나요?
컴파일러가 장난을 치고 있습니까?
우리는javap 명령을 사용하여 생성된class 파일 두 개를 보십시오
OuterClass의 역컴파일 결과

15:30 $ javap -c OuterClass
Compiled from "OuterClass.java"
public class OuterClass extends java.lang.Object{
public OuterClass();
 Code:
  0: aload_0
  1: invokespecial  #11; //Method java/lang/Object."<init>":()V
  4: aload_0
  5: ldc #13; //String en
  7: putfield #15; //Field language:Ljava/lang/String;
  10: aload_0
  11: ldc #17; //String US
  13: putfield #19; //Field region:Ljava/lang/String;
  16: return

public static void main(java.lang.String[]);
 Code:
  0: new #1; //class OuterClass
  3: dup
  4: invokespecial  #27; //Method "<init>":()V
  7: astore_1
  8: new #28; //class OuterClass$InnerClass
  11: dup
  12: aload_1
  13: dup
  14: invokevirtual  #30; //Method java/lang/Object.getClass:()Ljava/lang/Class;
  17: pop
  18: invokespecial  #34; //Method OuterClass$InnerClass."<init>":(LOuterClass;)V
  21: astore_2
  22: aload_2
  23: invokevirtual  #37; //Method OuterClass$InnerClass.printOuterClassPrivateFields:()V
  26: return

static java.lang.String access$0(OuterClass);
 Code:
  0: aload_0
  1: getfield #15; //Field language:Ljava/lang/String;
  4: areturn

static java.lang.String access$1(OuterClass);
 Code:
  0: aload_0
  1: getfield #19; //Field region:Ljava/lang/String;
  4: areturn

}

어?아니오, OuterClass에서 우리는 이 두 가지 방법을 정의하지 않았습니다.

static java.lang.String access$0(OuterClass);
 Code:
  0: aload_0
  1: getfield #15; //Field language:Ljava/lang/String;
  4: areturn

static java.lang.String access$1(OuterClass);
 Code:
  0: aload_0
  1: getfield #19; //Field region:Ljava/lang/String;
  4: areturn

}

주어진 주석을 보면access$0은outerClass의language 속성을 되돌려줍니다.access$1은 outerClass의 지역 속성을 되돌려줍니다.그리고 이 두 가지 방법은 모두 OuterClass의 실례를 매개 변수로 받아들인다.이 두 가지 방법은 왜 생성되었을까, 어떤 작용이 있을까?우리는 내부류의 반번역 결과를 보면 알 수 있다.
OuterClass$InnerClass의 역컴파일 결과

15:37 $ javap -c OuterClass\$InnerClass
Compiled from "OuterClass.java"
public class OuterClass$InnerClass extends java.lang.Object{
final OuterClass this$0;

public OuterClass$InnerClass(OuterClass);
 Code:
  0: aload_0
  1: aload_1
  2: putfield #10; //Field this$0:LOuterClass;
  5: aload_0
  6: invokespecial  #12; //Method java/lang/Object."<init>":()V
  9: return

public void printOuterClassPrivateFields();
 Code:
  0: new #20; //class java/lang/StringBuilder
  3: dup
  4: ldc #22; //String language=
  6: invokespecial  #24; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  9: aload_0
  10: getfield #10; //Field this$0:LOuterClass;
  13: invokestatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
  16: invokevirtual  #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  19: ldc #37; //String ;region=
  21: invokevirtual  #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  24: aload_0
  25: getfield #10; //Field this$0:LOuterClass;
  28: invokestatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
  31: invokevirtual  #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  34: invokevirtual  #42; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  37: astore_1
  38: getstatic  #46; //Field java/lang/System.out:Ljava/io/PrintStream;
  41: aload_1
  42: invokevirtual  #52; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
  45: return
}

다음 코드는access$0의 코드를 호출합니다. OuterClass의language 개인 속성을 얻기 위해서입니다.
13:   invokestatic#27;//Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
다음 코드는access$1의 코드를 호출했습니다. OutherClass의region 개인 속성을 얻기 위해서입니다.
28:   invokestatic#39;//Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
주의: 내부 클래스 구조를 할 때 외부 클래스의 인용을 전달하고 내부 클래스의 속성으로 하기 때문에 내부 클래스는 외부 클래스의 인용을 가진다.
this$0은 내부 클래스가 가지고 있는 외부 클래스 인용입니다. 구조 방법을 통해 인용을 전달하고 값을 부여합니다.

final OuterClass this$0;

public OuterClass$InnerClass(OuterClass);
 Code:
  0: aload_0
  1: aload_1
  2: putfield #10; //Field this$0:LOuterClass;
  5: aload_0
  6: invokespecial  #12; //Method java/lang/Object."<init>":()V
  9: return

작은 매듭
이 부분은private가 효력을 상실한 것처럼 보이지만 실제로는 효력을 상실하지 않습니다. 내부 클래스가 외부 클래스의 개인 속성을 호출할 때, 그 진정한 실행은 컴파일러가 생성한 속성을 호출하는 정적 방법 (즉acess$0,access$1 등) 을 호출하여 이 속성 값을 가져오기 때문입니다.이 모든 것은 컴파일러의 특수 처리이다.
이번에도 실효?
위의 작법이 자주 사용된다면, 이런 작법은 접촉이 적지만 운행할 수 있지 않을까.

public class AnotherOuterClass {
 public static void main(String[] args) {
   InnerClass inner = new AnotherOuterClass().new InnerClass();
   System.out.println("InnerClass Filed = " + inner.x);
 }

 class InnerClass {
   private int x = 10;
 }

}

위와 같이javap를 사용하여 역컴파일해 보십시오.근데 이번에는 Inner Class 결과를 한번 보도록 하겠습니다.

16:03 $ javap -c AnotherOuterClass\$InnerClass
Compiled from "AnotherOuterClass.java"
class AnotherOuterClass$InnerClass extends java.lang.Object{
final AnotherOuterClass this$0;

AnotherOuterClass$InnerClass(AnotherOuterClass);
 Code:
  0: aload_0
  1: aload_1
  2: putfield #12; //Field this$0:LAnotherOuterClass;
  5: aload_0
  6: invokespecial  #14; //Method java/lang/Object."<init>":()V
  9: aload_0
  10: bipush  10
  12: putfield #17; //Field x:I
  15: return

static int access$0(AnotherOuterClass$InnerClass);
 Code:
  0: aload_0
  1: getfield #17; //Field x:I
  4: ireturn

}

컴파일러가 자동으로 개인 속성을 가져오는 뒷문 방법인access$0을 생성하여 x의 값을 가져옵니다.
AnotherOuterClass.class의 반컴파일 결과

16:08 $ javap -c AnotherOuterClass
Compiled from "AnotherOuterClass.java"
public class AnotherOuterClass extends java.lang.Object{
public AnotherOuterClass();
 Code:
  0: aload_0
  1: invokespecial  #8; //Method java/lang/Object."<init>":()V
  4: return

public static void main(java.lang.String[]);
 Code:
  0: new #16; //class AnotherOuterClass$InnerClass
  3: dup
  4: new #1; //class AnotherOuterClass
  7: dup
  8: invokespecial  #18; //Method "<init>":()V
  11: dup
  12: invokevirtual  #19; //Method java/lang/Object.getClass:()Ljava/lang/Class;
  15: pop
  16: invokespecial  #23; //Method AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V
  19: astore_1
  20: getstatic  #26; //Field java/lang/System.out:Ljava/io/PrintStream;
  23: new #32; //class java/lang/StringBuilder
  26: dup
  27: ldc #34; //String InnerClass Filed =
  29: invokespecial  #36; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  32: aload_1
  33: invokestatic #39; //Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
  36: invokevirtual  #43; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  39: invokevirtual  #47; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  42: invokevirtual  #51; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
  45: return

}

그 중에서 이 호출은 외부 클래스가 내부 클래스의 실례를 통해 개인 속성 x를 얻는 작업이다
33:   invokestatic#39;//Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
총결산
그중에 자바 공식 문서에 이런 말이 있어요.
if the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.
구성원과 구조 방법이 개인 수식자로 설정되어 있고 외부 클래스만 접근할 때 허용된다는 뜻이다.
어떻게 내부 유형의 개인 구성원이 외부에 방문하지 못하게 합니까
위의 두 부분을 보고 나면 내부 클래스의 개인 구성원들이 외부 클래스에 방문하지 않으려면 모두 어렵다고 느낄 것이다. 누가 컴파일러에게 오지을 시키겠는가. 사실은 할 수 있다.그것은 익명 내부 클래스를 사용하는 것이다.
mRunnable 대상의 유형은 익명 내부 클래스의 유형이 아니라 Runnable이기 때문에 (정상적으로 얻을 수 없음), Runanble에는 x라는 속성이 없기 때문에 mRunnable.x는 허용되지 않습니다.

public class PrivateToOuter {
 Runnable mRunnable = new Runnable(){
   private int x=10;
   @Override
   public void run() {
     System.out.println(x);
   }
 };

 public static void main(String[] args){
   PrivateToOuter p = new PrivateToOuter();
   //System.out.println("anonymous class private filed= "+ p.mRunnable.x); //not allowed
   p.mRunnable.run(); // allowed
 }
}

최종 요약
본고에서private는 표면적으로 효력을 상실한 것처럼 보이지만 실제로는 없는 것이고 호출할 때 간접적인 방법을 통해 개인적인 속성을 얻는다.
자바의 내부 클래스 구조는 외부 클래스에 대한 응용을 가지고 있으며, C++는 그렇지 않다는 점은 C++와 다르다.
자바의 디테일을 깊이 있게 다룬 책
Java 프로그래밍 사상
Sun Corporation Technology: Effective Java 중국어 버전
Java 가상 머신 이해: JVM의 고급 기능 및 모범 사례
이상은 Java private 수식자에 대한 자료 정리입니다. 후속으로 관련 자료를 계속 보충합니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기