面向?qū)ο蟪绦蛟O(shè)計概述
面向?qū)ο蟪绦蛟O(shè)計(簡稱OOP)是當今主流的程序設(shè)計范型,它已經(jīng)取代了傳統(tǒng)的“結(jié)構(gòu)化”過程化程序開發(fā)技術(shù)。Java是完全面向?qū)ο蟮?,必須熟悉OOP才能夠編寫Java程序。
傳統(tǒng)結(jié)構(gòu)化程序設(shè)計主要通過設(shè)計一系列的過程(即算法)來解決問題,即算法+數(shù)據(jù)結(jié)構(gòu)=程序,先要確定如何操作數(shù)據(jù),再決定如何組織數(shù)據(jù)使之便于操作。而面向?qū)ο蟪绦蛟O(shè)計將數(shù)據(jù)域(屬性)和對數(shù)據(jù)的操作(方法)綁定在一個對象中,將數(shù)據(jù)放在第一位,然后再考慮操作數(shù)據(jù)的算法。
對于規(guī)模較小的問題將其分解為過程開發(fā)較為理想,而對于規(guī)模較大的問題使用OOP比較理想,比如出現(xiàn)錯誤,在集成在對象的方法中尋找錯誤比在眾多零散過程中查找更容易。
類和對象
類:類是構(gòu)造對象的模板或藍圖,用于定義對象的數(shù)據(jù)域和方法。一個java源文件中只能有一個公共類,且類名與文件名相同。編譯源文件時每個類(包括接口、枚舉、內(nèi)部類)都生成一個.class文件。如果A類使用B類,稱A類是B類的客戶。
對象:由類構(gòu)造的實例,一個類可以構(gòu)造多個實例。
設(shè)計類的簡單規(guī)則
先從設(shè)計類的數(shù)據(jù)域開始,再向類里添加方法。
類之間的關(guān)系
1.泛化(Generalization)
A是B和C的父類,B,C具有公共類(父類)A,說明A是B,C的一般化(也稱泛化)。
泛化關(guān)系就是繼承關(guān)系。如果一個類別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當中,對泛化關(guān)系有三個要求:
1、子類與父類應(yīng)該完全一致,父類所具有的屬性、操作,子類應(yīng)該都有
2、子類中除了與父類一致的信息以外,還包括額外的信息
3、可以使用父類的實例的地方,也可以使用子類的實例
2.關(guān)聯(lián)(Association)
表示不同類對象之間有關(guān)聯(lián),這是一種靜態(tài)關(guān)系,與運行過程的狀態(tài)無關(guān),在最開始就可以確定。因此也可以用 1 對 1、多對 1、多對多這種關(guān)聯(lián)關(guān)系來表示。比如客戶和訂單,每個訂單對應(yīng)特定的客戶,每個客戶對應(yīng)一些特定的訂單,再如籃球隊員與球隊之間的關(guān)聯(lián)(下圖所示)。

其中,關(guān)聯(lián)兩邊的"employee"和“employer”標示了兩者之間的關(guān)系,而數(shù)字表示兩者的關(guān)系的限制,是關(guān)聯(lián)兩者之間的多重性。通常有“*”(表示所有,不限),“1”(表示有且僅有一個),“0...”(表示0個或者多個),“0,1”(表示0個或者一個),“n...m”(表示n到m個都可以),“m...*”(表示至少m個)。
關(guān)聯(lián)關(guān)系(Association) 是類與類之間最常用的一種關(guān)系,它是一種結(jié)構(gòu)化關(guān)系,用于表示一類對象與另一類對象之間有聯(lián)系。在UML類圖中,用實線連接有關(guān)聯(lián)的對象所對應(yīng)的類,在使用Java、C#和C++等編程語言實現(xiàn)關(guān)聯(lián)關(guān)系時,通常將一個類的對象作為另一個類的屬性。在使用類圖表示關(guān)聯(lián)關(guān)系時可以在關(guān)聯(lián)線上標注角色名。
雙向關(guān)聯(lián): 默認情況下,關(guān)聯(lián)是雙向的。

