$emit传递多个参数_10年架构师深解java核心技术:方法参数+对象构造,确定不学?...
方法參數
首先回顧一下在程序設計語言中有關參數傳遞給方法(或函數)的一些專業術語。值調用(call by value)表示方法接收的是調用者提供的值。而引用調用(call by reference)表示方法接收的是調用者提供的變量位置。可以想到,一個方法可以修改傳遞引用所對應的變量值,而不能修改傳遞值調用所對應的變量值。這一點不僅僅是Java 語言,其他程序設計語言也是如此。“……調用”(call by)是一個標準的計算機科學術語,它用來描述各個程序設計語言中方法參數的傳遞方式。(事實上,以前還有名稱調用(call by name),Algol程序設計語言—最古老的高級程序設計語言之一—使用的就是這種參數傳遞方式。不過,對于今天來說,這種傳遞方式已經成為歷史。)Java程序設計語言使用值調用。也就是說,方法得到的是所有參數值的一個拷貝,特別是,方法不能修改傳遞給它的任何參數變量的內容。
例如,考慮下面的調用:
double percent = 10;
harry.raiseSalary(percent);
不必理睬這個方法的具體實現,在方法調用之后,percent的值還是10。
下面再仔細地研究一下這種情況。假定一個方法試圖將一個參數值增加至3倍:
public static void tripleValue(double x) // doesn’t work{x = 3 * x;}然后調用這個方法:double percent = 10;tripleValue(percent);可以看到,無論怎樣調用這個方法,執行之后,percent的值還是10。下面看一下具體的執行
過程:
1)x被初始化為percent值的一個拷貝(也就是10)。
2)x被乘以3后等于30。但是percent仍然是10(如圖4-6所示)。
3)這個方法結束之后,參數變量x不再使用。
然而,方法參數共有兩種類型:
? 基本數據類型(數字、布爾值)。
? 對象引用。
已經看到,一個方法不可能修改一個基本數據類型的參數。而對象引用作為參數就不同了,
我們可以很容易地利用下面這個方法實現將一個雇員的薪金提高兩倍的操作:
public static void tripleSalary(Employee x) // works{x.raiseSalary(200);}當調用harry = new Employee(. . .);tripleSalary(harry);具體的執行過程為:
1)x被初始化為harry值的拷貝,這里是一個對象的引用。
2)raiseSalary方法應用于這個對象引用。x和harry同時引用的那個Employee對象的薪金提高了200%。
3)方法結束后,參數變量x不再使用。當然,對象變量harry繼續引用那個薪金增至3倍的雇員對象(如圖4-7所示)。
已經看到,實現一個改變對象參數狀態的方法并不是一件難事。理由很簡單,方法得到的是對象引用的拷貝,對象引用及其他的拷貝同時引用同一個對象。
很多程序設計語言(特別是,C++和Pascal)提供了兩種參數傳遞的方式:值調用和引用調用。有些程序員認為Java 程序設計語言對對象采用的是引用調用,實際上,這種理解是不對的。
由于這種誤解具有一定的普遍性,所以下面給出一個反例,來詳細地闡述一下這個問題。
首先,編寫一個交換兩個雇員對象的方法:
public static void swap(Employee x, Employee y) // doesn't work{Employee temp = x;x = y;y = temp;}如果Java程序設計語言對對象采用的是引用調用的話,這個方法應該能夠實現交換數據的效果:
Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b);
// does a now refer to Bob, b to Alice?
但是,方法并沒有改變存儲在變量a和b中的對象引用。swap方法的參數x和y被初始化為兩個對象引用的拷貝,這個方法交換的是這兩個拷貝。
// x refers to Alice, y to Bob
Employee temp = x;
x = y;
y = temp;
// now x refers to Bob, y to Alice
最終,白費力氣。在方法結束時參數變量x和y被丟棄了。原來的變量a和b仍然引用這個方法調用之前所引用的對象(如圖4-8所示)。
這個過程說明:Java 程序設計語言對對象采用的不是引用調用,實際上,對象引用進行的是值傳遞。
下面總結一下在Java程序設計語言中,方法參數的使用情況:
? 一個方法不能修改一個基本數據類型的參數(即數值型和布爾型值)。
? 一個方法可以改變一個對象參數的狀態。
? 一個方法不能讓對象參數引用一個新的對象。
例4-4中的程序給出了相應的演示。在這個程序中,首先試圖將一個值參數的值提高兩倍,但沒有成功:
Testing tripleValue:
Before: percent=10.0
End of method: x=30.0
After: percent=10.0
隨后,成功地將一個雇員的薪金提高了兩倍:
Testing tripleSalary:
Before: salary=50000.0
End of method: salary=150000.0
After: salary=150000.0
方法結束之后,harry引用的對象狀態發生了改變。這是因為這個方法可以通過對象引用的拷貝修改所引用的對象狀態。
最后,程序演示了swap方法的失敗效果:
Testing swap:
Before: a=Alice
Before: b=Bob
End of method: x=Bob
End of method: y=Alice
After: a=Alice
After: b=Bob
可以看出,參數變量x和y被交換了,但是變量a和b沒有受到影響。
例4-4 ParamTest.java
對象構造
前面已經學會了編寫簡單的構造器,以便對定義的對象進行初始化。但是,由于對象構造非常重要,所以Java提供了多種機制來編寫構造器。下面將介紹這些內容。
重載
從前面可以看到,GregorianCalendar類有多個構造器。我們可以使用:
GregorianCalendar today = new GregorianCalendar( );
或者
GregorianCalendar deadline = new GregorianCalendar(2099, Calendar.DECEMBER, 31);
這種能力叫做重載(overloading)。如果多個方法(比如,GregorianCalendar構造器方法)有相同的名字、不同的參數,便產生了重載。編譯器必須挑選出具體執行哪個方法,它通過用各個方法給出的參數類型與特定方法調用所使用的值類型進行匹配來挑選出正確的方法。如果編譯器找不到匹配的參數,或者找出多個可能的匹配,就會產生編譯時錯誤(這個過程被稱為重載解析(overloading resolution)。)
默認域初始化
如果在構造器中沒有顯式地給域賦予初值,它就會被自動地賦為默認值:數值為0、布爾值為flase、對象引用為null。然而,只有缺少程序設計經驗的人才會這樣做。確實,如果不明確地對域進行初始化,就會影響程序代碼的可讀性。
默認構造器
所謂默認構造器是指沒有參數的構造器。例如,Employee類的默認構造器:
public Employee( ){name = "";salary = 0;hireDay = new Date( );}如果在編寫一個類時沒有編寫構造器,系統就會提供一個默認構造器。這個默認構造器將所有的實例域設置為默認值。于是,實例域中的數值型數據設置為0、布爾型數據設置為false、所有對象變量將設置為null。
如果類中提供了至少一個構造器,但是沒有提供默認的構造器,那么在構造對象時若不提供構造參數就被視為不合法。例如,在例4-2中的Employee類提供了一個簡單的構造器:
Employee(String name, double salary, int y, int m, int d)
對于這個類,構造默認的雇員屬于不合法。也就是,調用
e = new Employee( );
將會產生錯誤。
顯式域初始化
由于類的構造器方法可以重載,所以可以采用多種形式設置類的實例域的初始狀態。確保不管怎樣調用構造器,每個實例域都可以被設置為一個有意義的初值。這是一種很好的設想。
可以在類的定義中,簡單地將一個值賦給任何域。例如:
在執行構造器之前,先執行賦值。當一個類的所有構造器都希望將相同的值賦予某個特定的實例域時,這種方式特別有用。
初始值不一定是常量。在下面的例子中,域可以調用方法來進行初始化。仔細看一下Employee類,其中每個雇員有一個id域。可以使用下列方式進行初始化:
參數名
在編寫很小的構造器時(這是十分常見的),常常在參數命名上出現錯誤。
通常,參數用單個字符命名:
然而,這樣做有一個缺陷:只有閱讀代碼才能夠了解參數n和參數s的含義。
于是,有些程序員在每個參數前面加上一個前綴“a”:
這樣很清晰。每一個讀者一眼就能夠看懂參數的含義。
還一種常用的技巧,它基于這樣的事實:參數變量用同樣的名字將實例域屏蔽起來。例如,如果將參數命名為salary,那么salary將引用這個參數,而不是實例域。但是,可以采用this.salary的形式訪問實例域。回想一下,this指示隱式參數,也就是被構造的對象。下面是一個例子:
調用另一個構造器
關鍵字this引用方法的隱式參數。然而,這個關鍵字還有另外一個含義。
如果構造器的第一個語句形如this(...),那么這個構造器將調用同一個類的另一個構造器。下面是一個典型的例子:
當調用new Employee(60000) 時,Employee(double) 構造器將調用Employee(String, double) 構造器。
采用這種方式使用this關鍵字非常有用,這樣對公共的構造器代碼部分只編寫一次即可。
初始化塊
前面已經講過兩種初始化數據域的方法:
? 在構造器中設置值
? 在聲明中賦值
實際上,Java還有第三種機制,稱為初始化塊。在一個類的聲明中,可以包含多個代碼塊。
只要構造類的對象,這些塊就會被執行。例如,
在這個例子中,無論使用哪個構造器構造對象,id域都在對象初始化塊中被初始化。首先運行初始化塊,然后才運行構造器的主體部分。
這種機制不是必須的,也不常見。通常,直接將初始化代碼放置在一個構造器的內部。
由于初始化數據域有多種途徑,所以列出構造過程的所有路徑可能相當混亂。下面是調用構造器的具體處理步驟:
1)所有數據域被初始化為默認值(0、false或null)。
2)按照在類聲明中出現的次序依次執行所有域初始化語句和初始化塊。
3)如果構造器第一行調用了第二個構造器,則執行第二個構造器主體。
4)執行這個構造器的主體。
當然,應該精心地組織好初始化代碼,這樣有利于其他程序員的理解。例如,如果讓類的構造器行為依賴于數據域聲明的順序,就會顯得很奇怪并且容易引起錯誤。
可以通過提供一個初始化值,或者使用一個靜態的初始化塊來對靜態域進行初始化。前面已經介紹過第一種機制:
static int nextId = 1;
如果對類的靜態域進行初始化的代碼比較復雜,就可以使用靜態的初始化塊。
將代碼放置在一個塊中,并標記關鍵字static。下面是一個例子。其功能是將雇員ID的起始值賦予一個小于10 000的隨機整數。
在類第一次加載的時候,將會進行靜態域的初始化。與實例域一樣,靜態域的默認初值是 0、false或null,除非將它們顯式設置成其他值。所有的靜態初始化語句以及靜態初始化塊都將按照類定義中出現的順序執行。
例4-5 ConstructorTest.java
java.util.Random 1.0
? Random( )
構造一個新的隨機數生成器。
? int nextInt(int n) 1.2
返回一個0~n-1之間的隨機數。
對象析構與finalize方法
有些面向對象的程序設計語言,特別是C++,有顯式的析構器方法,其中放置一些當對象不再使用時所需要用到的清理代碼。在析構器中,最常見的操作是回收分配給對象的存儲空間。
由于Java有自動的垃圾回收器,不需要人工回收內存,所以Java不支持析構器。
當然,某些對象使用了內存之外的其他資源,如文件或使用了系統資源的另一個對象的句柄。在這種情況下,當資源不再需要的時候,將其回收和再利用十分重要。
可以為任何一個類添加finalize方法。finalize方法將在垃圾回收器清除對象之前被調用。在實際應用中,不要使用finalize方法回收任何短缺的資源,這是因為很難知道這個方法什么時候才能夠被調用。
覺得有收貨的話,可以轉發關注小編,之后持續更新技術干貨!!!!!
總結
以上是生活随笔為你收集整理的$emit传递多个参数_10年架构师深解java核心技术:方法参数+对象构造,确定不学?...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java.exe指的是什么意思
- 下一篇: 如何进行VMware vSphere虚拟