1. 程式人生 > >Java基礎之--訪問許可權修飾符

Java基礎之--訪問許可權修飾符

文章出自:安卓進階學習指南
作者:Alex_Zhao
稽核者: 麥田哥
完稿日期:2017.10.24

在我們每天寫的程式碼中,無論是類還是變數,都少不了修飾符這個東西,所有的修飾符都是 Java 語言規定的關鍵字。
那麼我們每天在使用它們的時候有沒有想過以下的幾個問題:

  • 這個修飾符到底是做什麼用的?
  • 為什麼要使用它?

如果你在使用這些修飾符的時候也考慮過這些問題,或者是對這些修飾符不是很瞭解,想對它們有一些深入的瞭解,那麼這篇文章或許正式你需要的。如有寫的不好或不對的地方,歡迎指正。

為何會有訪問許可權修飾符

我們平時在寫程式碼的時候都會用到一些類庫,比如 Java JDK 中自帶的公共類庫,或者一些第三方的庫。使用這些庫都可以極大的方便我們的開發,少寫很多程式碼。
類庫的開發者將類庫提供給其他開發者使用的時候,肯定都不希望自己庫的程式碼可以被人隨意的呼叫(特別是第三方的商業SDK),這些類庫一般都只會向外提供一些少量的方法供使用者去呼叫,而把自己的核心程式碼藏起來,不讓使用者看到和呼叫。
同時,類庫也會面臨不斷修改升級的問題,如果不加以控制,類庫方做任何修改都會直接影響到使用者的程式,這樣的話,類庫要想修改的話就會非常麻煩。
鑑於以上的問題,Java 在設計的時候就提供了訪問許可權修飾符這個東西,通過許可權來控制類,方法,變數是否能被訪問。

修飾符都有哪些

類修飾符:

public :(訪問控制符)將一個類宣告為公共類,它可以被任何物件訪問,一個程式的主類必須是公共類。
abstract :將一個類宣告為抽象類,沒有實現的方法,需要子類提供方法實現。
final :將一個類宣告為最終類(即非繼承類),表示它不能被其他類繼承。
static :將一個類宣告為靜態的,僅內部類可以使用此修飾符。

成員變數修飾符

public :(公共訪問控制符)指定該變數為公共的,它可以被任何物件的方法訪問。
protected :(保護訪問控制符)指定該變數可以只被自己的類和子類訪問。在子類中可以覆蓋此變數。
private

:(私有訪問控制符)指定該變數只允許自己的類的方法訪問,其他任何類(包括子類)中的方法均不能訪問。
final :最終修飾符,指定此變數的值不能變。
static :(靜態修飾符)指定變數被所有物件共享,即所有例項都可以使用該變數。變數屬於這個類。

方法修飾符

public :(公共控制符)指定該方法為公共的,它可以被任何物件的方法訪問。
protected :(保護訪問控制符)指定該方法可以被它的類和子類進行訪問。
private :(私有控制符)指定此方法只能有自己類等方法訪問,其他的類不能訪問(包括子類)。
final :指定該方法不能被過載。
static:指定不需要例項化就可以啟用的一個方法。

在以上所羅列出的修飾符中,方法和成員變數的修飾符是一樣的,都是 public,private,protected,final,static 這五個。所以可以把方法看成是一個特殊的變數。其中 public,private,protected 這三個都是直接限定訪問許可權的,final,static 則是規定了變數和方法型別。

public,private,protected

這三個修飾符控制的訪問範圍對照表如下:

修飾符 當前類 同一包內 子孫類 其他包
public Y Y Y Y
protected Y Y Y N
default Y Y N N
private Y N N N

