In Java programming, if a member is modified using the private keyword, only the class in which the member is located and the methods of this class can be used, and no other class can access this private member.
The basic functions of the private modifier are described above. Today we will study the failure of the private function.
Java inner classes
I believe many people have used inner classes in Java. Java allows one class to be defined within another class. The class inside the class is an inner class, also called a nested class. A simple inner class implementation can be copied as follows:
class OuterClass {
class InnerClass{
}
}
Today’s question is related to Java internal classes, and only involves part of the internal class knowledge related to the research of this article. Specific subsequent articles on Java internal classes will be introduced.
First time failure?
A scenario we often use in programming is to access private member variables or methods of an external class in an internal class. This is okay. Implemented as the following code.
Copy the code code as follows:
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();
}
}
Why is this? Isn't it true that private-modified members can only be accessed by the class they represent? Is private really invalid?
Is the compiler causing trouble?
Let’s use the javap command to view the two generated class files.
The decompilation result copy code of OuterClass is as follows:
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
}
Huh? No, we did not define these two methods in 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
}
Judging from the comments given, access$0 returns the language attribute of outerClass; access$1 returns the region attribute of outerClass. And both methods accept instances of OuterClass as parameters. Why are these two methods generated and what do they do? Let's take a look at the decompilation results of the inner class and we will know.
Decompilation results of OuterClass$InnerClass
Copy the code code as follows:
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
}
The following code calls the code of access$0, whose purpose is to obtain the language private attribute of OuterClass.
Copy the code code as follows:
13: invokestatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
The following code calls the code of access$1, whose purpose is to obtain the region private attribute of OutherClass.
Copy the code code as follows:
28: invokestatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
Note: When the inner class is constructed, the reference of the outer class will be passed in and used as an attribute of the inner class, so the inner class will hold a reference to its outer class.
this$0 is the external class reference held by the inner class. The reference is passed and assigned through the constructor method.
Copy the code code as follows:
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
summary
This part of private seems to be invalid, but in fact it is not invalid, because when the inner class calls the private property of the external class, its real execution is to call the static method of the property generated by the compiler (i.e. access$0, access$1, etc.) to get these attribute values. This is all special processing by the compiler.
Doesn't it work this time?
If the above writing method is very commonly used, then this writing method is rarely used, but it can run.
Copy the code code as follows:
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;
}
}
As above, use javap to decompile and take a look. But this time let’s take a look at the results of InnerClass. Copy the code. The code is as follows:
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
}
It happened again, and the compiler automatically generated a backdoor method access$0 to obtain the private attribute once to obtain the value of x.
The decompilation result copy code of AnotherOuterClass.class is as follows:
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
}
This call is the operation of the external class to obtain the private property x through the instance of the internal class. Copy the code as follows:
33: invokestatic #39; //Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
Another summary
Among them, the official Java documentation has this sentence. Copy the code as follows:
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.
This means that if the members and constructors (of the inner class) are set to private modifiers, access is allowed if and only if their outer class.
How to prevent private members of inner classes from being accessed by outsiders
I believe that after reading the above two parts, you will feel that it is very difficult for the private members of the internal class to be accessed by the external class. Who wants the compiler to be "nosy"? In fact, it can be done. That is to use anonymous inner classes.
Since the type of the mRunnable object is Runnable, not the type of the anonymous inner class (which we cannot get normally), and there is no x attribute in Runanble, mRunnable.x is not allowed.
Copy the code code as follows:
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
}
}
Final summary
In this article, private appears to be invalid on the surface, but in fact it is not. Instead, private properties are obtained through indirect methods when calling.
Java's internal classes hold applications for external classes when constructed, but C++ does not. This is different from C++.