面向對象程序設計概述
面向對象程序設計(簡稱OOP)是當今主流的程序設計范型,它已經取代了傳統的“結構化”過程化程序開發技術。Java是完全面向對象的,必須熟悉OOP才能夠編寫Java程序。
傳統結構化程序設計主要通過設計一系列的過程(即算法)來解決問題,即算法+數據結構=程序,先要確定如何操作數據,再決定如何組織數據使之便于操作。而面向對象程序設計將數據域(屬性)和對數據的操作(方法)綁定在一個對象中,將數據放在第一位,然后再考慮操作數據的算法。
對于規模較小的問題將其分解為過程開發較為理想,而對于規模較大的問題使用OOP比較理想,比如出現錯誤,在集成在對象的方法中尋找錯誤比在眾多零散過程中查找更容易。
類和對象
類:類是構造對象的模板或藍圖,用于定義對象的數據域和方法。一個java源文件中只能有一個公共類,且類名與文件名相同。編譯源文件時每個類(包括接口、枚舉、內部類)都生成一個.class文件。如果A類使用B類,稱A類是B類的客戶。
對象:由類構造的實例,一個類可以構造多個實例。
設計類的簡單規則
先從設計類的數據域開始,再向類里添加方法。
類之間的關系
1.泛化(Generalization)
A是B和C的父類,B,C具有公共類(父類)A,說明A是B,C的一般化(也稱泛化)。
泛化關系就是繼承關系。如果一個類別A“繼承自”另一個類別B,就把這個A稱為“B的子類別”,而把B稱為“A的父類別”也可以稱“B是A的超類”。繼承可以使得子類別具有父類別的各種屬性和方法,而不需要再次編寫相同的代碼。在令子類別繼承父類別的同時,可以重新定義某些屬性,并重寫某些方法,即覆蓋父類別的原有屬性和方法,使其獲得與父類別不同的功能。另外,為子類別追加新的屬性和方法也是常見的做法。
public class Person
{
protected String name;
protected int age;
public void move()
{
……
}
public void say()
{
……
}
}
public class Student extends Person
{
private String studentNo;
public void study()
{
……
}
}
在UML當中,對泛化關系有三個要求:
1、子類與父類應該完全一致,父類所具有的屬性、操作,子類應該都有
2、子類中除了與父類一致的信息以外,還包括額外的信息
3、可以使用父類的實例的地方,也可以使用子類的實例
2.關聯(Association)
表示不同類對象之間有關聯,這是一種靜態關系,與運行過程的狀態無關,在最開始就可以確定。因此也可以用 1 對 1、多對 1、多對多這種關聯關系來表示。比如客戶和訂單,每個訂單對應特定的客戶,每個客戶對應一些特定的訂單,再如籃球隊員與球隊之間的關聯(下圖所示)。
其中,關聯兩邊的"employee"和“employer”標示了兩者之間的關系,而數字表示兩者的關系的限制,是關聯兩者之間的多重性。通常有“*
”(表示所有,不限),“1”(表示有且僅有一個),“0...”(表示0個或者多個),“0,1”(表示0個或者一個),“n...m”(表示n到m個都可以),“m...*
”(表示至少m個)。
關聯關系(Association) 是類與類之間最常用的一種關系,它是一種結構化關系,用于表示一類對象與另一類對象之間有聯系。在UML類圖中,用實線連接有關聯的對象所對應的類,在使用Java、C#和C++等編程語言實現關聯關系時,通常將一個類的對象作為另一個類的屬性。在使用類圖表示關聯關系時可以在關聯線上標注角色名。
雙向關聯: 默認情況下,關聯是雙向的。
public class Customer
{
private Product[] products;
……
}
public class Product
{
private Customer customer;
……
}
單向關聯: 類的關聯關系也可以是單向的,單向關聯用帶箭頭的實線表示。
public class Customer
{
private Address address;
……
}
public class Address
{
……
}
自關聯: 在系統中可能會存在一些類的屬性對象類型為該類本身,這種特殊的關聯關系稱為自關聯。
public class Node
{
private Node subNode;
……
}
重數性關聯: 重數性關聯關系又稱為多重性關聯關系(Multiplicity),表示一個類的對象與另一個類的對象連接的個數。在UML中多重性關系可以直接在關聯直線上增加一個數字表示與之對應的另一個類的對象的個數。
public class Form
{
private Button buttons[];
……
}
public class Button
{
…
}
3.依賴(Dependence)
和關聯關系不同的是,依賴關系是在運行過程中起作用的。
依賴關系有如下三種情況:
1、A類是B類中某方法的局部變量;
2、A類是B類方法當中的一個參數;
3、A類向B類發送消息,從而影響B類發生變化;
在UML中,依賴關系用帶箭頭的虛線表示,由依賴的一方指向被依賴的一方。
public class Driver
{
public void drive(Car car)
{
car.move();
}
……
}
public class Car
{
public void move()
{
......
}
……
}
4.聚合(Aggregation)
表示的是整體和部分的關系,但整體和部分不是強依賴的,即整體與部分可以分開。聚合關系(Aggregation) 表示一個整體與部分的關系。通常在定義一個整體類后,再去分析這個整體類的組成結構,從而找出一些成員類,該整體類和成員類之間就形成了聚合關系。在聚合關系中,成員類是整體類的一部分,即成員對象是整體對象的一部分,但是成員對象可以脫離整體對象獨立存在。在UML中,聚合關系用帶空心菱形的直線表示。
public class Car
{
private Engine engine;
public Car(Engine engine)
{
this.engine = engine;
}
public void setEngine(Engine engine)
{
this.engine = engine;
}
……
}
public class Engine
{
……
}
如:電話機包括一個話筒;電腦包括鍵盤、顯示器,一臺電腦可以和多個鍵盤、多個顯示器搭配,確定鍵盤和顯示器是可以和主機分開的,主機可以選擇其他的鍵盤、顯示器組成電腦。
5.組合(Composition)
也是整體與部分的關系,但和聚合不同,組合的整體與部分不可以分開。組合關系(Composition)也表示類之間整體和部分的關系,但是組合關系中部分和整體具有統一的生存期。一旦整體對象不存在,部分對象也將不存在,部分對象與整體對象之間具有同生共死的關系。
public class Head
{
private Mouth mouth;
public Head()
{
mouth = new Mouth();
}
……
}
public class Mouth
{
……
}
如:公司和部門,部門是部分,公司是整體,公司A的財務部不可能和公司B的財務部對換,就是說,公司A不能和自己的財務部分開;人與人的心臟。
6.實現(Implementation)
實現是用來規定接口和實線接口的類或者構建結構的關系,接口是操作的集合,而這些操作就用于規定類或者構建的一種服務。
接口之間也可以有與類之間關系類似的繼承關系和依賴關系,但是接口和類之間還存在一種實現關系(Realization),在這種關系中,類實現了接口,類中的操作實現了接口中所 聲明的操作。在UML中,類與接口之間的實現關系用帶空心三角形的虛線來表示。
public interface Vehicle
{
public void move();
}
public class Ship implements Vehicle
{
public void move()
{
……
}
}
public class Car implements Vehicle
{
public void move()
{
……
}
}
耦合程度
依賴、關聯、聚合、組合、泛化、實現的耦合程度依次遞增。
對象與對象變量
對象是調用構造方法在堆上分配內存產生的(用new + 構造方法來調用),而對象變量是在棧上的持有對象引用的變量(聲明方式為:類名 + 對象名)。一個對象可被多個對象變量引用。比如語句Student s = new Student(),new Student()在堆上創建了對象,s是引用變量,包含對于該對象的引用。
該語句完成了下列操作:
1.加載 Student 類,包括:加載、驗證、準備、解析、初始化(如果不是第一次主動引用Student對象,這步省略)
2.在棧內存為 s 開辟空間
3.在堆內存為 Student 對象開辟空間
4.對 Student 對象的成員變量進行默認初始化
5.對 Student 對象的成員變量進行顯式初始化
6.通過構造方法對 Student 對象的成員變量賦值
7.Student 對象初始化完畢,把對象地址賦值給 s 變量(如果不賦值給s變量,new Student()就是一個匿名對象)
更詳細的原理見:深入理解Java虛擬機之Java內存區域與內存溢出異常
大多數情況下,我們可以簡單地說s是一個Student類對象,而不用冗長地說s是一個包含對Student對象引用的變量。在Java中,數組被看作對象,一個數組變量實際上是一個包含數組引用的變量。
存儲區域
1.創建的實例及成員變量(靜、非靜態)在堆中
2.局部變量在棧中
3.類的基本信息和方法定義在方法區
UML類圖
相關知識見深入淺出UML類圖
構造器
構造器用于構造對象實例,并對數據域進行相應初始化,物理層面表現為在堆上為對象的非靜態成員開辟存儲空間。
構造器名應該與類名相同,無返回值,甚至連void也沒有,可以被可見性修飾符(如public)修飾,因為它是用來創建實例的,所以它永遠是實例方法,不能被static修飾。
構造方法可以重載,沒有參數的構造方法稱為無參構造方法或默認構造方法,當類中沒有定義構造方法時,編譯器會自動插入一個方法體為空的默認構造方法,但一旦定義了有參構造方法,該默認構造方法不會被插入。
建議在構造方法中調用各屬性的set方法來初始化屬性,而不是給屬性直接賦值,這樣set方法的合法性檢查也會應用于構造方法。
訪問對象的數據和方法
在面向對象編程中,對象成員可以引用該對象的數據域和方法。在創建一個對象后,它的數據域和方法可以使用點操作符(.)來訪問和調用,該操作符也稱為對象成員訪問操作符。
引用數據域和null值
如果一個引用類型的數據域沒有引用任何對象,那么它的值為null,是一個引用類型直接量。訪問值為null引用變量的數據域或方法會拋出一個NullPointerException。
默認賦值規則
類中的變量如果沒有賦值,會被自動賦予默認值,引用類型默認值為null,byte為(byte)0,short為(short)0,int為0,long為0L,float為0.0f,double為0.0,char為‘\u0000’(空字符,但也占長度),boolean為false。但如果Java沒有給方法里的局部變量賦值,會出現編譯錯誤。
基本變量和引用變量的區別
基本變量類型有byte,short,int,long,float,double,char,boolean八種,其他類型變量都是引用變量。基本變量對應內存所存儲的值是基本類型值,而引用變量存儲的值是一個引用,是對象的存儲地址。將一個變量賦給另一個變量,另一個變量就被賦予同樣的值。對基本類型來說,就是將一個變量的實際值賦給另一個變量;對引用變量來說,就是將一個變量的引用賦給另一個變量,從而兩個變量指向同一個對象。
沒有變量引用的對象會成為垃圾,Java運行系統會檢測垃圾并自動回收它所占的空間,這個過程稱為垃圾回收。如果你認為不再需要某個對象,可以顯式地給該對象變量賦null值,讓它被JVM自動回收。
靜態/非靜態變量、常量和靜態/非靜態方法
靜態變量:又稱類變量,是由一個類的所有實例共享,變量值存儲在一個公共內存地址,描述類的對象的公共屬性,不依賴于具體對象的變量。用關鍵字static表示。不要從構造器中傳入參數來初始化靜態域,最好使用set方法改變靜態數據域。
非靜態變量:又稱實例變量,是依賴于具體對象的變量,變量值存儲在特定對象的內存地址。
常量:類的所有對象共享且不可變的量,用static final修飾,final決定了其不可變性,static決定了它不依賴于具體對象,可以不實例化直接通過類名調用。
靜態方法:無需創建類實例就可以調用的方法,不依賴于具體對象,用關鍵字static表示,其中main方法也是靜態方法。靜態方法在類加載的時候就存在了,它不依賴于任何實例。所以靜態方法必須有實現,也就是說它不能是抽象方法。
非靜態方法:又稱實例方法,是依賴于具體對象的方法。
關系:
靜態數據域或方法既可以通過類訪問,也可以通過對象訪問;非靜態數據域或方法只能通過對象訪問(但為了便于程序可讀性,建議用類名調用靜態成員,用對象名調用非靜態成員)。
靜態方法只能訪問靜態數據域,非靜態方法可以訪問非靜態數據域和靜態數據域。
在同一個類中: 對于靜態方法,其他的靜態或非靜態方法都可以直接調用它。而對于非靜態方法,其他的非靜態方法是可以直接調用它的。但是其他靜態方法只有通過創建對象才能調用它。
工廠方法
靜態方法還有另外一種常見的用途。類似LocalDate和NumberFormat的類使用靜態工廠方法來構造對象。比如NumberFormat使用如下工廠方法生成不同風格的格式化對象:
NumberFormat currencyFormatter = NumberFormat.gerCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.gerPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x));// prints $0.10
System.out.println(percentFormatter.format(x));//prints 10%
使用靜態工廠方法的原因主要有兩個:
1.無法命名構造器。構造器名和類名必須相同,但NumberFormat希望得到的不同實例擁有不同名字
2.當使用構造器時,無法改變所構造的對象類型。而Factory方法將返回一個DecimalFromat類對象,這是NumberFormat的子類
可見性修飾符
public
修飾的成員可以在任何范圍內直接訪問,只是一種最寬松的訪問控制等級。需要注意的,所謂的直接訪問仍需要先創建或獲得一個相應類的對象然后才可以使用”對象名.成員“的方式訪問其屬性或調用其方法,但是出于信息封裝和隱藏的需要一般不提倡把成員聲明為public的,而構造方法和需要外界直接調用的普通方法則適合聲明為public
protected
修飾的成員可以在其所在類中、同一包中及子類中(無論子類在不在同一個包)被直接訪問,但不能在位于不同包中的非子類中被直接訪問
default
缺省訪問修飾符的成員只能在其所在類中或包中直接訪問,在不同包中即使是不同包的子類也不能直接訪問。
private
private成員只能在所在類中被直接訪問,是4種訪問等級最高的一個,建議為了實現類的封裝,實例數據域應該使用private修飾。如果不想讓一個類創建實例,可以用private修飾其構造方法。
注意:private和protected只能修飾類成員,public和default可以修飾類和類成員。public的類成員只有位于public類里才能出包訪問,如果類是default的,也不能出包訪問。
包
包可以用來更好地組織、管理類,在不同的包中可以定義同名的類而不會發生沖突,建議將因特網域名的逆序作為包名,比如域名habitdiary.cn,可以定義包名為cn.habitdiary,還可以進一步定義子包名,比如cn.habitdiary.core,當然包和子包的這種嵌套關系只是為了邏輯結構更加嚴謹,在編譯器看來這兩個包是互相獨立的集合。為了把類放入包中,需要在程序中首先出現語句package + 包名
,前面不能有注釋或空白。如果定義類時沒有指定包,就表示把它放在默認包中,建議最好將類放入包中,不要使用默認包。源文件應該放到與完整包名匹配的子目錄下,比如cn.habitdiary.core應該放在子目錄cn/habitdiary/core下,編譯器會把編譯得到的.class文件放在同一目錄下。
類的導入
如果一個A類要使用不在同一個包下的B類,必須使用import關鍵字導入B類,當然B類能被導入的前提是在包外可見,即使用public修飾。
精確導入:導入某個包的特定類,如import java.util.Scanner
通配導入:導入某個包的所有類,如import java.util.*
編譯器定位類會按照配置的環境變量找到類庫,再根據導入路徑在相應的包中定位相應的類。
靜態導入:import語句不僅可以導入類,還可以導入靜態方法和靜態域,只要加入static關鍵字。比如import static java.lang.System.*
導入了System類的靜態方法和靜態域,就可以不加類名前綴:out.println("Hello world!")
,out是System類里定義的靜態成員,是PrintStream的實例。
注意:
1、在要使用某個類時可以不導入,但要采用包名.類名的方式使用這個類。
2、不可以使用精確導入導入兩個包中的同名類,此時應該一個類精確導入,另一個類通配導入。程序默認使用的類是精確導入的類,如果要使用通配導入的同名類,要使用包名.類名的方式。
3、使用通配導入的時候不能跨級導入,比如在cn.habitdiary.core
包中的類只能通過cn.habitdiary.core.*
來導入,而不能通過cn.habitdiary.*
來導入。
數據域封裝
封裝,即隱藏對象的屬性和實現細節,僅對外公開接口,用戶無需知道對象內部的細節,但可以通過對象對外提供的接口來訪問該對象。封裝通過控制在程序中屬性的讀和修改的訪問級別,避免了對數據域的直接修改,提高了數據的安全性和易維護性。實現封裝的關鍵是絕不能讓類中的方法直接訪問其他類的實例域,程序僅通過方法和數據進行交互。
實現封裝的步驟:
1.用private可見性修飾符修飾類成員
2.設置訪問器(get方法),方法簽名習慣為:public returnType getPropertyName(),如果返回值類型是boolean型,習慣下定義為:public boolean isPropertyName()
3.設置修改器(set方法),方法簽名習慣為:public void setPropertyName(dataType propertyValue)
優點:
- 減少耦合:可以獨立地開發、測試、優化、使用、理解和修改
- 減輕維護的負擔:可以更容易被程序員理解,并且在調試的時候可以不影響其他模塊
- 有效地調節性能:可以通過剖析確定哪些模塊影響了系統的性能
- 提高軟件的可重用性
- 降低了構建大型系統的風險:即使整個系統不可用,但是這些獨立的模塊卻有可能是可用的
以下 Person 類封裝 name、gender、age 等屬性,外界只能通過 get() 方法獲取一個 Person 對象的 name 屬性和 gender 屬性,而無法獲取 age 屬性,但是 age 屬性可以供 work() 方法使用。
注意到 gender 屬性使用 int 數據類型進行存儲,封裝使得用戶注意不到這種實現細節。并且在需要修改 gender 屬性使用的數據類型時,也可以在不影響客戶端代碼的情況下進行。
public class Person {
private String name;
private int gender;
private int age;
public String getName() {
return name;
}
public String getGender() {
return gender == 0 ? "man" : "woman";
}
public void work() {
if (18 <= age && age <= 50) {
System.out.println(name + " is working very hard!");
} else {
System.out.println(name + " can't work any more!");
}
}
}
向方法傳遞對象參數
可以將對象傳遞給方法,同傳遞數組一樣,傳遞對象實際上是傳遞對象的引用。Java只有一種參數傳遞方式:值傳遞。只不過引用類型(包括對象、普通數組、對象數組)變量的值是引用,引用上傳值的最好描述為傳共享。
以下代碼中 Dog dog 的 dog 是一個指針,存儲的是對象的地址。在將一個參數傳入一個方法時,本質上是將對象的地址以值的方式傳遞到形參中。因此在方法中改變指針引用的對象,那么這兩個指針此時指向的是完全不同的對象,一方改變其所指向對象的內容對另一方沒有影響。
public class Dog {
String name;
Dog(String name) {
this.name = name;
}
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
String getObjectAddress() {
return super.toString();
}
}
public class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
System.out.println(dog.getObjectAddress()); // Dog@4554617c
func(dog);
System.out.println(dog.getObjectAddress()); // Dog@4554617c
System.out.println(dog.getName()); // A
}
private static void func(Dog dog) {
System.out.println(dog.getObjectAddress()); // Dog@4554617c
dog = new Dog("B");
System.out.println(dog.getObjectAddress()); // Dog@74a14482
System.out.println(dog.getName()); // B
}
}
但是如果在方法中改變對象的字段值會改變原對象該字段值,因為改變的是同一個地址指向的內容。
class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
func(dog);
System.out.println(dog.getName()); // B
}
private static void func(Dog dog) {
dog.setName("B");
}
}
對象數組和普通數組
所有數組變量存儲的都是對數組的引用,但是普通數組里存儲的就是實際的值,對象數組里存儲的則還是對象的引用,而非對象本身,其類似于二維數組有兩層的引用關系,對象數組元素的初始值默認為null。
不可變對象和類
一旦創建之后內容就不可改變的對象是不可變對象,它的類稱為不可變類。
一個對象是不可變的要滿足以下三個條件:
1、對象創建以后其狀態就不能修改(將所有域設為private,且不對外提供setter方法,getter方法也不能返回指向可變數據域的引用)
2、對象的所有域都是final類型
3、對象是正確創建的(在對象的創建期間this引用沒有逸出)
變量作用域
一個類的實例變量和靜態變量的作用于是整個類,不論在何處聲明,所以類的變量和方法可以在類中以任意順序出現。但是當一個變量初始化要基于另一個變量時不是這樣。比如
public class F{
private int j = i + 1;
private int i;
}
就是錯誤的,因為j的初始化基于i已經被初始化的前提。
局部變量(包括方法中聲明的變量和形參等)的作用域則從聲明開始到包含該變量的塊結束處。如果一個局部變量和類成員變量有相同的名字,則局部變量優先,同名的成員變量被隱藏。可以通過this引用顯示隱藏的成員變量。
建議:在聲明類的變量和方法時,最好按照:數據域 —— 構造方法 —— 普通方法的順序,且最好在數據域和方法之間空一行,提高程序可讀性。
this引用
this關鍵字有兩大作用:
1.表示指向調用對象本身的引用名
2.可以在構造方法內部調用同一個類的其他構造方法,此時this(參數列表)
語句應該出現在構造方法其他語句之前,如果一個類有多個構造方法,最好盡可能使用this(參數列表)
的形式來實現它們。這樣做可以簡化代碼,使類易于維護。
對象構造
默認域初始化
即依賴編譯器對數據域的默認初始化。
顯式域初始化
在數據域的定義處進行賦值,可以在執行構造器之前,先執行賦值操作。當一個類的所有構造器都希望把相同的值賦予某個特定實例域時,這種方法特別有用。初始值不一定是常量值,可以調用方法返回一個值對域進行初始化。
無參數的構造器
即將對數據域的初始化置于一個無參的構造器中。
有參數的構造器
即給構造器傳入參數對數據域進行初始化
初始化塊
在方法內的代碼塊稱為普通代碼塊,就是一組用花括號括起來的語句,沒有特殊含義。而如果用花括號包含的一組數據域賦值代碼塊出現在類內,就稱為初始化塊或構造代碼塊。初始化塊一般在數據域聲明處之后,構造器之前。如果在初始化塊前加static關鍵字,并在塊內初始化靜態數據域,就成了靜態初始化塊,不允許在靜態初始化塊內初始化實例成員。
初始化順序
1.初始化主類(main方法所在類),主類的靜態成員和靜態初始化塊,按在代碼中出現的順序一次執行(如果主類有父類先加載父類)
2.父類靜態成員和靜態初始化塊,按在代碼中出現的順序依次執行(classLoader的類加載過程)。
3.子類靜態成員和靜態初始化塊,按在代碼中出現的順序依次執行(classLoader類加載過程)。
4.父類的實例成員、實例初始化塊,按在代碼中出現的順序依次執行。
5.父類構造方法
6.子類實例成員、實例初始化塊,按在代碼中出現的順序依次執行。
7.子類構造方法
注意:靜態成員和靜態初始化塊只在該類被加載的時候初始化一次,可以看作初始化類。當某類被主動引用時初始化該類及其父類。
比如下面的例子:
SuperClass.java
public class SuperClass {
public static int a = 1;
private int aa = 5;
static{
System.out.println(a);
System.out.println(2);
}
public SuperClass(){
System.out.println(7);
}
{
System.out.println(aa);
System.out.println(6);
}
}
SubClass.java
public class SubClass extends SuperClass {
public static int b = 3;
private int bb = 8;
static{
System.out.println(b);
System.out.println(4);
}
public SubClass(){
System.out.println(10);
}
{
System.out.println(bb);
System.out.println(9);
}
}
Main.java
public class Main {
static {
System.out.println("Main static");
}
public static void main(String[] args) {
System.out.println("Main");
SubClass b = new SubClass();
}
}
運行結果如下:
關于類加載機制詳細解釋見:深入理解Java虛擬機之虛擬機類加載機制
文檔注釋
JDK 包含一個很有用的工具 , 叫做 javadoc , 它可以由源文件生成一個 HTML 文檔 。我們平時查閱的API就是這樣形成的,而且添加文檔注釋后,eclipse也會對添加了文檔注釋的方法等給出智能提示。
注釋的插入
javadoc 實用程序 ( utility ) 從下面幾個特性中抽取信息:
- 包
- 公有類與接口
- 公有的和受保護的構造器及方法
- 公有的和受保護的域
應該為上面幾部分編寫注釋。注釋應該放置在所描述特性的前面 。注釋以 /**
開始, 并以 */
結束 。插入文檔注釋的方法是輸入/**
后回車即可。
每個 /** . . . */
文檔注釋在標記之后緊跟著自由格式文本 (free-form text)。標記由@
開始, 如 @ author
或 @ param
。
自由格式文本的第一行或幾行是關于類、變量和方法的主要描述。javadoc 實用程序自動地將這些句子抽取出來形成概要頁 。
在自由格式文本中, 可以使用 HTML 修飾符 , 例如 , 用于強調的 <em> ... </em>
、用于著重強調的<strong> ... </strong>
以及包含圖像的< img.../> 等 。 不過, 一定不要使用<hl>
或<hr>
,因為它們會與文檔的格式產生沖突 。若要鍵入等寬代碼, 需使用 {@code...}
而不是<code> ... </code>
———— 這樣一來 , 就不用操心對代碼中的 <
字符轉義了 。
類注釋
類注釋必須放在 import 語句之后, 類定義之前 。
下面是一個類注釋的例子:
沒有必要在每一行的開始用星號*
,例如,以下注釋同樣是合法的:
然而,大部分IDE提供了自動添加星號*
,并且當注釋行改變時,自動重新排列這些星號的功能。
方法注釋
每一個方法注釋必須放在所描述的方法之前。除了通用標記之外, 還可以使用下面的標記:
-
@param
變量描述
這個標記將對當前方法的 "param" ( 參數 ) 部分添加一個條目。這個描述可以占據多行,并可以使用 HTML 標記。一個方法的所有@param
標記必須放在一起。 -
@return
描述
這個標記將對當前方法添加 "return" (返回 ) 部分。這個描述可以跨越多行,并可以使用 HTML 標記 。 -
@throws
類描述
這個標記將添加一個注釋,用于表示這個方法有可能拋出異常。
下面是一個方法注釋的示例:
域注釋
只需要對公有域(通常指的是靜態常量)建立文檔。例如:
通用注釋
下面的標記可以用在類文檔的注釋中。
包與概述注釋
注釋的抽取
javadoc 工具將你 Java 程序的源代碼作為輸入,輸出一些包含你程序注釋的HTML文件。
每一個類的信息將在獨自的HTML文件里。javadoc 也可以輸出繼承的樹形結構和索引。
文檔標簽速查:菜鳥教程之Java文檔注釋
類設計技巧
1.一定要保證數據域私有
2.一定要對數據初始化
最好不要依賴默認初始化,會影響程序可讀性。
3.不要在類中使用過多的基本類型
用其他集合相關基本類型的類代替多個基本類型使用
4.不是所有的域都需要獨立的域訪問器和域修改器
有的數據域定義后不必要修改
5.將職責過多的類進行分解
所謂職責是指類變化的原因。如果一個類有多于一個的動機被改變,那么這個類就具有多于一個的職責。而單一職責原則(SRP:Single responsibility principle)就是指一個類或者模塊應該有且只有一個改變的原因。
在軟件系統中,一個類(大到模塊,小到方法)承擔的職責越多,它被復用的可能性就越小,而且一個類承擔的職責過多,就相當于將這些職責耦合在一起,當其中一個職責變化時,可能會影響其他職責的運作,因此要將這些職責進行分離,將不同的職責封裝在不同的類中,即將不同的變化原因封裝在不同的類中,如果多個職責總
是同時發生改變則可將它們封裝在同一類中。
6.類名和方法名要能夠體現它們的職責
7.優先使用不可變的類
更改對象的問題在于,如果多個線程試圖同時更新一個對象,就會發生并發更改,其結果是不可預料的。如果類是不可更改的,就可以安全地在多個線程間共享其對象。修改狀態可通過返回狀態已修改的新對象來實現,而不是修改對象本身。