Java核心技術筆記 繼承

《Java核心技術 卷Ⅰ》 第5章 繼承前端

  • 類、超類、子類
  • Object:全部類的超類
  • 泛型數組列表
  • 對象包裝器與自動裝箱
  • 參數數量可變的方法
  • 枚舉類
  • 繼承的設計技巧

類、超類和子類

定義子類

關鍵字extend表示繼承。java

public class Manager extends Employee
{
  // 添加方法和域
}

extend代表正在構造的新類派生於一個已存在的類。git

已存在的類稱爲超類(superclass)、基類(base class)或父類(parent class);
新類稱爲子類(subclass)、派生類(derived class)或孩子類(child class)。程序員

子類有超類沒有的功能,子類封裝了更多的數據,擁有更多的功能。github

因此在擴展超類定義子類時,僅須要指出子類與超類的不一樣之處。數組

覆蓋方法

有時候,超類的有些方法並不必定適用於子類,爲此要提供一個新的方法來覆蓋(override)超類中的這個方法:安全

public class Manager extends Employee
{
  private double bonus;
  ...
  public double getSalary()
  {
    double baseSalary = super.getSalary();
    return baseSalary + bonus;
  }
  ...
}

這裏因爲Manager類的getSalary方法並不能直接訪問超類的私有域app

這是由於儘管子類擁有超類的全部域,可是子類無法直接獲取到超類的私有部分,由於超類的私有部分只有超類本身才可以訪問。而子類想要獲取到私有域的內容,只能經過超類共有的接口。ide

而這裏Employee的公有方法getSalary正是這樣的一個接口,而且在調用超類方法使,要使用super關鍵字。工具

那你可能會好奇:

  • 不加super關鍵字不行麼?
  • Employee類的getSalary方法不該該是被Manager類所繼承了麼?

這裏若是不使用super關鍵字,那麼在getSalary方法中調用一個getSalary方法,勢必會引發無限次的調用本身。

關於superthis須要注意的是:他們並不相似,由於super不是一個對象的引用,不能將super賦給另外一個對象變量,它只是一個指示編譯器調用超類方法的特殊關鍵字。

子類構造器

public Manager(String name, double salary, int year, int month, int day)
{
  super(name, salary, year, month, day);
  bonus = 0;
}

語句super(...)是調用超類中含有對應參數的構造器。

Q1:爲何要這麼作

A1:因爲子類的構造器不能訪問超類的私有域,因此必須利用超類的構造器對這部分私有域進行初始化。可是注意,使用super調用構造器的語句必須是子類構造器的第一條語句

Q2:必定要使用麼

A2:若是子類的構造器沒有顯示地調用超類構造器,則將自動地調用超類默認(沒有參數)的構造器;若是超類並無不帶參數構造器,而且子類構造器中也沒有顯示調用,則Java編譯器將報告錯誤

Employee[] staff = new Employee[3];
staff[0] = manager;
staff[1] = new Employee(...);
staff[2] = new Employee(...);
for(Employee e : staff)
{
  System.out.println(e.getName() + "" + e.getSalary());
}

這裏將e聲明爲Emplyee對象,可是實際上e既能夠引用Employee對象,也能夠引用Manager對象。

  • 當引用Employee對象時,e.getSalary()調用的是EmployeegetSalary方法
  • 當引用Manager對象時,e.getSalary()調用的是ManagergetSalary方法

虛擬機知道e實際引用的對象類型,因此可以正確地調用相應的方法。

一個對象變量能夠指示多種實際類型的現象被稱爲多態(polymorphism)。在運行時可以自動地選擇調用哪一個方法的現象稱爲自動綁定(dynamic binding)。

繼承層次

集成並不只限於一個層次。

由一個公共超類派生出來的全部類的集合被稱爲繼承層次(inheritance hierarchy),在繼承層次中,從某個特定的類到其祖先的路徑被稱爲該類的繼承鏈(inheritance chain)。

Java不支持多繼承,有關Java中多繼承功能的實現方式,見下一章有關接口的部分。

多態

"is-a"規則的另外一種表述法是置換法則,它代表程序中出現超類對象的任何地方均可以用子類對象置換

Employee e;
e = new Employee(...);
e = new Manager(...);

在Java程序設計語言中,對象變量是多態的

