Java核心技術(卷I)第四~六章
面向對象設計部分 OOP
第四章 對象與類
- 類之間的關係有
uses-a: 依賴,一個類的方法操縱另一個類的對象。
has-a:聚合,一個類的對象包含另一個類的對象。
is-a:繼承,用於表示特殊和一般關係。 - 構造器( constructor ):構造新實例,應和類名相同,在構造器前面加上 new 操作符用於構造對象。
Date birthday = new Date()
注意:Date deadline
只是定義了一個對象變量,她可以引用Date
類型的對象,但deadline本事不是對象。在Java中,任何對象變量的值都是對存儲在另一個地方的一個對象的引用,new 操作符的返回值也是一個引用。可以顯示地將對象變量設置爲 null 表明這個對象變量目前沒有引用任何值。
另外,局部變量不會自動初始化爲null。不能對已經存在的對象調用構造器達到重新設置實例域的目的。不要在構造器中定義與實例域重名的局部變量。
如果類中提供了至少一個構造器,但是沒有提供無參數的構造器,則在構造對象的時候,如果沒有提供參數就被視爲不合法。
調用另一個構造器:如果構造器的第一個語句行如this(…),那麼這個構造器將調用同類中的另一個構造器。
public Employee{
this('Employee #' + nextId, s);
nextId++;
}
- 更改器( mutator method )和訪問器(accessor method)方法:對實例做出修改的叫做更改器方法,僅訪問實例域而不進行修改的叫做訪問器方法。通常在訪問器前面加 get, 在更改器前面加get。
- 隱式( implicit )參數和顯式( explicit )參數:
public void raiseSalary(double byPercent){
salary += salary*byPercent/100; }
第一個參數稱爲隱式參數,是出現在方法名之前的類對象,關鍵字 this 表示隱式參數;第二個參數是方法名後面括號中的數值,顯式參數。 - 在封裝時,不要編寫返回引用可變對象的訪問器方法。如果要返回一個可變對象的引用,應先對其 克隆( clone ), 對象的克隆是指存放在另一個位置上的對象副本。
- final實例域:final修飾基本( primitive )類型域,或不可變( immutable )類型域。構建對象時必須在每一個構造器執行之後初始化這樣的域。
- 靜態域和靜態方法:如果將域定義爲static, 那麼每個類中只有一個這樣的域,每一個對象對於所有的實例域都有一份自己的拷貝。即使沒有對象,靜態域也存在,它屬於類,而不屬於任何對象。
靜態方法是一種不能向對象操作的方法,如Math.pow(x, a)
,沒有隱式參數。因爲靜態方法不能操作對象,所以不能再靜態方法中訪問實例域,但可以訪問自身類中的靜態域。我們建議使用類名來調用靜態方法。
使用靜態方法的兩種情況:
- 一個方法不需要訪問對象狀態,其所需參數都是通過顯示參數提供。
- 一個方法只需要訪問類的靜態域。
- 工廠( Factory )方法:
- 方法參數:Java總是採用按值調用( call by value )。方法參數共有兩種類型:基本數據類型和對象引用。方法得到的是對象引用的拷貝,對象引用及其拷貝同時引用同一個對象。
Java 中方法參數的使用情況:
- 一個方法不能修改一個基本數據類型的參數。
- 一個方法可以改變一個對象的參數。
- 一個方法不能讓對象參數引用一個新的對象。
- 顯式域初始化:可以在類定義中直接將一個值付給任何域,也可以調方法對域進行初始化。
- 初始化塊( initialization block ):在一個類的聲明中,可以包含多個代碼塊,只要構造類的對象,這些塊就會自動執行,建議將初始化塊放在域定義之後。
靜態的初始化塊:將代碼放在一個塊中,並標記關鍵字static。 - 可以爲一個類添加 finalize 方法,這個方法將在垃圾回收器清除對象之前調用。但在實際應用中,不要依賴使用這個方法回收任何短缺資源,因爲很難知道這個方法什麼時候被調用。
- 包 (package):使用包的主要原因是確保類名的唯一性。
- 類的導入:
- 第一種方式是在每個類名前添加完整的包名。
java.util.Date today = new java.util.Date();
- 使用import語句導入一個特定的類或者整個包。
import java.util.*;
只能使用星號導入一個包。
import還增加了導入靜態方法和靜態域的功能。
`import static java.lang.System.*;
- 第一種方式是在每個類名前添加完整的包名。
- 要想將一個類放到包中,就要將包的名字放在源文件的開頭。
package com.horstmann.corejava
如果沒有在源文件中放置package中,這個源文件的類就被放置到一個默認的包中( default package ),沒有名字。 - 文檔註釋( javadoc ):可以由源文件生成一個HTML文檔。在源代碼中添加以專用的定界符/* 開始的註釋。每個/*…*/文檔註釋在標記後緊跟自由格式文本( free-form text ),標記由 @ 開始, 自由文本的第一句應該是一個概要性的句子。
方法註釋:
- @param:變量描述。可以佔據多行並用HTML描述,一個方法的所有@param標記必須放在一起。
- @return:可以佔據多行並用HTML描述
- @ throws:添加一個註釋,表示這個方法有可能拋出異常。
通用註釋: - @author 姓名:產生一個作者條目
- @version 文本:產生一個版本條目
- @since 文本:產生一個始於條目,可以是對引入特性的描述
- @deprecated 文本:對類、方法或變量添加一個不再使用的註釋,文本中給出取代的建議。
- @see 引用:這個標記將在”see also”部分添加一個超級鏈接。可用於類或方法中。
@see com.horstmann.corejava.Employee#raiseSalary(double)
一定要使用要使用#分割類名與方法名,或類名與變量名。
如果@see之後有一個 < 字符就要制定一個超鏈接,可以鏈接到任何URL。
@see <a href="www.horstmann.com/corejava.html">The Core Java home page </a>
如果@see之後有一個雙引號(“)字符,文本就會顯示在see also 之後。 - @link:可以在註釋的任何位置放置指向其他類或方法的超級鏈接,以及插入一個專用的標記。
{@link package class#feature label}
- 類註釋:必須放在import語句之後,類定義之前。
域註釋:只需要對公有域( 通常指的是靜態常量 )進行註釋。 - 產生包註釋:
- 提供一個以package.html命名的HTML文件,在標記…之間的所有文本都會被抽取出來。
- 提供一個以package-info.java命名的java文件。
第五章 繼承
- 使用關鍵字 extends 表示繼承,所有的繼承都是共有繼承。
class Manager extends Employee{}
- 使用關鍵字 super 指示編譯器調用超類方法。
double baseSalary = super.getSalary();
super在構造器中使用。使用super調用構造器的語句必須是子類構造器的第一條語句。如果子類沒有顯式調用超類構造器,則將自動調用超類默認構造器。
super(n, s, year, month, day);
- 一個變量對象可以指示多種實際類型的現象被稱作多態( polymorphism ), 在運行司能夠自動地選擇調用那個方法的現象叫做動態綁定( dynamic binding ).
- 繼承層次( inheritance hierarchy ):有一個公共超類派生出來的所有兩類的集合。在繼承層次中,從某個特定的類到其祖先的路徑被稱爲該類的繼承鏈。
- “is-a”是一種置換法則,表示程序中出現超類對象的任何地方都可以用子類對象置換。
Employee e;
e = new Employee();
e = new Manager();
這個例子:
Manager boss = new Manager()
Employee[] staff = new Employee[3];
staff[0] = boss;
在這個例子中,變量staff[0]與boss引用同一個對象,但編譯器將staff[0]看做Employee。 - 在覆蓋方法時,如果子類中定義了一個與超類簽名相同的方法,那麼子類中的這個方法就覆蓋了超類中的相同簽名方法,允許子類將覆蓋方法的返回類型定義爲原返回類型的子類型。此外,子類方法不能低於超類方法的可見性,特別是,如果超類方法時 public,子類方法一定要聲明爲 public。
public Employee getBuddy(){...}
public Manager getBuddy(){...}
- 方法表( method table ):虛擬機預先爲每個類創建了一個方法表,其中列出了所有方法的簽名和實際調用的方法。這樣在調用方法時只需查表即可。
- 對象的強制類型轉換:僅需要用一對圓括號將目標類名括起來,並放置在需要轉換的對象引用之前即可。只能在繼承層次內進行類型轉換。在將超類轉換成子類之前,應該使用 instanceof 進行檢查。注:一般情況下應儘量少使用類型轉換和instanceof運算符。
Manager boss = (Manager) staff[0];
- 抽象類:使用 abstract 關鍵字。
public abstract String getDescription(); #不需要實現
包含一個或者多個抽象方法的類本身必須被聲明爲抽象類。即使類不含有抽象方法,也可以將類聲明爲抽象類。
abstract class Person{ public abstract String getDescription(); }
除了抽象方法外,抽象類還可以包含具體數據和方法。
抽象類不能被實例化,但可以定義一個抽象類的對象變量,但它只能引用非抽象子類的對象。 - protected:對本包和子類可見。
private:僅對本類可見。
public:對所有類可見。
默認:對本包可見,不需要修飾符。 - Object:所有類的超類。如果沒有明確指出超類,Object類就被當做是這個類的超類。可以私用Object類型變量引用任何類型的對象。
Object obj = new Employee("Harry Hacker", 35000);
- 完美 equals 的建議:
- 顯式參數命名爲otherObject,稍後需要將它轉換成叫做other的變量。
- 檢測this和other是否引用用一個對象
if(this == otherObject) return true;
- 檢測otherObject是否爲null,是的話返回false
if(otherObject == null) return false;
- 比較this和otherObject是否屬於同一個類,如果equals的語句在每個子類中有所改變,就使用getClass檢測;或者說子類擁有自己的相等概念,則對稱性強制採用getClass進行檢測。
if(getClass() != otherObject.getClass()) return false;
如果所有子類都有統一的語義,使用instanceof檢查。或者說如果由超類決定相等的概念,就使用instanceof進行檢測。
if(!(otherObject instanceof ClassName)) return false;
- 將otherObject轉換爲相應的類類型變量。
ClassName other = (ClassName)otherObject;
- 現在開始對所有需要比較的域進行比較,使用==比較基本數據類型,使用equals比較對象域。
return field1 == other.field1 && Object.equals(field2, other.field2);
- Object.hashCode 方法:散列碼是由對象導出的一個整數值(可以使負數)。在Java 7中,最好使用null安全的Object.hashCode,如果參數爲null,這個方法返回0。該方法還可以提供多個參數,對各個參數調用Object.hashCode,並組合這些散列值。
return Object.hashCode(name, salary, hireDay);
- o.toString方法:絕大多數的toString方法都遵循這樣的格式:類的名字,隨後是一對方括號括起來的值域。
- 泛型數組列表:ArrayList這個採用參數類型( type parameter )的泛型類( generic class )。
ArrayList<Employee> staff = new ArrayList<>();
ArrayList<Integer> a = new ArrayList<>();!!!#這裏要使用基本數據類型的包裝器,Integer,而不是int
在Java 7 中可以省去菱形語法右邊尖括號裏的參數。
- 使用, add 方法將語速添加到數組列表中。
staff.add(new Employee("Tony Tester", 30000));
- 使用, ensureCapacity方法在填充數組前分配容量。
staff.ensureCapacity(4);
- 使用, size()方法返回數組列表中包含的實際元素數目。
- 使用, trimToSize()方法將存儲區域的大小調整到當前元素所需要的存儲空間數目。
- 使用, set(int index, E element)方法設置第i個元素。
- 使用, get(int index)方法返回此列表中指定位置上的元素。
- 使用, remove(int index):移除此列表中首次出現的指定元素(如果存在)。
將原始的ArrayList賦值給類型化的ArrayList會得到一個警告。使用類型轉換並不能避免出現警告。
- 使用, add 方法將語速添加到數組列表中。
- 對象包裝器和自動裝箱:所有的基本烈性都有一個與之對應的類,這些類稱爲 包裝器(wrapper),當Integer類型的數組列表獲得或添加值的時候,使用
list.add(Integer.valueOf(3));
這稱爲自動裝箱(autoboxing)。==運算符也可以應用於對象包裝器對象,只不過檢測的是對象是否指向同一個存儲區域。 - 參數數量可變的方法:
public PrintStream printf(String fmt, Object...args){}
這裏…表示這個方法可以接受任意數量的對象。實際上,printf方法接受兩個參數,一個是格式字符串,另一個是Object[]數組,其中保存着所有參數(如果提供的是整形數組或者其他基本類型的值,自動裝箱功能將把它們轉換成對象。) - 枚舉類:所有沒枚舉類都是Enume類的子類。
public enum Size{
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
private String abbreviation;
}
- toString():返回枚舉類的常量名。
Size.SMALL.toString();
- valueOf:toString的逆方法,靜態方法
Size s = Enum.valueOf(Size.class, "SMALL");
- values:靜態方法,返回一個包含全部枚舉值的數組。
Size[] values = Size.values();
- ordinal:返回enum聲明中枚舉常量的位置,從0開始計數。
- toString():返回枚舉類的常量名。
第六章 接口與內部類
- 接口( interface ):接口不是類,而死對類需求的一組描述。接口中所有方法都自動屬於 pubic,因此不必提供關鍵字public, 但在接口的實現中必須將其聲明爲public。接口中的域都將自動被設置爲 public static final。接口中可以定義常量,但接口中決不能含有實例域,也不能在接口中實現方法.
爲了讓類實現接口,通常需要:
- 讓類聲明爲實現給定的接口。使用implements關鍵字
class Employee implements Comparable
- 對接口中的所有方法進行定義。
特性: - 接口不是類,不能使用new運算符。
- 但可以聲明接口的變量,接口變量必須引用實現了接口的類對象。
Comparable x = new Employee();
- 可以使用instanceof檢查一個對象是否實現了某個特定的接口。
if(anObject instanceof Comparable) {}
- 接口允許被拓展
public interface Powered extends Comparable{}
使用 , 將實現的各個藉口分隔開
class Empliyee implements Cloneable, Comparable
- 讓類聲明爲實現給定的接口。使用implements關鍵字
- 對象克隆:默認的克隆操作是淺拷貝,會拷貝隊形中所有屬於數值或者基本類型的與。如果對象包含了子對象的引用,拷貝的結果就會使兩個對象共享這部分信息。如果子對象是可變的,那就要重新定義clone方法以便實現深拷貝。
對每一個類判斷:
- 默認的clone是否滿足要求
- 默認的clone是否能夠條用哪個可變子對象的clone得到修補
- 是否不應該使用clone
要實現上述前兩點,必須 - 實現Cloneable接口
- 使用public訪問修飾符重新定義clone方法。
即使clone默認操作滿足要求,也要實現Cloneable接口,並將其重新定義爲public,並調用super.clone();
class Employee implements Cloneable{
public Employee clone() throws CloneNotSsupportException{
return (Employee)super.clone()
}
}
- 接口與回調( callback ):回調是一種常見的程序設計模式,在這種模式中,可以指出某個特定事件發生時應該採取什麼動作。
- 內部類(inner class):是定義在另一個類中的類。使用內部類的原因
- 內部類可以訪問該類定義所在的作用域中的數據,包括私有數據
- 內部類可以對同一個包中的其他類隱藏起來
- 當想要定義一個毀掉函數且不想編寫大量代碼時,使用匿名( anonymous )內部類比較便捷。
儘管內部類位於類中,但不意味着每個外部類都有一個內部類實例域。
內部類對象總有一個隱式引用,指向創建它的外部類對象。
- 內部類的特殊語法規則:
- OuterClassName.this表示外圍類的引用。
- OuterObject.new InnerClass(construction parameters):更加明確的編寫內部對象的構造器。
- OuterClass.InnerClass:這樣引用內部類
- 局部內部類:是定義在類方法中的類,不能使用public或者private訪問說明符進行聲明。可以對外界世界完全隱藏,即使外部類的其他代碼也不能訪問。局部類不僅可以訪問他們的外部類,還可以訪問被聲明爲final的局部變量。
public void start(int interval, final boolean beep){
class TimePrinter implements ActionListener{
public void actionPerformed(ActionEvent event){
if(beep) {...}
}
}
ActionListener listener = new ActionListener();
}
- 匿名內部類( anonymous inner class ):如果只想穿件這個類的一個對象,就不用命名了。
public static void start(int interval, final boolean final){
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
Date now = new Date();
...
}
}
}
通用語法格式爲
new SuperType(construction parameters){
inner class methods and data
}
由於構造器必須和類名相同,而匿名類沒有類名,這樣,匿名類就不能有構造器。取而代之的是將構造器參數傳遞給超類( superclass )的構造器。尤其在內部類實現接口的時候,不能有任何構造參數。
8. 靜態內部類:使用內部類僅僅爲了把一個類隱藏在另一個類的內部,而不需要內部類引用外圍類對象,爲此,可以將內部類聲明爲 static。當然只有內部類可以聲明爲static。