`
jaesonchen
  • 浏览: 311574 次
  • 来自: ...
社区版块
存档分类
最新评论

"失效"的private修饰符

 
阅读更多

在Java编程中,使用private关键字修饰了某个成员,只有这个成员所在的类和这个类的方法可以使用,其他的类都无法访问到这个private成员。

上面描述了private修饰符的基本职能,今天来研究一下private功能失效的情况。

Java内部类

在Java中相信很多人都用过内部类,Java允许在一个类里面定义另一个类,类里面的类就是内部类,也叫做嵌套类。一个简单的内部类实现可以如下

1
2
3
4
class OuterClass {
    class InnerClass{
    }
}

今天的问题和Java内部类相关,只涉及到部分和本文研究相关的内部类知识,具体关于Java内部类后续的文章会介绍。

第一次失效?

一个我们在编程中经常用到的场景,就是在一个内部类里面访问外部类的private成员变量或者方法,这是可以的。如下面的代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
  }
}

这是为什么呢,不是private修饰的成员只能被成员所述的类才能访问么?难道private真的失效了么?

编译器在捣鬼?

我们使用javap命令查看一下生成的两个class文件

OuterClass的反编译结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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中我们并没有定义这两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
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的region属性。并且这两个方法都接受OuterClass的实例作为参数。这两个方法为什么生成呢,有什么作用呢?我们看一下内部类的反编译结果就知道了。

OuterClass$InnerClass的反编译结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 私有属性。

1
13:   invokestatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;

下面代码调用了access$1的代码,其目的是得到OutherClass的region 私有属性。

1
28:   invokestatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;

注意:在内部类构造的时候,会将外部类的引用传递进来,并且作为内部类的一个属性,所以内部类会持有一个其外部类的引用。
this$0就是内部类持有的外部类引用,通过构造方法传递引用并赋值。

1
2
3
4
5
6
7
8
9
10
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等)来获取这些属性值。这一切都是编译器的特殊处理。

这次也失效?

如果说上面的写法很常用,那么这样的写法是不是很少接触,但是却可以运行。

1
2
3
4
5
6
7
8
9
10
11
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反编译看一下。不过这次我们先看一下InnerClass的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的反编译结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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的操作

1
33:   invokestatic #39; //Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I

再来个总结

其中java官方文档 有这样一句话

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是不被允许的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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表面上看上去失效了,但实际上是没有的,而是在调用时通过间接的方法来获取私有的属性。
  • Java的内部类构造时持有对外部类的应用,C++不会,这一点和C++不一样。
分享到:
评论

相关推荐

    Java:"失效"的private修饰符

    然而,在特定情况下,如内部类的使用,private修饰符的效果似乎“失效”,但这并非真正的失效,而是Java语言设计的一种特性。 首先,我们来看private修饰符的基本作用。当一个成员被声明为private时,它不能被同一...

    Java中的private修饰符失效了?

    然而,在特定情况下,比如在内部类(也称为嵌套类)中,`private`修饰符的这种限制似乎“失效”了。下面我们将详细探讨这个问题以及背后的原因。 首先,我们要明确内部类的概念。内部类是在另一个类的定义内部定义...

    细话Java:”失效”的private修饰符

     上面描述了private修饰符的基本职能,来研究一下private功能失效的情况。  Java内部类  在Java中相信很多人都用过内部类,Java允许在一个类里面定义另一个类,类里面的类是内部类,也叫做嵌套类。一个简单的...

    Spring事务管理失效原因汇总

    也就是说,这些修饰符的方法上不能直接使用@Transactional注解,从而导致事务管理失效。此外,protected方法虽然可以被重写,但在使用AOP配置时也需要进行适当的权限设置才能生效。 在讨论了代理模式、异常分类、...

    2021-2022计算机二级等级考试试题及答案No.117.docx

    题目中指出,只能在本类被访问的方法应该用private修饰。 3. 文件读取:在Java中,如果需要从文件中读取数据,通常会使用FileInputStream类,它提供了读取二进制数据的能力。 4. ASP.NET 支持的语言:ASP.NET框架...

    因Spring AOP导致@Autowired依赖注入失败的解决方法

    这可能是由于方法的修饰符是 private 导致的。 二、问题分析 在 org.springframework.aop.support.AopUtils 中,MethodMatcher 接口用于匹配方法。在方法 matches 中,会获取类的所有接口和方法,然后遍历所有方法...

    C# / .NET经典题目集锦(带标准答案)--面试必备

    C#/.NET经典题目集锦(带标准答案)...类和类的成员的访问修饰符分别有Public、Private、Protected、Internal、Protected Internal等,权限从高到低依次是Public、Internal、Protected Internal、Protected、Private。

    JAVA内部类

    **修饰符**:成员内部类可以使用`final`、`abstract`、`public`、`private`、`protected`、`static`等修饰符,其中`static`修饰后的内部类将转换为静态嵌套类。 #### 静态嵌套类 静态嵌套类是一种特殊的成员内部类...

    SpringSide 团队的编码规范

    - 修饰符顺序:public, protected, private, abstract, static, final, transient, volatile, synchronized, native, strictfp。 - 类和接口的声明顺序应遵循一定的结构,包括静态字段、静态初始化块、字段、初始...

    C#面向对象编程基础概念汇总

    public和private是两个常用的修饰符,public表示他所修饰的类成员可以允许其他任何类来访问,private只允许同一个类中的成员访问,其他类包括他的子类都无法访问。 五、封装 每个对象都包含他能进行操作所要的所有...

    C++_development_examples

    - **封装**:通过访问修饰符(public, private, protected)来控制类成员的可见性,实现数据隐藏,是面向对象的基本原则。 - **继承**:子类可以继承父类的属性和方法,实现代码的扩展和多态性。 - **多态**:C++...

    2021-2022计算机二级等级考试试题及答案No.4564.docx

    12. 访问控制符:在面向对象编程中,public、private和protected是常见的访问控制符,而static是用于声明静态成员的修饰符,不是访问控制符(选项C)。 13. 关系运算:若要改变属性的排列顺序,应使用投影...

    [Effective.C.中文版].(Scott.Meyers).(中文版&第3版).pdf.7z

    理解何时使用私有(private)和保护(protected)访问修饰符来保护数据成员,以及如何通过友元(friend)类和函数来扩展类的功能而不破坏封装性。 2. **常量与引用**:常量对象和引用是C++中的重要概念,它们在确保...

    2021-2022计算机二级等级考试试题及答案No.11110.docx

    8. **C#中的访问修饰符**:`private`表示私有成员,只能在类的内部访问;`protected`表示保护成员,可以在类内部和派生类中访问;`public`表示公共成员,完全公开,无访问限制;`internal`表示在同一命名空间内的...

    《防止用户进行正常的GUI 操作》配套源代码

    2. **程序安全**:在C++编程中,可以使用访问修饰符(如public、private、protected)来控制类成员的访问性,以此限制用户对代码的直接操作。同时,通过对输入验证和异常处理,可以防止用户通过GUI进行恶意操作。 3...

    130道ASP.NET面试题

    1. 访问修饰符的理解: - `private`:私有成员,只允许在定义它的类内部访问,提供封装性。 - `protected`:保护成员,允许在定义它的类和其子类中访问,用于继承场景。 - `public`:公共成员,可以在任何地方...

    2021-2022计算机二级等级考试试题及答案No.11959.docx

    - **知识点说明**:在Java中,`private`修饰符表示只有在同一个类内部才能访问该成员。 - `public`:在任何地方都可以访问。 - `protected`:在同一包内或子类中可以访问。 - `default`(默认):仅在同一包内...

    asp。net面试题

    访问修饰符:private、protected、public、internal - **private**:私有成员,只能在当前类内部访问。 - **protected**:受保护成员,可以在当前类或其派生类中访问。 - **public**:公共成员,可以被任何代码...

    Java学习笔记(必看经典)

    修饰符 返回值 方法名 调用过程中 方法体 可能出现的例外 public int/void addNumber(参数) throw Excepion {} 例: public int addNumber(int a,int b){ } 注:方法名中的参数int a,int b为局部变量 类方法中的...

Global site tag (gtag.js) - Google Analytics