Manager boss = new Manager(...);
Employee[] staff = new Employee[3];
staff[0] = boss;

這個例子中,雖然staff[0]boss引用同一個對象,可是編譯器將staff[0]當作Employee對象,這意味着這樣調用是沒有問題的:

boss.setBonus(5000); // OK

可是不能這樣調用:

staff[0].setBonus(5000); // Error

這裏由於staff[0]聲明的類型是Employee,而setBonus不是Employee類的方法。

儘管把子類賦值給超類引用變量是沒有問題的,但這並不意味着反過來也能夠:

Manager m = staff[2]; // Error

若是這樣賦值成功了,那麼編譯器將m當作是一個Manager類,在調用setBonus因爲所引用的Employee類並無該方法,從而會發生運行時錯誤。

理解方法調用

弄清楚如何在對象上應用方法調用很是重要。

好比有一個C類對象xC有一個方法f(args)

如今以調用x.f(args)爲例,說明調用過程的詳細描述:

  1. 編譯器查看對象的聲明類型和方法名。C類中可能有多個同名的方法,編譯器將列舉全部C類中名爲f的方法和其超類中屬性爲public且名爲f的方法(超類私有沒法訪問)。
  2. 接下來,編譯器查看調用方法時提供的參數類型。若是存在一個徹底匹配的f,就選擇這個方法,這個過程被稱爲重載解析(overloading resoluton)。這個過程容許類型轉換(int轉double,Manager轉Employee等等)。若是編譯器沒有找到,或者發現類型轉換後,有多個方法與之匹配,就會報告一個錯誤。
  3. 若是是private方法,static方法、final方法或者構造器,編譯器將能夠準確知道應該調用哪一個方法,這種稱爲靜態綁定(static binding)。若是不是這些,那調用的方法依賴於隱式參數的實際類型,而且在運行時動態綁定,好比x.f(args)這個例子。
  4. 當程序運行時,而且動態綁定調用時,虛擬機必定調用與x所引用對象的實際類型最合適的那個類的方法。好比x實際是C類,它是D類的子類,若是C類定義了方法f(String),就直接調用;不然將在C類的超類中尋找,以此類推。簡單說就是順着繼承層次從下到上的尋找方法。

若是每次調用方法都要深度/廣度遍歷搜索繼承鏈,時間開銷很是大。

所以虛擬機預先爲每一個類建立一個方法表(method table),其中列出了全部方法的簽名和實際調用的方法,這樣一來在真正調用時,只須要查表便可。

若是調用super.f(args),編譯器將對隱式參數超類的方法表進行搜索。

以前的Employee類和Manager類的方法表:

Employee:
  getName() -> Employee.getName()
  getSalary() -> Employee.getSalary()
  getHireDay() -> Employee.getHireDay()
  raiseSalary(double) -> Employee.raiseSalary(double)

Manager:
  getName() -> Employee.getName()
  getSalary() -> Manager.getSalary()
  getHireDay() -> Employee.getHireDay()
  raiseSalary(double) -> Employee.raiseSalary(double)
  setBonus(double) -> Manager.setBonus(double)

在運行時,調用e.getSalary()的解析過程:

  • 虛擬機提取實際類型的方法表(之因此叫實際類型,是由於Employee e能夠引用全部Employee類的子類,因此要肯定實際引用的類型)。
  • 虛擬機搜索定義getSalary簽名的類,虛擬機肯定調用哪一個方法。
  • 最後虛擬機調用方法。

注:在覆蓋一個方法時,子類方法不能低於超類方法的可見性,特別是超類方法是public,子類覆蓋方法時必定聲明爲public,由於常常會發生這樣的錯誤:在聲明子類方法時,由於遺漏public而使編譯器把它解釋爲更嚴格的訪問權限。

阻止繼承:final類和方法

有時候,可能但願阻止人們利用某個類定義子類。

不容許擴展的類被稱爲 final類。

若是在定義類時使用了final修飾符就代表這個類是final類。

public final class Executive extends Manager
{
  ...
}

方法也能夠被聲明爲final,這樣子類就不能覆蓋這個方法,final類中的全部方法自動地稱爲final方法。

public class Employee
{
  ...
  public final String getName()
  {
    return name;
  }
  ...
}