public class Customer
{
private Product[] products;
……
}
public class Product
{
private Customer customer;
……
}
單向關(guān)聯(lián): 類的關(guān)聯(lián)關(guān)系也可以是單向的,單向關(guān)聯(lián)用帶箭頭的實線表示。

public class Customer
{
private Address address;
……
}
public class Address
{
……
}
自關(guān)聯(lián): 在系統(tǒng)中可能會存在一些類的屬性對象類型為該類本身,這種特殊的關(guān)聯(lián)關(guān)系稱為自關(guān)聯(lián)。

public class Node
{
private Node subNode;
……
}
重數(shù)性關(guān)聯(lián): 重數(shù)性關(guān)聯(lián)關(guān)系又稱為多重性關(guān)聯(lián)關(guān)系(Multiplicity),表示一個類的對象與另一個類的對象連接的個數(shù)。在UML中多重性關(guān)系可以直接在關(guān)聯(lián)直線上增加一個數(shù)字表示與之對應(yīng)的另一個類的對象的個數(shù)。

public class Form
{
private Button buttons[];
……
}
public class Button
{
…
}
3.依賴(Dependence)
和關(guān)聯(lián)關(guān)系不同的是,依賴關(guān)系是在運行過程中起作用的。
依賴關(guān)系有如下三種情況:
1、A類是B類中某方法的局部變量;
2、A類是B類方法當中的一個參數(shù);
3、A類向B類發(fā)送消息,從而影響B(tài)類發(fā)生變化;
在UML中,依賴關(guān)系用帶箭頭的虛線表示,由依賴的一方指向被依賴的一方。

public class Driver
{
public void drive(Car car)
{
car.move();
}
……
}
public class Car
{
public void move()
{
......
}
……
}
4.聚合(Aggregation)
表示的是整體和部分的關(guān)系,但整體和部分不是強依賴的,即整體與部分可以分開。聚合關(guān)系(Aggregation) 表示一個整體與部分的關(guān)系。通常在定義一個整體類后,再去分析這個整體類的組成結(jié)構(gòu),從而找出一些成員類,該整體類和成員類之間就形成了聚合關(guān)系。在聚合關(guān)系中,成員類是整體類的一部分,即成員對象是整體對象的一部分,但是成員對象可以脫離整體對象獨立存在。在UML中,聚合關(guān)系用帶空心菱形的直線表示。

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)
也是整體與部分的關(guān)系,但和聚合不同,組合的整體與部分不可以分開。組合關(guān)系(Composition)也表示類之間整體和部分的關(guān)系,但是組合關(guān)系中部分和整體具有統(tǒng)一的生存期。一旦整體對象不存在,部分對象也將不存在,部分對象與整體對象之間具有同生共死的關(guān)系。

public class Head
{
private Mouth mouth;
public Head()
{
mouth = new Mouth();
}
……
}
public class Mouth
{
……
}
如:公司和部門,部門是部分,公司是整體,公司A的財務(wù)部不可能和公司B的財務(wù)部對換,就是說,公司A不能和自己的財務(wù)部分開;人與人的心臟。

6.實現(xiàn)(Implementation)
實現(xiàn)是用來規(guī)定接口和實線接口的類或者構(gòu)建結(jié)構(gòu)的關(guān)系,接口是操作的集合,而這些操作就用于規(guī)定類或者構(gòu)建的一種服務(wù)。
接口之間也可以有與類之間關(guān)系類似的繼承關(guān)系和依賴關(guān)系,但是接口和類之間還存在一種實現(xiàn)關(guān)系(Realization),在這種關(guān)系中,類實現(xiàn)了接口,類中的操作實現(xiàn)了接口中所 聲明的操作。在UML中,類與接口之間的實現(xiàn)關(guān)系用帶空心三角形的虛線來表示。