看完上面的表格後,相信對於訪問許可權和修飾符應該有了一個直觀的認識。再囉嗦一點,說的直白點就是:

  • 如果你想讓這個類,方法或變數能夠被其他的任何類訪問到,那麼就給它加上 public 修飾符。
  • protected 修飾符,只能用來修飾變數和方法。如果你不想這個變數或方法被其他包裡面的類訪問到,同時又需要被繼承此類的子孫類訪問,那麼你應該使用 protected 修飾符。
  • 而如果不使用任何修飾符的話,也就是預設狀態,那麼它的訪問範圍就進一步縮小了,只能是當前類和相同包名下面的類才能訪問了,子孫類都是訪問不到的。
  • 最後一個 private ,它的範圍最小,同樣是只適用於變數和方法。被它修飾了的話,就只能是同一個類下面的才能訪問了,其他任何地方都是沒法訪問的。

上面把訪問許可權部分說完了,接著再來說說 final,static 這二個。

final

首先從字面意思上來理解,最終的。既然是最終的了,那麼就是已經定型了,不能再修改了。所以被它所修飾的類,方法和變數都有了相對的特性:

  • 被 final 修飾的類是不可以被繼承的,因為繼承的初衷就是為了能夠部分修改,擴充套件父類的內容,如果父類被 final 定義為了最終版本,那麼當然是不能再被修改的了,自然也就不能被繼承。
  • 被 final 修飾的方法是不可以被重寫的,原理同上。
  • 被 final 修飾的變數的值是不可以再被改變了的,既然是不可變的,那麼就有了另外一個稱呼 – 常量。

static

字面意思:靜態的。既然有靜態的,那麼肯定也會有相應的動態的,這裡的靜態和動態其實是相對於記憶體的變化來說的。
一個類在被例項化的時候,JVM 會給它初始化一個記憶體地址,如果這個類有多個例項,它們的記憶體地址是不一樣的,這即是動態。那麼靜態就可以理解為它的記憶體地址是不會變的,被 static 修飾的變數和方法在類被初始化的時候會被分配到一個單獨的記憶體地址中,一旦分配好就不會再變了。這裡是說的記憶體地址不會變,但是它裡面的內容或值是可以變的,但是如果前面同時有 final 修飾的話,那麼值也是不變的。
針對於這個特性,我們就可以直接用 類名.xxx 的形式來直接呼叫該類中定義的靜態變數和方法,而不是先建立類的例項,再通過例項來訪問變數和方法。

上面是我關於 static 這個修飾符的一個簡單的理解,要詳細的解釋 static 其實涉及到了很多更底層的知識,比如 JVM 初始化一個類的過程,記憶體中堆,棧的變化等等,限於篇幅這裡就不展開了,感興趣的朋友可以去找下相關的文章學習下。

abstract

下面來說下最後一個修飾符 abstract,字面意思:抽象的。只能用來修飾類和方法,
被修飾的類裡可以有若干抽象方法,也可以沒有。抽象類不可以直接例項化,它需要被繼承,用它的子類來例項化。
被修飾的方法為抽象方法,抽象方法裡面沒有具體的實現內容,具體實現內容需要由子類來實現。

在我開始學 Java 的時候,老師說過,寫程式要先從具體到抽象,再從抽象到具體。這話好深奧,那麼到底什麼意思呢?舉個比較經典的例子:

我們要為學校編寫一套程式,來管理全體師生的學習和生活,我們接到任務開始幹。首先是老師,老師有各種屬性,姓名,年齡等等,然後需要上課,備課等方法;然後是學生,學生也有姓名,年齡等屬性,還需要有上課,做作業等方法。
如果是這樣按照具體需求來一個個寫,沒問題,程式可以做出來。但是有沒有想過,如果真這麼做了,這裡面是不是有很多重複的東西,比如老師和學生都有姓名,年齡等屬性,也都需要上課。這些屬性和方法是一樣或者差不多的,如果全部分開寫,是不是要寫很多個,後面如果要改需求的話,是不是所有地方都要改,這是不是無形中增加了很多工作量,而且容易漏掉和出錯。

