Java:"失效"的private修饰符

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

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

Java内部类

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

class OuterClass {
  class InnerClass{
  }
}

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

第一次失效?

一个我们在编程中经常用到的场景,就是在一个内部类里面访问外部类的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();
 }
}

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

编译器在捣鬼?

我们使用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的region属性。并且这两个方法都接受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反编译看一下。不过这次我们先看一下InnerClass的结果

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

再来个总结

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

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细节的书籍

Java编程思想
Sun 公司核心技术丛书:Effective Java中文版
深入理解Java虚拟机:JVM高级特性与最佳实践

以上就是对Java private 修饰符的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!

(0)

相关推荐

  • Java中的访问修饰符详细解析

    1.类的修饰符分为:可访问控制符和非访问控制符两种. 可访问控制符是:公共类修饰符 public 非访问控制符有:抽象类修饰符 abstract :最终类修饰符 final 1 )公共类修饰符 public : Java 语言中类的可访问控制符只有一个: public 即公共的.每个 Java 程序的主类都必须是 public 类作为公共工具供其它类和程序使用的应定义为 public 类. 2 )抽象类修饰符 abstract :凡是用 abstract 修饰符修饰的类,被称为抽象类.所谓抽象类

  • 举例详解Java中的访问权限修饰符

    访问权限符: (1)public: 对于成员来说:任何其他类都可以访问它们,不管在同一个包中还是在另外的包中. 对于类来说:    也是一样. (2)friendly: 对于成员老说:如果一个类的成员没有任何权限修饰,那么它门就是缺省包访问权限,用friendly来表示,注 意friendly不是Java中的关键字,这里是个人喜欢的方式用它表示而已.同一个包内其它类可以访问,但包外 就不可以.对于同一个文件夹下的.没有用package的classes,Java会自动将这些classes初见为隶属

  • 详解Java编程中protected修饰符与static修饰符的作用

    protected 来谈谈protected访问权限问题.看下面示例1: Test.java class MyObject {} public class Test { public static void main(String[] args) { MyObject obj = new MyObject(); obj.clone(); // Compile error. } } 此时出现上文提到的错误:The method clone from the type Object is not v

  • 关于Java中修饰符的总结(fina除外)

    public/protected/private public表示公开,private表示私有,protected表示保护,什么都不写表示默认default. 方法: public protected default private √ √ √ √ 同一类中 √ √ √ × 同一包中 √ √ × × 子类中 √ × × × 不同包中 成员变量: 考虑的安全性和健壮性的问题,成员变量一般为私有,并对外提供一些public的方法修改成员变量,在修改的方法中对输入进行判断和控制. Private成员变

  • 浅谈java中的访问修饰符

    一. public:所有类都可以访问 protected:所有子类和同包下的类都可以访问 缺省:同包类都可以访问 private:类本身才可以访问 注意点:protected修饰类属性时,例如 复制代码 代码如下: package Parent; public class Parent{ protected int i=5; } package Son; public class Son extends Parent{ public static void main(String[] args)

  • Java中的private修饰符失效了?

    在Java编程中,使用private关键字修饰了某个成员,只有这个成员所在的类和这个类的方法可以使用,其他的类都无法访问到这个private成员. 上面描述了private修饰符的基本职能,今天来研究一下private功能失效的情况. Java内部类 在Java中相信很多人都用过内部类,Java允许在一个类里面定义另一个类,类里面的类就是内部类,也叫做嵌套类.一个简单的内部类实现可以如下 复制代码 代码如下: class OuterClass {     class InnerClass{   

  • Java修饰符 abstract,static,final 的区别详解

    static 表示静态,它可以修饰属性,方法和代码块. 1.static修饰属性(类变量),那么这个属性就可以用类名.属性名来访问,也就是使这个属性成为本类的类变量,为本类对象所共有.这个属性就是全类公有.(共有的类变量与对象无关,只和类有关). 类加载的过程,类本身也是保存在文件中(字节码文件保存着类的信息)的,java会通过I/O流把类的文件(字节码文件)读入JVM(java虚拟机),这个过程成为类的加载.JVM(java虚拟机)会通过类路径(CLASSPATH)来找字节码文件. 类变量,会

  • Java的访问修饰符与变量的作用域讲解

    Java访问修饰符(访问控制符) Java 通过修饰符来控制类.属性和方法的访问权限和其他功能,通常放在语句的最前端.例如: public class className { // body of class } private boolean myFlag; static final double weeks = 9.5; protected static final int BOXWIDTH = 42; public static void main(String[] arguments)

  • Java final 修饰符知识点总结(必看篇)

    final从字面上理解含义为"最后的,最终的".在Java中也同样表示出此种含义. final可以用来修饰变量(包括类属性.对象属性.局部变量和形参).方法(包括类方法和对象方法)和类. 1. final修饰类: final修饰类即表示此类已经是"最后的.最终的"含义.因此,用final修饰的类不能被继承,即不能拥有自己的子类. 如果视图对一个已经用final修饰的类进行继承,在编译期间或发生错误. 2. final修饰方法: final修饰的方法表示此方法已经是&

  • java中final修饰符实例分析

    final修饰符: final修饰成员变量必须有程序员显示指定初始值. 类的Field:必须在静态初始化块中或声明该Field时指定初始值. 实例Field:必须在非静态初始块中,声明Field或者构造器中指定初始值. final局部变量:必须由程序员显示初始化. final修饰的基本变量和引用类型变量的区别? final修饰的基本变量:不能对基本变量重新赋值. final修饰的引用变量:只保证这个引用类型所引用的地址不会变,即 一直引用同一个对象,但这个对象完全可以发生改变. 复制代码 代码如

随机推荐