public interface Vehicle
{
public void move();
}
public class Ship implements Vehicle
{
public void move()
{
……
}
}
public class Car implements Vehicle
{
public void move()
{
……
}
}
耦合程度
依賴、關(guān)聯(lián)、聚合、組合、泛化、實現(xiàn)的耦合程度依次遞增。
對象與對象變量
對象是調(diào)用構(gòu)造方法在堆上分配內(nèi)存產(chǎn)生的(用new + 構(gòu)造方法來調(diào)用),而對象變量是在棧上的持有對象引用的變量(聲明方式為:類名 + 對象名)。一個對象可被多個對象變量引用。比如語句Student s = new Student(),new Student()在堆上創(chuàng)建了對象,s是引用變量,包含對于該對象的引用。
該語句完成了下列操作:
1.加載 Student 類,包括:加載、驗證、準備、解析、初始化(如果不是第一次主動引用Student對象,這步省略)
2.在棧內(nèi)存為 s 開辟空間
3.在堆內(nèi)存為 Student 對象開辟空間
4.對 Student 對象的成員變量進行默認初始化
5.對 Student 對象的成員變量進行顯式初始化
6.通過構(gòu)造方法對 Student 對象的成員變量賦值
7.Student 對象初始化完畢,把對象地址賦值給 s 變量(如果不賦值給s變量,new Student()就是一個匿名對象)
更詳細的原理見:深入理解Java虛擬機之Java內(nèi)存區(qū)域與內(nèi)存溢出異常
大多數(shù)情況下,我們可以簡單地說s是一個Student類對象,而不用冗長地說s是一個包含對Student對象引用的變量。在Java中,數(shù)組被看作對象,一個數(shù)組變量實際上是一個包含數(shù)組引用的變量。
存儲區(qū)域
1.創(chuàng)建的實例及成員變量(靜、非靜態(tài))在堆中
2.局部變量在棧中
3.類的基本信息和方法定義在方法區(qū)