這裏注意final域的區別,final域指的是構造對象後就再也不運行改變他們的值了,不過若是一個類聲明爲final,只有其中的方法自動地成爲final,而不包括域。

將方法或類聲明爲 final主要目的是:確保不會在子類中改變語義。

強制類型轉換

有時候就像將浮點數轉換爲整數同樣,也可能須要將某個類的對象引用轉換成另外一個類的對象引用。

對象引用的轉換語法與數值表達式的類型轉換相似,僅須要一對圓括號將目標類名括起來,並放置在須要轉換的對象引用以前就能夠了。

Manager boss = (Manager) staff[0];
// 由於以前把boss這個Manager類對象也存在了Employee數組中
// 如今經過強制類型轉換回覆成Manager類
進行類型轉換的惟一緣由:在暫時忽略對象的實際類型以後,使用對象的所有功能。

在Java中,每一個對象變量都屬於一個類型,類型描述了這個變量所引用的以及可以引用的對象類型。

將一個值存入變量時,編譯器將檢查是否容許該操做:

  • 將一個子類的引用賦給一個超類變量,編譯器時容許的
  • 可是將一個超類引用賦給一個子類變量,必須進行類型轉化,這樣才能經過運行時的檢查

若是試圖在繼承鏈上進行向下的類型轉換,並謊報有關對象包含的內容(好比硬要把一個Employee類對象轉換成Manager類對象):

Manager boss = (Manager) staff[1]; // Error

運行時,Java運行時系統將報告這個錯誤(不是在編譯階段),併產生一個ClassCastException異常,若是沒有捕獲異常,程序將會終止。

因此應該養成一個良好習慣:在進行類型強轉以前,先查看一下是否能成功轉換,使用instanceof操做符便可:

if(staff[1] instanceof Manager)
{
  boss = (Manager) staff[1];
  ...
}

注:若是xnull,則它對任何一個類進行instanceof返回值都是false,它由於沒有引用任何對象。

抽象類

位於上層的類一般更具備通用性,甚至可能更加抽象,對於祖先類,咱們一般只把它做爲派生其餘類的基類,而不做爲想使用的特定的實例類,好比Person類對於EmployeeStudent類而言。

因爲Person對子類一無所知,可是又想規範他們,一種作法是提供一個方法,而後返回空的值,另外一種就是使用abstract關鍵字,這樣Person就徹底不用實現這個方法了。

public abstract String getDescription();
// no implementation required

爲了提供程序的清晰度,包含一個或多個抽象方法的類自己必須被聲明爲抽象的。

public abstract class Person
{
  private String name;
  ...
  public abstract String getDescription();
  ...
  public String getName()
  {
    return name;
  }
}

除了抽象方法外,抽象類還能夠包含具體數據和具體方法。

儘管許多人認爲,在抽象類中不能包含具體方法,可是仍是建議儘可能把通用的域和方法(無論是否抽象)都放在超類(無論是否抽象)中。

雖然你能夠聲明一個抽象類的引用變量,可是隻能引用非抽象子類的對象,由於抽象類不能被實例化。

在非抽象子類中定義抽象類的方法:

public class Student extends Person
{
  private String major;
  ...
  public String getDescription()
  {
    return "a student majoring in " + major;
  }
}

儘管Person類中沒有具體定義getDescription的具體內容,可是當一個Person類型引用變量p使用p.getDescription()也是沒有問題的,由於根據前面的方法調用過程,在運行時,方法的實際尋找是從實際類型開始尋找的,而實際類型都是定義了這個方法的具體內容。

那你可能會問,我能夠只在Student類中定義getDescription不就好了麼?爲何還要在Person去聲明?由於若是這樣的話,就不能經過p調用getDescription方法了,由於編譯器只容許調用在類中聲明的方法。

受保護訪問

有些時候,人們但願超類中的某些方法容許被子類訪問,或容許子類的方法訪問超類中的某個域,而不讓其餘類訪問到。

爲此,須要將這些方法或域聲明爲protected

例如,若是Employee中的hireDay聲明爲protected,而不是private,則Manager中的方法就能夠直接訪問它。

不過,Manager中的方法只可以訪問Manager對象中的hireDay,而不能訪問其餘Employee對象中的這個域,這樣使得子類只能得到訪問受保護域的權利。

