1. 程式人生 > 其它 >Java類訪問許可權

Java類訪問許可權

目錄

1 類訪問許可權

1.1 四種訪問許可權解析

Java有四種訪問許可權, 其中三種有訪問許可權修飾符,分別為private,public和protected,還有一種不帶任何修飾符
四種訪問許可權:

  • private: Java語言中對訪問許可權限制的最窄的修飾符,一般稱之為私有的。被其修飾的類、屬性以及方法只能被該類的物件訪問,其子類不能訪問,更不能允許跨包訪問。
  • default:即不加任何訪問修飾符,通常稱為預設訪問模式。該模式下,只允許在同一個包中進行訪問。
  • protect
    : 介於public 和 private 之間的一種訪問修飾符,一般稱之為保護形。被其修飾的類、屬性以及方法只能被類本身的方法及子類訪問,即使子類在不同的包中也可以訪問。
  • publicJava語言中訪問限制最寬的修飾符,一般稱之為公共的。被其修飾的類、屬性以及方法不僅可以跨類訪問,而且允許跨包(package)訪問。

下面用表格的形式來展示四種訪問許可權之間的異同點,這樣會更加形象。表格如下所示:

同一個類 同一個包 不同包的子類 不同包的非子類
Private
Default
Protected
Public

1.2 Protected分析

假設在包accesscontrol下面有AccessControlDemoBase 兩個類,其中protected double price;Base類的成員變數,因為兩個類在同一個包中,所以在AccessControlDemo類中可以直接訪問System.out.println(base.price);具體例項如下:
accesscontrol.AccessControlDemo

package accesscontrol;

public class AccessControlDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Base base=new Base("123-1",120.1);
        System.out.println(base.price);
        
    }
}

accesscontrol.Base

package accesscontrol;

public class Base {
    
    private String isbn;
    protected double price;
    
    //預設建構函式
    public Base() {}

    //建構函式,如果只定義帶引數的建構函式而不定義預設建構函式,那麼Base的子類必須定義顯式建構函式
    public Base(String isbn, double price) {
        this.isbn = isbn;
        this.price = price;
    }
    

    public String getIsbn() {
        return isbn;
    }
    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
}

但是假如我們將AccessControlDemo這個類移到test這個包中,我們會發現eclipse中提示錯誤,編譯無法通過,因為在test包中對protected型別的成員變數不可見。
假如我們在test包中建立一個Base類的子類Bulk,也就是說BulkBase類不同包的子類。那麼在Bulk類中能夠直接訪問protected double price;這個基層自Base類的成員變數,例項如下:
test.AccessControlDemo

package test;

public class AccessControlDemo {
    public static void main(String[] args) {
        Bulk bulk=new Bulk("123-1",120.1);
        bulk.print();
    }
}

test.Bulk

package test;

import accesscontrol.Base;

public class Bulk extends Base {
    
    public Bulk() {
        super();
    }

    public Bulk(String isbn, double price) {
        super(isbn, price);
    }

    public void print()
    {
        System.out.println(this.price);
    }
}

1.3 private失效情況

Java程式設計中,使用private關鍵字修飾了某個成員,只有這個成員所在的類和這個類的方法可以使用,其他的類都無法訪問到這個private成員。

1.3.1 Java內部類

Java中相信很多人都用過內部類,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();
  }
}

這是為什麼呢,不是private修飾的成員只能被成員所述的類才能訪問麼?難道private真的失效了麼?

使用javap命令檢視一下生成的兩個class檔案
OuterClass的反編譯結果

$ 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返回outerClasslanguage屬性;access$1返回outerClassregion屬性。並且這兩個方法都接受OuterClass的例項作為引數。這兩個方法為什麼生成呢,有什麼作用呢?我們看一下內部類的反編譯結果就知道了。
OuterClass$InnerClass的反編譯結果

$ 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的程式碼,其目的是得到OuterClasslanguage 私有屬性。

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

下面程式碼呼叫了access$1的程式碼,其目的是得到OutherClassregion 私有屬性。

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表面上看上去失效了,但實際上是沒有的,而是在呼叫時通過間接的方法來獲取私有的屬性。