Java和C++在細節(jié)上的差異:程序設計結構
一、基本程序設計結構:
Java的基本程序結構、關鍵字、操作符都和C/C++非常相似,以下為主要的幾點區(qū)別:
1. Java的原始數(shù)值型數(shù)據(jù)類型中不包含無符號類型,如c中的unsigned int。
2. 在進行移位運算時,當向左邊移動時,如1 << 35, 對于int類型,由于其占有4個bytes(32bits), 因此在Java中,大于32的移位將對32取模,即1 << 35的結果等于1 << 3,以此類推,long將會對64取模。對于int類型而言,如果確實需要獲取32位以上的移位,需要將返回值的類型提升到long即可。
3. 在c語言中,可以通過判斷一個數(shù)值是否為0來代替布爾中的false,其他的數(shù)值均表示為true。該寫法可應用于if和while等子句中,如 if (i) {....}, 當i的值不為0時,該條件可為真,或者是在判斷指針對象是否為NULL時,也可作為if和while的條件,因此很容易出現(xiàn)將if (i == 9) {...}寫成if (i = 9) {...}的低級錯誤,在Java中禁止了該類轉(zhuǎn)換,既if和while中條件必須是布爾類型,如果在Java中寫成 if (i = 9) {...}將會直接導致編譯錯誤,從而更好的避免了該類問題的發(fā)生。
4. Java中去除了goto字句,但是仍然視為保留字。然而Java中的break字句,新增了帶標簽的break [label],可以使break語句直接跳出指定的循環(huán),而不僅僅是缺省的最內(nèi)層循環(huán)。注:標簽必須放在希望跳出的最外層循環(huán)之前,并且緊跟一個冒號。如:
- public void test() {
- int n;
- read_data:
- while (...) {
- for (...) {
- System.out.print("Enter a number >= 0: ");
- n = in.nextInt();
- if (n < 0)
- break read_data;
- }
- }
- //下面的代碼將會被立即執(zhí)行,當break跳出最外層的循環(huán)之后。
- if (n < 0) {
- ...
- } else {
- ...
- }
- }
5. Java中支持0長度的數(shù)組定義,如int et = new int[0]; 在C/C++中,該寫法將會導致編譯錯誤。
6. 多維數(shù)組的兩種常用訪問方式。
- public static void main(String[] args) {
- int[][] magicSquare =
- {
- {16,3,2,13},
- {5,10,11,8},
- {9,6,7,12},
- {4,15,14,1}
- };
- // 通過普通的for循環(huán)訪問
- for (int i = 0; i < magicSquare.length; ++i) {
- for (int j = 0; j < magicSquare[i].length; ++j) {
- System.out.printf("%s ",magicSquare[i][j]);
- }
- System.out.println();
- }
- // 通過普通的for each循環(huán)訪問
- for (int[] row : magicSquare) {
- for (int col : row) {
- System.out.printf("%s ",col);
- }
- System.out.println();
- }
- }
- /*兩次輸出結果均為:
- 16 3 2 13
- 5 10 11 8
- 9 6 7 12
- 4 15 14 1 */
7. Java中的不規(guī)則二維數(shù)組。
- public void foo() {
- int[][] odds = new int[NMAX+1][];
- for (int n = 0; n <= NMAX; ++n)
- odds[n] = new int[n + 1];
- for (int n = 0; n < odds.length; ++n) {
- for (int k = 0; k < odds[n].length; ++k)
- odds[n][k] = n * k;
- }
- }
C/C++中對應于Java的不規(guī)則二維數(shù)組的表示方式。
- void foo() {
- int** odds = new int*[10];
- for (int n = 0; n < 10; ++n) {
- if (n == 0)
- odds[n] = new int;
- else
- odds[n] = new int[n + 1];
- }
- for (int n = 0; n < 10; ++n) {
- for (int k = 0; k < n + 1; ++k)
- odds[n][k] = n * k;
- }
- //注:C/C++代碼部分需要自行釋放分配的內(nèi)存。
- for (int n = 0; n < 10; ++n) {
- if (n == 0)
- delete odds[n];
- else
- delete [] odds[n];
- }
- delete [] odds;
- }
二、對象與類:
1. Java對象實例的存儲方式:
所有的Java對象實例都是通過new的方式創(chuàng)建的,如Employee employee = new Employee()。而此時創(chuàng)建的employee對象實例,實際是指向Employee對象的一個實例的引用,主要體現(xiàn)為實例之間基于等號的賦值,如:employee = employee2; 賦值后兩個變量將指向同一個Employee對象實例。Java處理對象變量的方式和C++中的引用比較類似,但是還是存在一定的差異,首先C++不存在空引用,既引用變量定義時也必須被同時聲明其所引用的對象實例,再者就是引用一旦定義時初始化后就不能再被重新賦值了。因此這里可以將Java的對象變量看做C++中的對象指針,如:BirthdayDate d; /*Java*/ 等同于 BirthdayDate* d; /*C++*/。
與Java對象實例聲明的方式相同,C++中的對象指針也是通過new的方式進行初始化的,如BirthdayDate* d = new BirthdayDate. 同樣可以將C++中的對象指針賦值為NULL,也可以將其重新賦值指向另外一個對象實例。與Java相同,通過new操作符創(chuàng)建的對象實例是存儲在堆中的,不同的是,Java的對象在創(chuàng)建后,無需開發(fā)人員在去關注該對象實例需要合適被釋放,所有的操作均有Java虛擬機中提供的垃圾回收機制自動完成。而C++中的該類對象,則需要開發(fā)人員通過調(diào)用delete操作符來自行完成釋放,如果忘記釋放將會產(chǎn)生內(nèi)存泄露。在C++中,不僅可以將對象存儲在堆上,同樣也可以定義并存儲的棧上,如BrithdayDate d; 該對象實例不需要手工釋放,在棧退出時將自動釋放該對象的存儲空間,同時也會調(diào)用該對象的析構函數(shù)。
2. Java對象方法的顯式參數(shù)和隱式參數(shù):
- public class Employee {
- public void raiseSalary(double byPercent) {
- double raise = salary + byPercent / 100;
- salary += raise;
- }
- private double salary;
- }
raiseSalary是Employee類的一個成員方法,該方法是由兩個參數(shù)構成,一個是顯式參數(shù)byPercent,另一個則是隱式參數(shù)this,既raiseSalary方法是實現(xiàn)體可以改為:
- public void raiseSalary(double byPercent) {
- double raise = this.salary + byPercent / 100;
- this.salary += raise;
- }
這里的隱式參數(shù)this表示當前調(diào)用raiseSalary方法的對象實例的自身,該機制和C++基本相同。
注:靜態(tài)方法中不存在該特征。
3. Java對象中定義的final實例域,如:public class Employee { ... private final String name; }, 該類型的field必須在對象構造函數(shù)中進行初始化,之后該變量將不能再被重新賦值。和final字段相似,C++對象中的const成員變量也必須在對象構造函數(shù)的初始化列表中完成賦值任務,在之后的使用中該字段將不會再被修改,否則會產(chǎn)生編譯錯誤。對于Java的final域而言,以便應用于基本數(shù)據(jù)類型,如int,double等,或者不可變類型,如String。對于可變類型而言,final修飾符可能會造成某些預料之外的混亂,如 private final Date hiredate; 當該field作為某個get方法的返回值返回給調(diào)用者之后,final的修飾作用只能保證返回后的date對象不能再被重新賦值并指向新的對象實例引用,但是可以通過直接修改返回值對象的自身數(shù)據(jù)來破壞對象的封裝性,從而可能造成數(shù)據(jù)的非法性,或者狀態(tài)的不一致性。
4. 函數(shù)參數(shù)傳遞的方式:傳值和傳引用。
在Java中調(diào)用函數(shù)是,參數(shù)都是通過傳值的方式傳遞到函數(shù)內(nèi)部,然而根據(jù)參數(shù)類型的不同,其表現(xiàn)仍然存在一定的差異。主要總結為以下3點:
1)被調(diào)用方法不能修改一個基本數(shù)據(jù)類型的參數(shù),如:int,double,boolean等,見如下代碼:
- private static void tripleValue(double x) {
- x *= 3;
- System.out.println("End of method: x = " + x);
- }
- public static void testTripleValue() {
- System.out.println("Test tripleValue");
- double percent = 10;
- System.out.println("Before: percent = " + percent);
- tripleValue(percent);
- System.out.println("After: percent = " + percent);
- }
- /* 結果如下:
- Test tripleValue
- Before: percent = 10.0
- End of method: x = 30.0
- After: percent = 10.0 */
2)被調(diào)用方法可以改變一個對象參數(shù)的狀態(tài),見如下代碼:
- private static void tripleSalary(Employee x) {
- x.raiseSalary(200);
- System.out.println("End of method: salary = " + x.getSalary());
- }
- public static void testTripleSalary() {
- System.out.println("Test tripleSalary");
- Employee harry = new Employee("Harry",50000);
- System.out.println("Before: salary = " + harry.getSalary());
- tripleSalary(harry);
- System.out.println("After: salary = " + harry.getSalary());
- }
- /* 結果如下:
- Test tripleSalary
- Before: salary = 50000.0
- End of method: x = 150000.0
- After: salary = 150000.0 */
3)被調(diào)用方法不能實現(xiàn)讓對象參數(shù)引用一個新的對象,見如下代碼:
- private static void swap(Employee a,Employee b) {
- Employee temp = x;
- x = y;
- y = temp;
- System.out.println("End of method: x = " + x.getName());
- System.out.println("End of method: y = " + y.getName());
- }
- public static void testSwap() {
- System.out.println("Test Swap");
- Employee a = new Employee("Alice",70000);
- Employee b = new Employee("Bob",60000);
- System.out.println("Before: a = " + a.getName());
- System.out.println("Before: b = " + b.getName());
- swap(a,b);
- System.out.println("After: a = " + a.getName());
- System.out.println("After: b = " + b.getName());
- }
- /* 結果如下:
- Test swap
- Before: a = Alice
- Before: b = Bob
- End of method: x = Bob
- End of method: y = Alice
- After: a = Alice
- After: b = Bob */
C++有值調(diào)用和引用調(diào)用,引用參數(shù)標有&符號。如:void tripleValue(double& x)或void swap(Employee& x,Employee& y)方法實現(xiàn)修改他們引用參數(shù)的目的,既該方法執(zhí)行完成后,調(diào)用函數(shù)的參數(shù)變量的值將發(fā)生改變。
5. 對象的構造和構造函數(shù):
在Java中如果一個class沒有定義任何構造函數(shù),Java編譯器將自動生成一個缺省的構造函數(shù),沒有任何參數(shù),其行為只是按照Java默認的方式初始化該類的所有域變量,如數(shù)值型為0,布爾為false,對象則為null。但是如果該class定義了自己的構造函數(shù),那么缺省構造函數(shù)將不會被自動生成,再試圖調(diào)用自動生成的缺省構造函數(shù)將會導致編譯錯誤。該行為和C++完全一致。但是Java提供了另外一種域變量初始化方式,如下:
- public class Employee {
- ...
- private String name = ""; //直接賦值
- private int id = assignId();//通過調(diào)用域方法完成初始化。
- }
在C++中不能直接在類的定義中以任何形式直接初始化成員變量。但是C++提供了在構造函數(shù)中以初始化列表的方式完成成員變量對象的初始化,特別是const成員,必須在這里賦值。
通過一個構造器調(diào)用另一個構造器從而完成域變量的初始化和部分代碼復用。通過this關鍵字(或稱隱式參數(shù))作為函數(shù)名,然后傳入?yún)?shù)調(diào)用你期望的另一個構造函數(shù),注:this被調(diào)用之前不能執(zhí)行任何其他的code。
- public Employee(double s) {
- //calls Employee(String,double)
- this("Employee #" + nextId,s);
- ++nextId;
- }
在C++中如果打算完成此功能,必須將構造函數(shù)的部分邏輯抽取出來,以便讓多個構造函數(shù)去調(diào)用,然后不同的構造函數(shù)之間不能直接調(diào)用。
在Java定義的子類中,如果子類的構造函數(shù)不是調(diào)用父類的缺省構造函數(shù),則需要在子類構造函數(shù)的***行代碼中指定欲調(diào)用的父類構造函數(shù),該調(diào)用需要通過super關鍵字來完成。見如下代碼:
- public class MyFirst {
- public static void main(String[] args) {
- BaseClass bc1 = new SonClass();
- BaseClass bc2 = new SonClass(5);
- }
- }
- class BaseClass {
- public BaseClass() {
- System.out.println("This is BaseClass");
- }
- public BaseClass(int i) {
- System.out.println("This is BaseClass with i.");
- }
- }
- class SonClass extends BaseClass {
- public SonClass() {
- System.out.println("This is SonClass");
- }
- public SonClass(int i) {
- super(5);
- System.out.println("This is SonClass with i");
- }
- }
- /* 結果如下:
- This is BaseClass
- This is SonClass
- This is BaseClass with i.
- This is SonClass with i */
在C++中也可以完成該種類型的指定,但是必須在子類構造函數(shù)的初始化列表中完成對父類指定構造函數(shù)的調(diào)用。
- class BaseClass {
- public:
- BaseClass() {
- printf("This is BaseClass\n");
- }
- BaseClass(int i) {
- printf("This is BaseClass with i\n");
- }
- };
- class SonClass : public BaseClass {
- public:
- SonClass() {
- printf("This is SonClass\n");
- }
- SonClass(int i) : BaseClass(i) {
- printf("This is SonClass with i\n");
- }
- };
- int main()
- {
- BaseClass* bc1 = new SonClass;
- BaseClass* bc2 = new SonClass(5);
- delete bc1;
- delete bc2;
- return 0;
- }
- /* 結果如下:
- This is BaseClass
- This is SonClass
- This is BaseClass with i.
- This is SonClass with i */
在Java的域變量初始化方法中存在初始化塊的方式,既除聲明即初始化、構造函數(shù)初始化之外的第三種域變量初始化方式。在一個類的聲明中可以存在多個代碼塊,只要構造類的對象,這些塊就會被執(zhí)行,然后再運行類的構造函數(shù)。靜態(tài)域變量可以在靜態(tài)初始化塊中完成初始化的工作,但是該初始化塊只是在類***次加載時被執(zhí)行一次,之后都將不再被執(zhí)行。見如下代碼:
- class Employee {
- public Employee(String n,double s) {
- name = n;
- salary = s;
- }
- ...
- private static int nextId;
- private int id;
- private String name;
- private double salary;
- //object initialization block.
- {
- id = nextId;
- nextId++;
- }
- //static initialization block.
- static
- {
- Random generator = new Random();
- nextId = generator.nextInt();
- }
- }
6. C++的對象析構和Java對象的finalize方法:
C++是有顯式的析構方法,其中放置一些當對象不再使用時需要執(zhí)行的清理代碼。在析構函數(shù)中,最常見的操作時回收分配給對象的存儲空間,系統(tǒng)資源等。有Java有自動的垃圾回收器,不需要人工回收內(nèi)存,所以Java并不支持析構函數(shù)。如果打算在Java的代碼中完成類似的工作,可以通過為該類添加finalize方法,該方法將會在垃圾收集器清除對象之前調(diào)用,在實際應用中,不要依賴于使用finalize方法回收任何短缺的資源,這是因為很難知道這個方法什么時候才能調(diào)用。如果某個資源確實需要在使用完畢后立刻關閉,那么就需要由人工來管理??梢詰靡粋€類似dispose或close的方法完成相應的清理操作。特別需要說明,如果一個類使用了這樣的方法,當對象不再被使用時一定要調(diào)用它。
7. Java的包 vs C++的名字空間
他們具有極為相同的只能,即防止名字污染。當一個應用程序中存在多個第三方組件,那么不同組件中命名了相同名稱的類將是極為可能發(fā)生的,如Java中的Date類,在java.util和java.sql中均存在該名稱的類的聲明。為了有效的防止名字污染,C++中采用了namespace和using namespace的指令來明確定義某個類具體所位于的具體位置,Java中則采用了package和import語句。
Java在Java SE5.0 開始,import語句不僅可以導入類,還增加了導入靜態(tài)方法和靜態(tài)域的功能。如import static java.lang.System.*。在完成該靜態(tài)導入之后,就可以在剩下的代碼中直接使用System類的靜態(tài)方法和靜態(tài)域了,如out.println();exit(0)。該技巧主要用于帶有較長名稱的常量,如if (d.get(DAY_OF_WEEK) == MONDAY) ...,看起來比if (d.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) ...要容易的多。
#p#
三、繼承:
1. Java和C++在對象繼承方面的主要差異:
對象的繼承性是所有面向?qū)ο笳Z言都支持的面向?qū)ο筇匦灾?,Java和C++作為兩個重要的面向?qū)ο箝_發(fā)語言在此方面有著較多的相似性,但是在有些概念的表示方式上還是存在著一定的差異,先列舉如下:
1) 對象繼承的關鍵字,Java中采用extents關鍵字,如class DeriveClass extends BaseClass, 在C++中則使用(:)冒號表示類之間的繼承,如class DeriveClass : public BaseClass。
2) Java的繼承方式中不存在public,protected和private,其表現(xiàn)行為和C++中的public繼承完全一致。
3) 在有些情況下,子類中的方法需要顯式的調(diào)用超類中的方法實現(xiàn),特別是當子類中也存在同樣方法簽名的實現(xiàn)時,如果沒有明確的指出需要調(diào)用超類的方法,Java的編譯器會將子類當前的方法列為本次調(diào)用的候選方法,見如下代碼:
- class DeriveClass extends BaseClass {
- public double getSalary() {
- double baseSalary = getSalary();
- return baseSalary + bonus;
- }
- }
以上代碼中的getSalary()方法將會遞歸的調(diào)用其自身,而開發(fā)者的實際用意是調(diào)用超類中的getSalary方法,由于超類和子類中具有相同簽名的該方法,因此編譯器在此時選擇了子類中的getSalary。其修改方式如下:
- class DeriveClass extends BaseClass {
- public double getSalary() {
- double baseSalary = super.getSalary();
- return baseSalary + bonus;
- }
- }
加上關鍵字super明確的指出要調(diào)用超類中的getSalary方法。在C++中的實現(xiàn)方式為BaseClass::getSalary(),既在方法簽名的前面加上父類的名字和兩個連在一起的冒號(::)。
- class DeriveClass : public BaseClass {
- public:
- double getSalary() {
- double baseSalary = BaseClass::getSalary();
- return baseSalary + bonus;
- }
- }
4) Java中所有未聲明為final的方法都視為可以繼承的虛方法。在C++中,盡管沒有此類限制,但是在實際的應用中還是存在一些潛在的技巧以達到此效果。對于C++類中聲明的公有成員方法,如果該方法未聲明為virtual,既虛函數(shù),則暗示該類的子類實現(xiàn)者不要在子類中覆蓋(override)該方法。
5) Java中不支持多重繼承,不僅有效的避免了C++因多重繼承而帶來的一些負面影響,與此同時,在Java中可以通過繼承(extends)單個父類和實現(xiàn)(implements)多個接口的方式更好表達該類設計意愿。
6) Java中如果子類和超類同時包含具有相同簽名的公有域方法,那么在子類中將覆蓋超類中的域方法。這其中的方法簽名只是包括方法名和參數(shù)列表,既參數(shù)的個數(shù)和類型,函數(shù)的返回值不包含在方法簽名中,但是在Java中針對該種方法覆蓋的返回值還是存在一定的限制,既子類中的返回值的類型,或者與超類中該方法的返回值類型相同,或者為其返回類型的子類。C++中沒有此類返回值類型的限制。但是Java的此類限制也會帶來一些潛在的迷惑和危險,見如下代碼:
- class Employee {
- public Employee[] getBuddies() { ... }
- }
- class Manager extends Employee {
- public Manager[] getBuddies() { ... }
- }
- public static void main(String[] args) {
- Employee[] m = new Manager().getBuddies();
- //在Java中子類的數(shù)組在復制給超類的數(shù)組時不需要顯式的轉(zhuǎn)換,就像
- //子類的實例賦值給超類的實例一樣,也不需要任何顯式的轉(zhuǎn)換。
- //賦值之后e和m指向相同的內(nèi)存地址,同樣e[0]和m[0]也指向相同的實例。
- Employee[] e = m;
- //本次賦值合法也不會引發(fā)任何異常,但是會導致一個潛在的問題,既
- //m[0]的對象已經(jīng)被悄悄的改變了,指向了Employee的另外一個子類。
- e[0] = new OtherEmployee();
- //此時再調(diào)用m[0]中Manager定義的域方法時將會引發(fā)Java的運行時異常。
- m[0].setBonus(1000);
- }
7) Java中的final類,如果某個自定義類型被加入final關鍵字,則表示該類將不能被繼承,否則會直接產(chǎn)生編譯錯誤。在C++中沒有特殊的關鍵字類完成此類限制,然而在實際的應用中也同樣存在一些潛在的技巧協(xié)助開發(fā)者來進行此類限制的甄別。如將父類中的析構函數(shù)不設置為虛函數(shù),此方法則間接的暗示子類的實現(xiàn)者要留意,如果仍然繼承該父類,那么在實現(xiàn)多態(tài)時,如BaseClass* c = new DeriveClass,如果之后需要釋放c變量的內(nèi)存資源時 delete c, 此時由于父類中的析構函數(shù)并不是虛函數(shù),因此此次調(diào)用將只會執(zhí)行父類的析構函數(shù),而不會調(diào)用子類的析構函數(shù),最終導致類分割所帶來的一些潛在錯誤或資源泄漏。
8) 內(nèi)聯(lián)方法,在C++中有特殊的關鍵字inline用于幫助編譯器來推斷是否需要將該方法編譯成內(nèi)聯(lián)方法,以提高運行時的效率。在Java中沒有此類關鍵字,而是通過編譯器的一連串推演,最終決定該域方法是否可以編譯成內(nèi)聯(lián)方法,主要候選方法為簡短、被頻繁調(diào)用且沒有真正被子類覆蓋的域方法。
9) 超類到子類的強制類型轉(zhuǎn)換。在Java中可以通過直接強轉(zhuǎn)的方式來轉(zhuǎn)換,如Manager m = (Manager)e。如果裝換失敗將會引發(fā)運行時異常ClassCastException,因此很多情況下為了避免此類異常的發(fā)生,需要在強轉(zhuǎn)之前先進行判斷,如if (e instanceof Manager) { ... }, 如果條件為真,裝換將順利完成。在C++中也可以采用這樣的直接強轉(zhuǎn)方法,但是即使類型不匹配程序也不會在強轉(zhuǎn)是引發(fā)任何異常,而是在后面針對該變量的使用時才會導致錯誤的發(fā)生。在C++中存在dynamic_cast關鍵字,如dynamic_cast<Manager*>和dynamic_cast<Manager&>,前者為基于指針的轉(zhuǎn)換,如果轉(zhuǎn)換失敗返回變量為NULL,而后者則會引發(fā)異常。
10) 抽象類:在Java中如果class被定義為abstract class,該類將不能被實例化,如果子類未能完全實現(xiàn)超類中所有的抽象方法,那么子類也將會被視為抽象類。C++中沒有特殊的關鍵字來表示抽象類,而且通過將類中的一個或多個方法定義為純虛方法來間接實現(xiàn)的,見如下C++代碼,其中的first和second均為純虛方法,既在方法的尾部添加" = 0 "。
- class AbstractClass {
- public:
- virtual void first() = 0;
- virtual void second() = 0;
- virtual void third();
- }
11) protected關鍵字在Java和C++中針對域方法和域字段的訪問方式存在著不同的限制級別,相同之處是protected的方法和字段都可以被子類直接訪問,不同之處是Java中相同包中的類也可以直接他們。C++自身并不存在包的概念,然而即便是相同名字空間內(nèi)的對象也不能直接訪問。
2. Object:
Java是單根結構的框架,所有的對象都是Object的子類,即使在對象聲明時沒有進行直接的指定,Java的編譯器將會自行搞定這些。C++中沒有適當?shù)念愖鳛樗袑ο蟮母悾欢谟行╊悗熘锌梢宰孕卸x,如MFC的CObject等。Java的Object中有3個非常重要的方法equals、hashCode和toString。如果子類中重載了他們中的任意一個方法,同時也建議重載另外兩個域方法。
1) equals: 主要用于判定兩個對象是否相等。類的實現(xiàn)者可以根據(jù)自己的真實邏輯來重新實現(xiàn)該方法,通用實現(xiàn)規(guī)則見下例:
- public class Employee {
- //1. 顯式參數(shù)命名為otherObject,稍后需要將它轉(zhuǎn)換成另一個叫做other的變量。
- public boolean equals(Object otherObject) {
- //2. 檢測this與otherObject是否引用同一個對象(一種優(yōu)化)
- if (this == otherObject)
- return true;
- //3. 檢測otherObject是否為null,如果null,則返回false。
- if (otherObject == null)
- return false;
- //4. 比較this與otherObject是否屬于同一個類。
- //如果子類中的equals語義各不相同,使用下面的getClass方式,精確定義類類型。
- if (getClass() != otherObject.getClass())
- return false;
- //如果子類中的equal語義和超類完全相同,可以使用instanceof檢測即可。
- //5. 將otherObject轉(zhuǎn)換為相應的類類型變量
- Employee other = (Employee)otherObject;
- //6. 現(xiàn)在開始對所有需要比較的域進行比較了。其中使用==比較基本類型,
- //使用equals比較對象類型。
- return name.equals(other.name) && salary == other.salary;
- }
- }
注:數(shù)組元素的比較可以調(diào)用Arrays.equals方法檢測。如果子類中重新定義了equals方法,就要在其中包含調(diào)用super.equals(other).
Java在語言規(guī)范中給出了自定義equals方法需要遵守的規(guī)則:
◆ 自反性: 對于任何非空引用x,x.equals(x)應該返回true。
◆ 對稱性: 對于任何引用x和y,當且僅當y.equals(x)返回true,x.equals(y)也應該返回true。
◆ 傳遞性: 對于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也應該返回true。
◆ 一致性: 如果x和y引用的對象沒有發(fā)生變化,反復調(diào)用x.equals(y)應該返回同樣的結果。
對于任意非空引用x,x.equals(null)應該返回false。
2) hashCode: 導出一個經(jīng)過哈希計算后的整型值,Java對hashCode的缺省實現(xiàn)是返回當前對象的存儲地址。一下列出String的hashCode實現(xiàn)方式:
- public int hashCode() {
- int hash = 0;
- for (int i = 0; i < length(); ++i)
- hash = 31 * hash + charAt(i);
- return hash;
- }
注:自定義類型的equals和hashCode定義必須一致,如果x.equals(y)返回true,那么x.hashCode()就必須與y.hashCode()具有相同的值。如果打算實現(xiàn)自定義的hashCode方法,推薦使用在對象構造初始化后就不會再改變的域字段作為hashCode的計算因子。否則一旦使用可變域資源作為hashCode計算因子的一部分,將會導致一些隱藏的問題。比如當Employee對象實例存入HashMap中,但是使用者在存入集合之后,修改了某個參數(shù)hashCode計算的域字段的值,此后再在HashMap中查找原有對象時由于hashCode已經(jīng)改變,因此即使該對象已經(jīng)存入HashMap中,結果是仍然無法找到最初存入的對象了。數(shù)組類型的hashCode,可以通過Arrays.hashCode方法計算得出。
3) toString: Java中比較推薦的實現(xiàn)方式為:
- public String toString() {
- return getClass().getName() +
- "field1 = " + field1 +
- "field2 = " + field2;
- }
注:C#的Framework中也存在一個類似的Object對象,作為C#所有對象(包括自定義對象)的唯一根類,其中也有對應的3個方法equals、hashCode和toString。Effective C#中針對這3個方法提供了一個很好的建議,既如果自定義類重載了這3個方法中任何一個,那么強烈建議該類也重載另外兩個域方法。如對equals和toString而言,如果x.equals(y)返回true,那么x.toString.equals(y.toString)也將返回true,反之亦然。針對equals和hashCode域方法還有一種推薦的實現(xiàn)方式,如下:
- public bool equals(Object other) {
- return toString().equals(other.toString());
- }
- public int hashCode() {
- return toString().hashCode();
- }
3. 包裝類和自動打包:
1) 包裝器對象均為不可變對象,如String,既一旦初始化之后其值將不會再被改變。包裝器類是final類,不能為繼承。
2) 自動拆包和打包:Integer n = 3; n++; 在執(zhí)行n++時,Java編譯器將自動插入一條拆包指令,然后進行自增計算,***再將結果打入對象包內(nèi)。
3) 自動打包的規(guī)范要求boolean, byte, char <= 127, 和介于-128--127之間的short和int被包裝到固定的對象中,見如下代碼:
- public void test() {
- Integer a1 = 1000;
- Ingeger a2 = 1000;
- if (a1 == a2)
- System.out.println(
- "This won't be printed out because they are greater than 127.");
- Integer a3 = 100;
- Ingeger a4 = 100;
- if (a3 == a4)
- System.out.println(
- "This will be printed out because they are less then 127.");
- }
4) 打包和拆包過程是編譯器行為,不是虛擬機行為,是編譯器在生成字節(jié)碼的時候自動插入的指令。
5) 包裝類在容器中的應用。對于Java提供的泛型容器類其類型參數(shù)不能是primitive type,如int、float等,如果確實需要添加類似的數(shù)據(jù),需要將相應的包裝類作為容器類型參數(shù),之后在插入原始類型數(shù)據(jù),但是在插入過程中Java的編譯器將自動插入打包指令,因此實際插入到容器中的仍然是包裝類對象,見如下代碼:
- public static void main(String args[]) {
- ArrayList<Integer> l = new ArrayList<Integer>();
- for (int i = 0; i < 10; ++i)
- l.add(i);
- for (int i = 0; i < l.size(); ++i) {
- System.out.printf("The value is %d.\t",l.get(i));
- System.out.printf("The class name is %s.\n"
- , l.get(i).getClass().getName());
- }
- }
- /* 結果如下:
- The value is 0. The class name is java.lang.Integer.
- The value is 1. The class name is java.lang.Integer.
- The value is 2. The class name is java.lang.Integer.
- The value is 3. The class name is java.lang.Integer.
- The value is 4. The class name is java.lang.Integer.
- The value is 5. The class name is java.lang.Integer.
- The value is 6. The class name is java.lang.Integer.
- The value is 7. The class name is java.lang.Integer.
- The value is 8. The class name is java.lang.Integer.
- The value is 9. The class name is java.lang.Integer.
- */
4. Java函數(shù)的變參表示方式:
PrintStream printf(String fmt,Object...args),其效果相當于 PrintStream printf(String fmt,Object[] args)。在C++中變參的表示方式為int printf(const char* fmt, ...); 其后的缺省參數(shù)需要通過C語言中提供的宏VA_LIST來協(xié)助完成。
原文鏈接:http://www.cnblogs.com/stephen-liu74/archive/2011/07/27/2118660.html
【系列文章】