對於受保護的域來講,可是這在必定程度上違背了OOP提倡的數據封裝原則,由於若是當一個超類進行了一些修改,就必須通知全部使用這個類的程序員(而不像普通的private域,只能經過開放的方法去訪問)。

相比較,受保護的方法更具備實際意義。若是須要限制一個方法的使用,就能夠聲明爲protected,這代表子類獲得信任,能夠正確地使用這個方法,而其餘類(非子類)不行。

這種方法的一個最好示例就是Object類中的clone方法。

概括總結Java控制可見性的4個訪問修飾符:

  1. public:對全部類可見
  2. protected:對本包和全部子類可見
  3. private:僅對本類可見
  4. 默認,無修飾符:僅對本包可見

Object:全部類的超類

Obejct類是Java中全部類的始祖,Java中每一個類都是它擴展而來。

若是沒有明確指出超類,Object就被認爲是這個類的超類。

天然地,可使用Object類型的變量引用任何類型的對象:

Obejct obj = new Employee("Harry Hacker", 35000);

Object類型的變量只能用於各類值的通用持有者。若是想要對其中的內容進行具體操做,還須要清楚對象的原始類型,並進行相應的類型轉換:

Employee e = (Employee) obj;

equals方法

Object類中的 equals方法用於檢測一個對象 是否等於另一個對象。

這裏的等於指的是判斷兩個對象是否具備相同的引用

可是在判斷兩個不肯定是否爲null的對象是否相等時,須要使用Objects.equals方法,若是兩個都是null,將返回true;若是其中一個爲null,另外一個不是,則返回false;若是兩個都不爲null,則調用a.equals(b)

固然大多數時候Object.equals並不能知足,通常來講咱們須要比較兩個對象的狀態是否相等,這個時候須要重寫這個方法:

public class Manager extends Employee
{
  ...
  public boolean equals(Object otherObject)
  {
    // 首先要調用超類的equals
    if(!super.equals(otherObejct)) return false;
    Manager other = (Manager) otherObject;
    return bonus == other.bonus;
  }
}

相等測試與繼承

在閱讀後面的書籍筆記內容以前,首先補充一下getClassinstanceof究竟是什麼:

  • obejct.getClass():返回此object的運行時類Class(Java中有一個類叫Class)。好比一個Person變量p,則p.getClass()返回的就是Person這個類的Class對象,Class類提供了不少方法來獲取這個類的相關信息
  • obejct instanceof ClassName:用來在運行時指出這個對象是不是這個特定類或者是它的子類的一個實例,好比manager instanceof Employee是返回true

好了,讓咱們回到原書吧。

若是隱式和顯示的參數不屬於同一個類,equals方法如何處理呢?

有許多程序員喜歡使用instanceof來進行檢測:

if(!otherObject instanceof Employee) return false;

這樣作不但沒有解決otherObject是子類的狀況,而且還可能招致一些麻煩。

Java語言規範要求equals方法具備下面的特性:

  • 自反性:任何非空引用xx.equals(x)應返回true
  • 對稱性:任何引用xyy.equals(x)返回true,則x.equals(y)也應該返回true
  • 傳遞性:任何引用xyz,若是x.equals(y)返回truey.equals(z)返回true,則x.equals(z)也應該返回true
  • 一致性:若是xy引用對象沒有發生變化,反覆調用x.equals(y)應該返回一樣結果
  • 任意非空引用xx.equals(null)應該返回false

從兩個不一樣的狀況看一下這個問題:

  • 若是子類可以擁有本身的相等概念,則對稱性需求將強制採用getClass進行檢測
  • 若是由超類決定相等的概念,那麼就可使用instanceof進行檢測,這樣能夠在不一樣子類的對象之間進行相等的比較

給出一個編寫完美equals方法的建議:

  1. 顯示參數命名爲otherObejct,稍後將它轉換成另外一個叫作other的變量
  2. 檢測thisotherObject是否因用同一個對象:

    if(this == otherObject) return true;
  3. 檢測otherObject是否爲null

    if(otherObject == null) return false;
  4. 比較thisotherObject是否屬於同一個類

    // 若是equals語義在每一個子類中有改變,就用getClass
    if(getClass() != otherObject.getClass()) return false;
    // 若是子類擁有統一的語義,就用instanceof檢測
    if(!(otherObejct instanceof ClassName)) return false;
  5. otherObejct轉換爲相應的類類型變量:

    ClassName other  = (ClassName) otherObejct;
  6. 開始進行域的比較,使用==比較基本類型域,使用Objects.equals比較對象域

    return field1 == other.field1
      && Objects.equals(field2, other.field2)
      && ...;