UML類圖
相關(guān)知識見深入淺出UML類圖
構(gòu)造器
構(gòu)造器用于構(gòu)造對象實例,并對數(shù)據(jù)域進行相應(yīng)初始化,物理層面表現(xiàn)為在堆上為對象的非靜態(tài)成員開辟存儲空間。
構(gòu)造器名應(yīng)該與類名相同,無返回值,甚至連void也沒有,可以被可見性修飾符(如public)修飾,因為它是用來創(chuàng)建實例的,所以它永遠是實例方法,不能被static修飾。
構(gòu)造方法可以重載,沒有參數(shù)的構(gòu)造方法稱為無參構(gòu)造方法或默認構(gòu)造方法,當類中沒有定義構(gòu)造方法時,編譯器會自動插入一個方法體為空的默認構(gòu)造方法,但一旦定義了有參構(gòu)造方法,該默認構(gòu)造方法不會被插入。
建議在構(gòu)造方法中調(diào)用各屬性的set方法來初始化屬性,而不是給屬性直接賦值,這樣set方法的合法性檢查也會應(yīng)用于構(gòu)造方法。
訪問對象的數(shù)據(jù)和方法
在面向?qū)ο缶幊讨?,對象成員可以引用該對象的數(shù)據(jù)域和方法。在創(chuàng)建一個對象后,它的數(shù)據(jù)域和方法可以使用點操作符(.)來訪問和調(diào)用,該操作符也稱為對象成員訪問操作符。
引用數(shù)據(jù)域和null值
如果一個引用類型的數(shù)據(jù)域沒有引用任何對象,那么它的值為null,是一個引用類型直接量。訪問值為null引用變量的數(shù)據(jù)域或方法會拋出一個NullPointerException。
默認賦值規(guī)則
類中的變量如果沒有賦值,會被自動賦予默認值,引用類型默認值為null,byte為(byte)0,short為(short)0,int為0,long為0L,float為0.0f,double為0.0,char為‘\u0000’(空字符,但也占長度),boolean為false。但如果Java沒有給方法里的局部變量賦值,會出現(xiàn)編譯錯誤。
基本變量和引用變量的區(qū)別
基本變量類型有byte,short,int,long,float,double,char,boolean八種,其他類型變量都是引用變量?;咀兞繉?yīng)內(nèi)存所存儲的值是基本類型值,而引用變量存儲的值是一個引用,是對象的存儲地址。將一個變量賦給另一個變量,另一個變量就被賦予同樣的值。對基本類型來說,就是將一個變量的實際值賦給另一個變量;對引用變量來說,就是將一個變量的引用賦給另一個變量,從而兩個變量指向同一個對象。
沒有變量引用的對象會成為垃圾,Java運行系統(tǒng)會檢測垃圾并自動回收它所占的空間,這個過程稱為垃圾回收。如果你認為不再需要某個對象,可以顯式地給該對象變量賦null值,讓它被JVM自動回收。
靜態(tài)/非靜態(tài)變量、常量和靜態(tài)/非靜態(tài)方法
靜態(tài)變量:又稱類變量,是由一個類的所有實例共享,變量值存儲在一個公共內(nèi)存地址,描述類的對象的公共屬性,不依賴于具體對象的變量。用關(guān)鍵字static表示。不要從構(gòu)造器中傳入?yún)?shù)來初始化靜態(tài)域,最好使用set方法改變靜態(tài)數(shù)據(jù)域。
非靜態(tài)變量:又稱實例變量,是依賴于具體對象的變量,變量值存儲在特定對象的內(nèi)存地址。
常量:類的所有對象共享且不可變的量,用static final修飾,final決定了其不可變性,static決定了它不依賴于具體對象,可以不實例化直接通過類名調(diào)用。
靜態(tài)方法:無需創(chuàng)建類實例就可以調(diào)用的方法,不依賴于具體對象,用關(guān)鍵字static表示,其中main方法也是靜態(tài)方法。靜態(tài)方法在類加載的時候就存在了,它不依賴于任何實例。所以靜態(tài)方法必須有實現(xiàn),也就是說它不能是抽象方法。
非靜態(tài)方法:又稱實例方法,是依賴于具體對象的方法。
關(guān)系:
靜態(tài)數(shù)據(jù)域或方法既可以通過類訪問,也可以通過對象訪問;非靜態(tài)數(shù)據(jù)域或方法只能通過對象訪問(但為了便于程序可讀性,建議用類名調(diào)用靜態(tài)成員,用對象名調(diào)用非靜態(tài)成員)。
靜態(tài)方法只能訪問靜態(tài)數(shù)據(jù)域,非靜態(tài)方法可以訪問非靜態(tài)數(shù)據(jù)域和靜態(tài)數(shù)據(jù)域。
在同一個類中: 對于靜態(tài)方法,其他的靜態(tài)或非靜態(tài)方法都可以直接調(diào)用它。而對于非靜態(tài)方法,其他的非靜態(tài)方法是可以直接調(diào)用它的。但是其他靜態(tài)方法只有通過創(chuàng)建對象才能調(diào)用它。
工廠方法
靜態(tài)方法還有另外一種常見的用途。類似LocalDate和NumberFormat的類使用靜態(tài)工廠方法來構(gòu)造對象。比如NumberFormat使用如下工廠方法生成不同風(fēng)格的格式化對象:
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%
使用靜態(tài)工廠方法的原因主要有兩個:
1.無法命名構(gòu)造器。構(gòu)造器名和類名必須相同,但NumberFormat希望得到的不同實例擁有不同名字
2.當使用構(gòu)造器時,無法改變所構(gòu)造的對象類型。而Factory方法將返回一個DecimalFromat類對象,這是NumberFormat的子類
可見性修飾符
public
修飾的成員可以在任何范圍內(nèi)直接訪問,只是一種最寬松的訪問控制等級。需要注意的,所謂的直接訪問仍需要先創(chuàng)建或獲得一個相應(yīng)類的對象然后才可以使用”對象名.成員“的方式訪問其屬性或調(diào)用其方法,但是出于信息封裝和隱藏的需要一般不提倡把成員聲明為public的,而構(gòu)造方法和需要外界直接調(diào)用的普通方法則適合聲明為public
protected
修飾的成員可以在其所在類中、同一包中及子類中(無論子類在不在同一個包)被直接訪問,但不能在位于不同包中的非子類中被直接訪問
default
缺省訪問修飾符的成員只能在其所在類中或包中直接訪問,在不同包中即使是不同包的子類也不能直接訪問。
private
private成員只能在所在類中被直接訪問,是4種訪問等級最高的一個,建議為了實現(xiàn)類的封裝,實例數(shù)據(jù)域應(yīng)該使用private修飾。如果不想讓一個類創(chuàng)建實例,可以用private修飾其構(gòu)造方法。
注意:private和protected只能修飾類成員,public和default可以修飾類和類成員。public的類成員只有位于public類里才能出包訪問,如果類是default的,也不能出包訪問。
包
包可以用來更好地組織、管理類,在不同的包中可以定義同名的類而不會發(fā)生沖突,建議將因特網(wǎng)域名的逆序作為包名,比如域名habitdiary.cn,可以定義包名為cn.habitdiary,還可以進一步定義子包名,比如cn.habitdiary.core,當然包和子包的這種嵌套關(guān)系只是為了邏輯結(jié)構(gòu)更加嚴謹,在編譯器看來這兩個包是互相獨立的集合。為了把類放入包中,需要在程序中首先出現(xiàn)語句package + 包名,前面不能有注釋或空白。如果定義類時沒有指定包,就表示把它放在默認包中,建議最好將類放入包中,不要使用默認包。源文件應(yīng)該放到與完整包名匹配的子目錄下,比如cn.habitdiary.core應(yīng)該放在子目錄cn/habitdiary/core下,編譯器會把編譯得到的.class文件放在同一目錄下。
類的導(dǎo)入
如果一個A類要使用不在同一個包下的B類,必須使用import關(guān)鍵字導(dǎo)入B類,當然B類能被導(dǎo)入的前提是在包外可見,即使用public修飾。
精確導(dǎo)入:導(dǎo)入某個包的特定類,如import java.util.Scanner
通配導(dǎo)入:導(dǎo)入某個包的所有類,如import java.util.*
編譯器定位類會按照配置的環(huán)境變量找到類庫,再根據(jù)導(dǎo)入路徑在相應(yīng)的包中定位相應(yīng)的類。
靜態(tài)導(dǎo)入:import語句不僅可以導(dǎo)入類,還可以導(dǎo)入靜態(tài)方法和靜態(tài)域,只要加入static關(guān)鍵字。比如import static java.lang.System.*導(dǎo)入了System類的靜態(tài)方法和靜態(tài)域,就可以不加類名前綴:out.println("Hello world!"),out是System類里定義的靜態(tài)成員,是PrintStream的實例。
注意:
1、在要使用某個類時可以不導(dǎo)入,但要采用包名.類名的方式使用這個類。
2、不可以使用精確導(dǎo)入導(dǎo)入兩個包中的同名類,此時應(yīng)該一個類精確導(dǎo)入,另一個類通配導(dǎo)入。程序默認使用的類是精確導(dǎo)入的類,如果要使用通配導(dǎo)入的同名類,要使用包名.類名的方式。
3、使用通配導(dǎo)入的時候不能跨級導(dǎo)入,比如在cn.habitdiary.core包中的類只能通過cn.habitdiary.core.*來導(dǎo)入,而不能通過cn.habitdiary.*來導(dǎo)入。
數(shù)據(jù)域封裝
封裝,即隱藏對象的屬性和實現(xiàn)細節(jié),僅對外公開接口,用戶無需知道對象內(nèi)部的細節(jié),但可以通過對象對外提供的接口來訪問該對象。封裝通過控制在程序中屬性的讀和修改的訪問級別,避免了對數(shù)據(jù)域的直接修改,提高了數(shù)據(jù)的安全性和易維護性。實現(xiàn)封裝的關(guān)鍵是絕不能讓類中的方法直接訪問其他類的實例域,程序僅通過方法和數(shù)據(jù)進行交互。
實現(xiàn)封裝的步驟:
1.用private可見性修飾符修飾類成員
2.設(shè)置訪問器(get方法),方法簽名習(xí)慣為:public returnType getPropertyName(),如果返回值類型是boolean型,習(xí)慣下定義為:public boolean isPropertyName()
3.設(shè)置修改器(set方法),方法簽名習(xí)慣為:public void setPropertyName(dataType propertyValue)
優(yōu)點:
- 減少耦合:可以獨立地開發(fā)、測試、優(yōu)化、使用、理解和修改
- 減輕維護的負擔:可以更容易被程序員理解,并且在調(diào)試的時候可以不影響其他模塊
- 有效地調(diào)節(jié)性能:可以通過剖析確定哪些模塊影響了系統(tǒng)的性能
- 提高軟件的可重用性
- 降低了構(gòu)建大型系統(tǒng)的風(fēng)險:即使整個系統(tǒng)不可用,但是這些獨立的模塊卻有可能是可用的
以下 Person 類封裝 name、gender、age 等屬性,外界只能通過 get() 方法獲取一個 Person 對象的 name 屬性和 gender 屬性,而無法獲取 age 屬性,但是 age 屬性可以供 work() 方法使用。
注意到 gender 屬性使用 int 數(shù)據(jù)類型進行存儲,封裝使得用戶注意不到這種實現(xiàn)細節(jié)。并且在需要修改 gender 屬性使用的數(shù)據(jù)類型時,也可以在不影響客戶端代碼的情況下進行。
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!");
}
}
}
向方法傳遞對象參數(shù)
可以將對象傳遞給方法,同傳遞數(shù)組一樣,傳遞對象實際上是傳遞對象的引用。Java只有一種參數(shù)傳遞方式:值傳遞。只不過引用類型(包括對象、普通數(shù)組、對象數(shù)組)變量的值是引用,引用上傳值的最好描述為傳共享。
以下代碼中 Dog dog 的 dog 是一個指針,存儲的是對象的地址。在將一個參數(shù)傳入一個方法時,本質(zhì)上是將對象的地址以值的方式傳遞到形參中。因此在方法中改變指針引用的對象,那么這兩個指針此時指向的是完全不同的對象,一方改變其所指向?qū)ο蟮膬?nèi)容對另一方?jīng)]有影響。
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
}
}
但是如果在方法中改變對象的字段值會改變原對象該字段值,因為改變的是同一個地址指向的內(nèi)容。
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");
}
}
對象數(shù)組和普通數(shù)組
所有數(shù)組變量存儲的都是對數(shù)組的引用,但是普通數(shù)組里存儲的就是實際的值,對象數(shù)組里存儲的則還是對象的引用,而非對象本身,其類似于二維數(shù)組有兩層的引用關(guān)系,對象數(shù)組元素的初始值默認為null。
不可變對象和類
一旦創(chuàng)建之后內(nèi)容就不可改變的對象是不可變對象,它的類稱為不可變類。
一個對象是不可變的要滿足以下三個條件:
1、對象創(chuàng)建以后其狀態(tài)就不能修改(將所有域設(shè)為private,且不對外提供setter方法,getter方法也不能返回指向可變數(shù)據(jù)域的引用)
2、對象的所有域都是final類型
3、對象是正確創(chuàng)建的(在對象的創(chuàng)建期間this引用沒有逸出)
變量作用域
一個類的實例變量和靜態(tài)變量的作用于是整個類,不論在何處聲明,所以類的變量和方法可以在類中以任意順序出現(xiàn)。但是當一個變量初始化要基于另一個變量時不是這樣。比如
public class F{
private int j = i + 1;
private int i;
}
就是錯誤的,因為j的初始化基于i已經(jīng)被初始化的前提。
局部變量(包括方法中聲明的變量和形參等)的作用域則從聲明開始到包含該變量的塊結(jié)束處。如果一個局部變量和類成員變量有相同的名字,則局部變量優(yōu)先,同名的成員變量被隱藏??梢酝ㄟ^this引用顯示隱藏的成員變量。
建議:在聲明類的變量和方法時,最好按照:數(shù)據(jù)域 —— 構(gòu)造方法 —— 普通方法的順序,且最好在數(shù)據(jù)域和方法之間空一行,提高程序可讀性。
this引用
this關(guān)鍵字有兩大作用:
1.表示指向調(diào)用對象本身的引用名
2.可以在構(gòu)造方法內(nèi)部調(diào)用同一個類的其他構(gòu)造方法,此時this(參數(shù)列表)語句應(yīng)該出現(xiàn)在構(gòu)造方法其他語句之前,如果一個類有多個構(gòu)造方法,最好盡可能使用this(參數(shù)列表)的形式來實現(xiàn)它們。這樣做可以簡化代碼,使類易于維護。
對象構(gòu)造
默認域初始化
即依賴編譯器對數(shù)據(jù)域的默認初始化。
顯式域初始化
在數(shù)據(jù)域的定義處進行賦值,可以在執(zhí)行構(gòu)造器之前,先執(zhí)行賦值操作。當一個類的所有構(gòu)造器都希望把相同的值賦予某個特定實例域時,這種方法特別有用。初始值不一定是常量值,可以調(diào)用方法返回一個值對域進行初始化。
無參數(shù)的構(gòu)造器
即將對數(shù)據(jù)域的初始化置于一個無參的構(gòu)造器中。
有參數(shù)的構(gòu)造器
即給構(gòu)造器傳入?yún)?shù)對數(shù)據(jù)域進行初始化
初始化塊
在方法內(nèi)的代碼塊稱為普通代碼塊,就是一組用花括號括起來的語句,沒有特殊含義。而如果用花括號包含的一組數(shù)據(jù)域賦值代碼塊出現(xiàn)在類內(nèi),就稱為初始化塊或構(gòu)造代碼塊。初始化塊一般在數(shù)據(jù)域聲明處之后,構(gòu)造器之前。如果在初始化塊前加static關(guān)鍵字,并在塊內(nèi)初始化靜態(tài)數(shù)據(jù)域,就成了靜態(tài)初始化塊,不允許在靜態(tài)初始化塊內(nèi)初始化實例成員。
初始化順序
1.初始化主類(main方法所在類),主類的靜態(tài)成員和靜態(tài)初始化塊,按在代碼中出現(xiàn)的順序一次執(zhí)行(如果主類有父類先加載父類)
2.父類靜態(tài)成員和靜態(tài)初始化塊,按在代碼中出現(xiàn)的順序依次執(zhí)行(classLoader的類加載過程)。
3.子類靜態(tài)成員和靜態(tài)初始化塊,按在代碼中出現(xiàn)的順序依次執(zhí)行(classLoader類加載過程)。
4.父類的實例成員、實例初始化塊,按在代碼中出現(xiàn)的順序依次執(zhí)行。
5.父類構(gòu)造方法
6.子類實例成員、實例初始化塊,按在代碼中出現(xiàn)的順序依次執(zhí)行。
7.子類構(gòu)造方法
注意:靜態(tài)成員和靜態(tài)初始化塊只在該類被加載的時候初始化一次,可以看作初始化類。當某類被主動引用時初始化該類及其父類。
比如下面的例子:
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();
}
}
運行結(jié)果如下:

關(guān)于類加載機制詳細解釋見:深入理解Java虛擬機之虛擬機類加載機制
文檔注釋
JDK 包含一個很有用的工具 , 叫做 javadoc , 它可以由源文件生成一個 HTML 文檔 。我們平時查閱的API就是這樣形成的,而且添加文檔注釋后,eclipse也會對添加了文檔注釋的方法等給出智能提示。
注釋的插入
javadoc 實用程序 ( utility ) 從下面幾個特性中抽取信息:
- 包
- 公有類與接口
- 公有的和受保護的構(gòu)造器及方法
- 公有的和受保護的域
應(yīng)該為上面幾部分編寫注釋。注釋應(yīng)該放置在所描述特性的前面 。注釋以 /** 開始, 并以 */ 結(jié)束 。插入文檔注釋的方法是輸入/**后回車即可。
每個 /** . . . */ 文檔注釋在標記之后緊跟著自由格式文本 (free-form text)。標記由@開始, 如 @ author 或 @ param 。
自由格式文本的第一行或幾行是關(guān)于類、變量和方法的主要描述。javadoc 實用程序自動地將這些句子抽取出來形成概要頁 。
在自由格式文本中, 可以使用 HTML 修飾符 , 例如 , 用于強調(diào)的 <em> ... </em> 、用于著重強調(diào)的<strong> ... </strong>以及包含圖像的< img.../> 等 。 不過, 一定不要使用<hl>或<hr>,因為它們會與文檔的格式產(chǎn)生沖突 。若要鍵入等寬代碼, 需使用 {@code...} 而不是<code> ... </code> ———— 這樣一來 , 就不用操心對代碼中的 <字符轉(zhuǎn)義了 。
類注釋
類注釋必須放在 import 語句之后, 類定義之前 。
下面是一個類注釋的例子:

沒有必要在每一行的開始用星號*,例如,以下注釋同樣是合法的:

然而,大部分IDE提供了自動添加星號*,并且當注釋行改變時,自動重新排列這些星號的功能。
方法注釋
每一個方法注釋必須放在所描述的方法之前。除了通用標記之外, 還可以使用下面的標記:
-
@param變量描述
這個標記將對當前方法的 "param" ( 參數(shù) ) 部分添加一個條目。這個描述可以占據(jù)多行,并可以使用 HTML 標記。一個方法的所有@param標記必須放在一起。 -
@return描述
這個標記將對當前方法添加 "return" (返回 ) 部分。這個描述可以跨越多行,并可以使用 HTML 標記 。 -
@throws類描述
這個標記將添加一個注釋,用于表示這個方法有可能拋出異常。
下面是一個方法注釋的示例:

域注釋
只需要對公有域(通常指的是靜態(tài)常量)建立文檔。例如:

通用注釋
下面的標記可以用在類文檔的注釋中。


包與概述注釋

注釋的抽取
javadoc 工具將你 Java 程序的源代碼作為輸入,輸出一些包含你程序注釋的HTML文件。
每一個類的信息將在獨自的HTML文件里。javadoc 也可以輸出繼承的樹形結(jié)構(gòu)和索引。


