1. 程式人生 > >Java面向對象概述和三大特性

Java面向對象概述和三大特性

obi bcf 類對象 編譯 開發者 set 包含 parent 默認

  Java 是面向對象的高級編程語言,類和對象是 Java 程序的構成核心。圍繞著 Java 類和 Java 對象,有三大基本特性:封裝是 Java 類的編寫規範、繼承是類與類之間聯系的一種形式、而多態為系統組件或模塊之間解耦提供了解決方案。

  本文主要圍繞這三大特性介紹一下 Java 面向對象、組件解耦的核心思想。

1、面向對象思想

  面向對象編程是當今主流的程序設計思想,已經取代了過程化程序開發技術,Java 是完全面向對象編程語言,所以必須熟悉面向對象才能夠編寫 Java 程序。

  面向對象的程序核心是由對象組成的,每個對象包含著對用戶公開的特定功能和隱藏的實現部分。程序中的很多對象來自 JDK 標準庫,而更多的類需要我們程序員自定義。

  從理論上講,只要對象能夠實現業務功能,其具體的實現細節不必特別關心。

  面向對象有以下特點:

  (1)面向對象是一種常見的思想,比較符合人們的思考習慣;

  (2)面向對象可以將復雜的業務邏輯簡單化,增強代碼復用性;

  (3)面向對象具有抽象、封裝、繼承、多態等特性。

  面向對象的編程語言主要有:C++、Java、C#等。

2、類和對象的關系

  類:

  對某類事物的普遍一致性特征、功能的抽象、描述和封裝,是構造對象的模版或藍圖,用 Java 編寫的代碼都會在某些類的內部。類之間主要有:依賴、聚合、繼承等關系。

  對象:

  使用 new 關鍵字或反射技術創建的某個類的實例。同一個類的所有對象,都具有相似的數據(比如人的年齡、性別)和行為(比如人的吃飯、睡覺),但是每個對象都保存著自己獨特的狀態,對象狀態會隨著程序的運行而發生改變,需要註意狀態的變化必須通過調用方法來改變,這就是封裝的基本原則。

3、封裝思想

  核心思想就是“隱藏細節”、“數據安全”:將對象不需要讓外界訪問的成員變量和方法私有化,只提供符合開發者意願的公有方法來訪問這些數據和邏輯,保證了數據的安全和程序的穩定。

  具體的實現方式就是:

  使用 private 修飾符把成員變量設置為私有,防止外部程序直接隨意調用或修改成員變量,然後對外提供 publicsetget 方法按照開發者的意願(可以編寫一些業務邏輯代碼,雖然很少這樣做)設置和獲取成員變量的值。

  也可以把只在本類內部使用的方法使用 private,這就是封裝的思想,是面向對象最基本的開發規範之一。

  在此,我們有必要說一下 Java 的訪問權限修飾關鍵字。Java 中主要有 private、protected、public 和 默認訪問權限 四種:

  public 修飾符,具有最大的訪問權限,可以訪問任何一個在 CLASSPATH 下的類、接口、異常等。

  protected 修飾符,主要作用就是用來保護子類,子類可以訪問這些成員變量和方法,其余類不可以。

  default 修飾符,主要是本包的類可以訪問。

  private 修飾符,訪問權限僅限於本類內部,在實際開發過程中,大多數的成員變量和方法都是使用 private 修飾的。

  技術分享圖片

  Java 的訪問控制是停留在編譯層的,只在編譯時進行訪問權限檢查,不會在類文件中留下痕跡。

  通過反射機制,還是可以訪問類的私有成員的。

  我們舉個小例子:

技術分享圖片
 1 public class MobilePhone {
 2     
 3     // 使用 private 關鍵字把成員變量私有化
 4     private String os;
 5     private String phoneNumber;
 6     private String brand;
 7     private double dumpEnergy;
 8 
 9     // 對外提供訪問、設置成員變量的 public 方法
10     // 這樣就可以按照我們自己的意願來訪問、設置成員變量
11     // 而且也有助於在方法內部對數據有效性進行驗證
12     public void setOs(String os){
13         this.os = os;
14     }
15     public String getOs(){
16         return this.os;
17     }
18     public void setPhoneNumber(String phoneNumber){
19         this.phoneNumber = phoneNumber;
20     }
21     public String getPhoneNumber(){
22         return this.phoneNumber;
23     }
24     public void setBrand(String brand){
25         this.brand = brand;
26     }
27     public String getBrand(){
28         return this.brand;
29     }
30     public void setDumpEnergy(double dumpEnergy){
31         this.dumpEnergy = dumpEnergy;
32     }
33     public double getDumpEnergy(){
34         return this.dumpEnergy;
35     }
36 
37     // 發短信的方法,不需要做修改
38     public void sendMessage(String message, String targetPhoneNumber){
39         System.out.println("發給" + targetPhoneNumber + ", 內容是:" + message);
40     }
41 
42     // 充電方法,不需要做修改
43     public double charge(){
44         System.out.println("正在充電, 剩余電量:" + dumpEnergy * 100 + "%");
45         return dumpEnergy;
46     }
47     
48     // 對外提供的開機方法
49     public void startup(){
50         System.out.println("正在開機......");
51         
52         // 調用私有開機方法
53         startup2();
54 
55         System.out.println("完成開機");
56     }
57 
58     // 私有的開機方法,封裝開機細節
59     private void startup2(){
60         System.out.println("啟動操作系統......");
61         System.out.println("加載開機啟動項......");
62         System.out.println("......");
63     }
64 }
View Code

  在實際的開發過程中,這樣的封裝方式已經成了 Java Bean 代碼編寫的規範。現在主流的框架在使用反射技術為對象賦值、取值時使用的都是 set 和 get 方法,而不是直接操作字段的值。

4、繼承和類實例化過程

  (1)在多個不同的類中抽取出共性的數據和邏輯,對這些共性的內容進行封裝一個新的類即父類(也叫做超類或基類),讓之前的類來繼承這個類,那些共性的內容在子類中就不必重復定義,比如 BaseDAO、BaseAction 等。

  * (2)Java 的繼承機制是單繼承,即一個類只能有一個直接父類。

  * (3)如果子類和父類有同名成員變量和方法,子類可以使用 super 關鍵字調用父類的成員變量和方法,上述使用方式前提是成員在子類可見。

  * (4)在調用子類構造方法時,會隱式的調用父類的構造方法 super()。如果父類沒有無參構造方法,為了避免編譯錯誤,需要在子類構造方法中顯式的調用父類的含參構造方法。

  (5)子類創建時調用父類構造方法:子類需要使用父類的成員變量和方法,所以就要調用父類構造方法來初始化,之後再進行子類成員變量和方法的初始化。因此,構造方法是無法覆蓋的。

  * (6)當子類需要擴展父類的某個方法時,可以覆蓋父類方法,但是子類方法訪問權限必須大於或等於父類權限。

  (7)繼承提高了程序的復用性、擴展性,也是 Java 語言多態特征的前提。

  (8)在實際開發、程序設計過程中,並非先有的父類,而是先有了子類中通用的數據和邏輯,然後再抽取封裝出來的父類。

  我們簡單了解下類的實例化過程

  (1)JVM 讀取指定 classpath 路徑下的 class 文件,加載到內存,如果有直接父類,也會加載父類;

  (2)堆內存分配空間;

  (3)執行父類、子類靜態代碼塊;

  (4)對象屬性進行默認初始化;

  (5)調用構造方法;

  (6)在構造方法中,先調用父類構造方法初始化父類數據;

  (7)初始化父類數據後,顯示初始化,執行子類的構造代碼塊;

  (8)再進行子類構造方法的特定初始化;

  (9)初始化完畢後,將地址賦值給引用

  為了說明上面的內容,我們來編寫一個簡單的例子,實際意義並不大,只是為了演示類繼承實例化的過程。