若是在子類中從新定義equals,還要在其中包含調用super.equals(other)

另外,對於數組類型的域,可使用靜態的Array.equals方法檢測相應的數組元素是否相等。

hashCode方法

散列碼(hash code)是由對象導出的一個整數值。

散列碼是沒有規律的,若是xy是兩個不一樣的對象,x.hashCode()y.hashCode()基本上不會相同。

對於String類而言,字符串的散列碼是由內容導出的。

因爲hashCode方法定義在Object類中,所以每一個對象都有一個默認的散列碼,其值爲對象的存儲地址。

若是從新定義 equals方法,就必須從新定義 hashCode方法,以便用戶能夠將對象插入到散列表中。

hashCode方法應該返回一個整數數值(也能夠是負數),併合理地組合實例域的散列碼,以便讓各個不一樣的對象產生的散列碼更加均勻。

例如,Employee類的hashCode方法:

public class Employee
{
  public int hashCode()
  {
    return 7 * name.hashCde()
      + 11 * new Double(salary).hashCode()
      + 13* hireDay.hashCode();
  }
}

不過若是使用null安全的方法Objects.hashCode(...)就更好了,若是參數爲null,這個方法返回0。

另外,使用靜態方法Double.hashCode(salary)來避免建立Double對象。

還有更好的作法,須要組合多個散列值時,能夠調用Objects.hash並提供多個參數。

public int hashCode()
{
  return Obejcts.hash(name, salary, hireDay);
}
equalshashCode的定義必須一致:若是 x.equals(y)返回 true,那麼 x.hashCode()就必須與 y.hashCode()具備相同的值。

toString方法

Object中還有一個重要的方法,就是toString方法,它用於返回表示對象值的字符串。

絕大多數(但不是所有)的toString方法都遵循這樣的格式:類的名字,隨後是一對方括號括起來的域值。

public String toString()
{
  return getClass().getName()
    + "[name=" + name
    + ",salary=" + salary
    + ",hireDay=" + hireDay
    + "]";
}

toString方法也能夠供子類調用。

固然,設計子類的程序員也應該定義本身的toString方法,並將子類域的描述添加進去。

若是超類使用了getClass().getName(),子類只須要調用super.toString()便可。

public class Manager extends Employee
{
  ...
  public String toString()
  {
    return super.toString()
      + "[bonus=" + bonus
      + "]";
  }
}

如今,Manager對象將打印輸出以下所示內容:

Manager[name=...,salary=...,hireDay=...][bonus=...]

注意這裏在子類中調用的super.toString(),不是在超類Employee中調用的麼?爲何打印出來的是Manager

由於getClass正如前面所說,獲取的是這個對象運行時的類,與在哪一個類中調用無關。

若是任何一個對象x,調用System.out.println(x)時,println方法就會直接調用x.toString(),並打印輸出獲得的字符串。

Object類定義了toString方法,用來打印輸出對象所屬類名和散列碼

System.out.println(System.out)
// 輸出 java.io.PrintStream@2f6684

這樣的結果是PrintStream類設計者沒有覆蓋toString方法。

對於一個數組而言,它繼承了object類的toString方法,數組類型按照舊的格式打印:

int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 };
String s = "" + luckyNumbers;
// s [I@1a46e30