文檔標簽速查:菜鳥教程之Java文檔注釋
類設(shè)計技巧
1.一定要保證數(shù)據(jù)域私有
2.一定要對數(shù)據(jù)初始化
最好不要依賴默認初始化,會影響程序可讀性。
3.不要在類中使用過多的基本類型
用其他集合相關(guān)基本類型的類代替多個基本類型使用
4.不是所有的域都需要獨立的域訪問器和域修改器
有的數(shù)據(jù)域定義后不必要修改
5.將職責(zé)過多的類進行分解
所謂職責(zé)是指類變化的原因。如果一個類有多于一個的動機被改變,那么這個類就具有多于一個的職責(zé)。而單一職責(zé)原則(SRP:Single responsibility principle)就是指一個類或者模塊應(yīng)該有且只有一個改變的原因。
在軟件系統(tǒng)中,一個類(大到模塊,小到方法)承擔的職責(zé)越多,它被復(fù)用的可能性就越小,而且一個類承擔的職責(zé)過多,就相當于將這些職責(zé)耦合在一起,當其中一個職責(zé)變化時,可能會影響其他職責(zé)的運作,因此要將這些職責(zé)進行分離,將不同的職責(zé)封裝在不同的類中,即將不同的變化原因封裝在不同的類中,如果多個職責(zé)總
是同時發(fā)生改變則可將它們封裝在同一類中。
6.類名和方法名要能夠體現(xiàn)它們的職責(zé)
7.優(yōu)先使用不可變的類
更改對象的問題在于,如果多個線程試圖同時更新一個對象,就會發(fā)生并發(fā)更改,其結(jié)果是不可預(yù)料的。如果類是不可更改的,就可以安全地在多個線程間共享其對象。修改狀態(tài)可通過返回狀態(tài)已修改的新對象來實現(xiàn),而不是修改對象本身。