技術分享圖片
  1 /*
  2     父類
  3 */
  4 class Parent {
  5 
  6     int num = 5;
  7 
  8     static {
  9         System.out.println("父類靜態代碼塊");
 10         System.out.println();
 11     }
 12 
 13     {
 14         System.out.println("父類構造代碼塊1," + num);
 15         num = 1;
 16         System.out.println("父類構造代碼塊2," + num);
 17         doSomething();
 18         System.out.println();
 19     }
 20 
 21     Parent() {
 22         System.out.println("父類構造方法1," + num);
 23         num = 2;
 24         System.out.println("父類構造方法2," + num);
 25         doSomething();
 26         System.out.println();
 27     }
 28 
 29     void doSomething() {
 30         System.out.println("父類doSomething方法1," + num);
 31         num = 3;
 32         System.out.println("父類doSomething方法2," + num);
 33         System.out.println();
 34     }
 35 }
 36 
 37 /*
 38     子類
 39 */
 40 class Child extends Parent {
 41 
 42     int num = 10;
 43     
 44     /*
 45         靜態代碼塊,在類加載時執行
 46     */
 47     static {
 48         System.out.println("子類靜態代碼塊");
 49         System.out.println();
 50     }
 51 
 52     /*
 53         構造代碼塊
 54     */
 55     {
 56         System.out.println("子類構造代碼塊1," + num);
 57         num = 11;
 58         System.out.println("子類構造代碼塊2," + num);
 59         doSomething();
 60         System.out.println();
 61     }
 62 
 63     Child() {
 64         System.out.println("子類構造方法1," + num);
 65         num = 12;
 66         System.out.println("子類構造方法2," + num);
 67         doSomething();
 68         System.out.println();
 69     }
 70 
 71     void doSomething() {
 72         System.out.println("子類doSomething方法1," + num);
 73         num = 13;
 74         System.out.println("子類doSomething方法2," + num);
 75         System.out.println();
 76     }
 77 
 78 }
 79 
 80 public class A {
 81     public static void main(String[] args) {
 82         Child child = new Child();
 83         child.num = 20;
 84         child.doSomething();
 85     }
 86 }
 87 
 88 輸出:
 89 
 90 父類靜態代碼塊
 91 
 92 子類靜態代碼塊
 93 
 94 父類構造代碼塊1,5
 95 父類構造代碼塊2,1
 96 子類doSomething方法1,0
 97 子類doSomething方法2,13
 98 
 99 
100 父類構造方法1,1
101 父類構造方法2,2
102 子類doSomething方法1,13
103 子類doSomething方法2,13
104 
105 
106 子類構造代碼塊1,10
107 子類構造代碼塊2,11
108 子類doSomething方法1,11
109 子類doSomething方法2,13
110 
111 
112 子類構造方法1,13
113 子類構造方法2,12
114 子類doSomething方法1,12
115 子類doSomething方法2,13
116 
117 
118 子類doSomething方法1,20
119 子類doSomething方法2,13
View Code

5、多態、反射和組件解耦

  (1)Java 中可以使用父類、接口變量引用子類、實現類對象;

  (2)在這個過程中,會對子類、實現類對象做自動類型提升,其特有功能就無法訪問了,如果需要使用,可以做強制類型轉換。

  Java 的反射技術和多態特性是框架開發、組件解耦的核心,在這方面,Spring 的 IOC 和 DI 為我們提供了一個極好的學習範例,Spring 的 IOC 使用反射技術創建、管理對象,DI 使用多態技術為組件註入依賴對象。

  在沒有學習 Spring 之前,簡單的解決方案是使用一個 .properties 文件保存程序中使用的接口、實現類類型鍵值信息,然後在程序中使用一個全局 Properties 對象保存這些信息,並且使用反射技術把這些實現類初始化、提供一個靜態的方法獲取指定接口的實現類對象,在組件中就可以使用依賴對象的鍵獲取需要的對象。

  這樣的方案帶來的好處就是:當我們需要修改某個組件的實現方式時,比如把之前 JDBC 的 DAO 實現改為 Hibernate 實現,只要把這些新的實現類放到 classpath 下,把 .properties 文件對應接口的實現類類型改成新的 Hibernate 實現類,而不需要修改依賴組件的代碼。

  在之後的文章中,我們將寫一個簡單的例子進一步講解這個初級解耦方案。

Java面向對象概述和三大特性