前綴[I代表是一個整形數組,若是想要獲得裏面內容的字符串,應該使用Arrays.toString

String s = Arrays.toString(luckyNumbers);
// s [2,3,5,7,11,13]

若是想要打印多維數組,應該使用Arrays.deepToString方法。

強烈建議爲自定義的每個類增長 toString方法。

泛型數組列表

在許多程序設計語言中,必須在編譯時就肯定整個數組大小。

在Java中,容許運行時肯定數組的大小

int actualSize = ...;
Employee[] staff = new Employee[actualSize];

固然,這段代碼並無徹底解決運行時動態更改數組的問題。一旦肯定了大小,想要改變就不容易了。

在Java中,最簡單的解決方法是使用Java中另外一個被稱爲ArrayList的類,它使用起來有點像數組,但在添加或刪除元素時,具備自動調節數組容量的功能,而不須要爲此編寫任何代碼。

ArrayList是一個採用類型參數(type paraneter)的泛型類(generic class)。爲了指定數組列表保存的元素對象類型,須要用一對尖括號將類名括起來加在後面,例如ArrayList<Employee>

ArrayList<Employee> staff = new ArrayList<Employee>();
// 兩邊都是用參數有些繁瑣,在Java SE 7中,能夠省去右邊的類型參數
ArrayList<Employee> staff = new ArrayList<>();

這通常叫作「菱形語法」(<>),能夠結合new操做符使用。

若是賦值給一個變量,或傳遞到某個方法,或者從某個方法返回,編譯器會檢查這個變量、參數或方法的泛型類型,而後將這個類型放在<>中。

在這個例子中,new ArrayList<>()將賦值給一個類型爲ArrayList<Employee>的變量,因此泛型類型爲Employee

使用add方法能夠將元素添加到數組列表中。

staff.add(new Employee(...));

數組列表管理着對象引用的一個內部數組,最終數組空間有可能被用盡,這時數組列表將會自動建立一個更大的數組,並將全部的對象從較小數組中拷貝到較大數組中。

也能夠肯定存儲的元素數量,在填充數組前調用ensureCapacity方法:

// 分配一個包含100個對象的內部數組
// 在100次調用add時不用再每次都從新分配空間
staff.ensureCapacity(100);
// 固然也能夠經過把初始容量傳遞給構造器實現
ArrayList<Employee> staff = new ArrayList<>(100);

size方法返回數組列表包含的實際元素數目:

staff.size()

一旦可以確認數組列表大小再也不發生變化,能夠調用trimToSize方法。這個方法將存儲區域的大小調整爲當前元素數量所須要的存儲空間數目,垃圾回收器將回收多餘的存儲空間。

訪問數組列表元素

數組列表自動擴展容量的便利增長了訪問元素語法的複雜程度。

須要使用getset方法實現或改變數組元素的操做,而不是[index]語法格式。

staff.set(i, harry);
Employee e = staff.get(i);

當沒有泛型類時,原始的ArrayList類提供的get方法別無選擇只能返回Object,所以,get方法的調用者必須對返回值進行類型轉換:

Employee e = (Employee) staff.get(i);

固然仍是有一個比較方便的方法來靈活擴展又方便訪問:

ArrayList<X> list = new ArrayList<>();
while(...)
{
  x = ...;
  list.add(x);
}
X[] a = new X[list.size()];
// 使用toArray方法把數組元素拷貝到一個數組中
list.toArray(a);

還能夠在數組列表的中間插入元素:

int n = staff.size()/2;
staff.add(n, e);

固然也能夠刪除一個元素:

Employee e = staff.remove(n);

可使用for each循環遍歷數組列表:

for(Employee e : staff)
  do sth with e

對象包裝器與自動裝箱

有時須要將int這樣的基本類型轉換爲對象,全部基本類型都有一個與之對應的類。

例如,Integer類對應基本類型int,一般這些類稱爲包裝器(wrapper)。

這些對象包裝器有很明顯的名字:IntegerLongFloatDoubleShortByteCharacterVoidBoolean(前6個類派生於公共超類Number)。

對象包裝器類是不可變的,即一旦構造了包裝器,就不容許更改包裝在其中的值。

同時,對象包裝器類仍是final,所以不能定義它們的子類。

有一個頗有用的特性,便於添加int類型的元素到ArrayList<Integer>中。

ArrayList<Integer> list = new ArrayList<>();
list.add(3);
// 這裏將自動地變爲
list.add(Integer.valueOf(3));

這種變換被稱爲自動裝箱(autoboxing)。

相反地,將一個Integer對象賦給一個int值時,將會自動地拆箱。

int n = list.get(i);
// 將會被翻譯成
int n = list.get(i).intValue();

在算術表達式中也能自動地裝箱和拆箱,例如自增操做符應用於一個包裝器引用:

Integer n = 3;
n++;

編譯器自動地插入一條對象拆箱指令,而後自增,而後再結果裝箱。

==雖然也能夠用於對象包裝器對象,但通常檢測是對象是否指向同一個存儲區域。

Integer a = 1000;
Integer b = 1000;
if(a == b) ...;

然而Java中上面的判斷是有可能(may)成立的(這也太玄學了),因此解決辦法通常是使用equals方法。

還有一些須要強調的:

  • 包裝器引用能夠爲null,因此自動裝箱可能會拋出NullPointerException異常
  • 若是條件表達式中混用IntegerDouble類型,Integer值就會拆箱,提高爲double,再裝箱爲Double
  • 裝箱和拆箱是編譯器承認的,而不是虛擬機,編譯器在生成類字節碼時,插入必要的方法調用,虛擬機只是執行這些字節碼(就至關於一個語法糖吧)。

使用數值對象包裝器還有另一個好處,能夠將某些基本方法放置在包裝器中,好比,將一個數字字符串轉換成數值。

int x = Integer.parseInt(s);

參數數量可變的方法

Java SE 5之前的版本中,每一個Java方法都有固定數量的參數,然而如今的版本提供了可變的參數數量調用的方法。

好比printf方法的定義:

public class PrintStream
{
  public PrintStream printf(String fmt, Object... args)
  {
    return format(fmt, args);
  }
}

這裏的省略號...是Java代碼的一部分,代表這個方法能夠接收任意數量的對象(除fmt參數外)。

實際上,printf方法接收兩個參數,一個是格式字符串,另外一個是Object[]數組,其中保存着全部的參數。

編譯器須要對printf的每次調用進行轉換,以便將參數綁定到數組上,並在必要的時候進行自動裝箱:

System.out.printf("%d %s", new Object[]{ new Integer(n), "widgets" });

用戶也能夠自定義可變參數的方法,並將參數指定爲任意類型,甚至基本類型。

// 找出最大值
public static double max(double... values)
{
  double largest = Double.NEGATIVE_INFINITY;
  for(double v : values) if(v > largest) largest = v;
  return largest;
}

枚舉類

public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
實際上這個聲明定義的類型是一個類,它恰好有4個實例。

所以比較兩個枚舉類型值時,不須要調用equals,直接使用==就能夠了。

若是須要的話,能夠在枚舉類型中添加一些構造器、方法和域,構造器只在構造枚舉常量的時候被調用。

public enum Size
{
  SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");

  private String abbreviation;

  private Size(String abbreviation)
  {
    this.abbreviation = abbreviation;
  }

  public String getAbbreviation()
  {
    return abbreviation;
  }
}

全部的枚舉類型都是Enum類的子類,他們集成了這個類的許多方法,最有用的一個是toString,這個方法能返回枚舉常量名,例如Size.SMALL.toString()返回"SMALL"

toString的逆方法是靜態方法valueOf

Size s = Enum.valueOf(Size.class, "SMALL");

s設置成Size.SMALL

每一個枚舉類型都有一個靜態的values方法,返回一個包含所有枚舉值的數組。

Sizep[] values = Size.values();

ordinal方法返回enum聲明中枚舉常量的位置,位置從0開始技術。

反射

反射是一種功能強大且複雜的機制,使用它的主要人員是工具構造者,而不是應用程序員。

因此這部分先跳過,將會在之後一個專題單獨來講明。

繼承的設計技巧

  1. 將公共操做和域放在超類
  2. 不要使用受保護的域
  3. 使用繼承實現"is-a"關係
  4. 除非全部繼承的方法都有意義,不然不要使用繼承
  5. 在覆蓋方法時,不要改變預期的行爲,不要偏離最初的設計想法
  6. 使用多態,而非類型信息
  7. 不要過多地使用反射

Java繼承總結

  • 子類(定義、構造器、方法覆蓋)
  • 繼承層次
  • 多態
  • 方法調用的過程細節
  • final類和方法
  • 強制類型轉換
  • 抽象類
  • protected受保護訪問
  • Object全部類的超類
  • equals方法
  • 相等測試與繼承
  • hashCode方法
  • toString方法
  • 泛型數組列表
  • 對象包裝器與自動裝箱
  • 參數數量可變的方法
  • 枚舉類
  • 繼承設計技巧

我的靜態博客:

相關文章
相關標籤/搜索