所以為了程式以後的可維護和可擴充套件,我們就需要把一些相同或者相似的東西給提取出來(其實也是為了偷懶,所以懶是第一生產力啊,,,),為了達到這個目的,就有了抽象。

那麼接著上面的例子,我們先找出老師和學生的共同點,把這些共同點抽取出來。老師和學生都是人吧,人都有姓名,年齡吧,在這個類中,我們可以設定幾個抽象方法,分別為設定姓名,年齡,然後還有上課這個行為。這樣就抽象出了一個公共類:人。這樣就完成了從具體到抽象的過程。

然後在具體使用的時候就需要再從抽象到具體了,就是老師和學生都繼承人這個抽象類,繼承抽象類之後就需要實現其中的抽象方法(因為抽象類中的方法都沒有具體的實現內容,需要由它的子類去具體實現),根據老師和學生不同的特性去實現在抽象類中定義的抽象方法,這也就是從抽象到具體的過程。

從上面例子的整個過程應該就能理解抽象是什麼,以及為什麼會有抽象這個修飾符了。

Samples

上面只是對許可權修飾符做了一些基本的理論上的介紹,那麼怎麼把這些基礎理論運用到實際開發中呢,下面用幾個具體的例子來說明一下。

  1. 工具類
    在開發中,我們都會使用工具類,將一些經常需要用的,跟實際業務邏輯沒什麼關係的公共方法單獨抽離出來放在一個工具類中,當需要用到其中的某個方法或變數的時候直接呼叫一下就行了,非常的方便。先來看一段程式碼:
public final class BarUtils {
    private static final int    DEFAULT_ALPHA = 112;
    private static final String TAG_COLOR     = "TAG_COLOR";
    private static final String TAG_ALPHA     = "TAG_ALPHA";
    private static final int    TAG_OFFSET    = -123;

    /**
     * 獲取狀態列高度(px)
     *
     * @return 狀態列高度px
     */
    public static int getStatusBarHeight() {
        Resources resources = Utils.getApp().getResources();
        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
        return resources.getDimensionPixelSize(resourceId);
    }

//省略若干程式碼
}

上面的程式碼是一個狀態列的工具類,類名前有二個修飾符,public 和 final,那麼為什麼要用這二個修飾符呢?下面來分析下。

首先考慮一個問題,這個類的作用是什麼,需要在哪裡用?
前面已經說過,這是一個工具類,將一些在開發中需要用到的公共方法抽取出來放在裡面,方便呼叫。所以這個類的作用就是提供一些公共的方法方便開發時呼叫,哪裡會需要用到呢?很多地方都有可能,針對於這二個特性,再結合上面的訪問範圍表,我們發現,只能使用 public 修飾才合適,其他的都不能滿足我們的要求。
那為什麼還要加上 final 呢?加了 final 後就不能被繼承,類裡面的方法也不能被重寫。其實不加 final 也是可以的,因為這只是一個工具類,繼承它然後去重寫它裡面的方法其實是沒有什麼意義的,所以不加也行。只是 JVM 會對被 final 修飾發類,方法和變數進行優化,可以提高效能。(這點我也不是很清楚,,,)

總結

在這篇文章中,我儘量用淺顯易懂的語言來描述了 Java 中的幾個非常常用的修飾符的作用,以及他們產生的原因。希望以上的內容能夠幫助大家更好的理解許可權修飾符這個知識點。

限於篇幅,有些知識點並沒有展開。再有就是因為這篇文章其實是偏理論和概念,所以並沒有貼出示例程式碼。有興趣的可以自己去敲下相應的程式碼,結合上面的理論知識去驗證下是否正確。有什麼寫的不對的地方歡迎探討和指正。

最近我和幾位小夥伴一同建立了一個專案,Awesome-Android-Learning-Guide 旨在幫助大家鞏固 Java 基礎以及 Android 方面的提高和進階。歡迎大家 star 和 issues,共同成長和進步。