第一種說法
1. 簡單類型是按值傳遞的
Java 方法的參數是簡單類型的時候,是按值傳遞的 (pass by value)。這一點我們可以通過一個簡單的例子來說明:
/* 例 1 */
/**
@(#) Test.java *
@author fancy */
public class Test {
public static void test(boolean test) {
test = ! test;
System.out.println("In test(boolean) : test = " + test);
}
public static void main(String[] args) {
boolean test = true;
System.out.println("Before test(boolean) : test = " + test); test(test);
System.out.println("After test(boolean) : test = " + test);
}
}
運行結果:
Before test(boolean) : test = true
In test(boolean) : test = false
After test(boolean) : test = true
不難看出,雖然在 test(boolean) 方法中改變了傳進來的參數的值,但對這個參數源變量本身並沒有影響,即對 main(String[]) 方法裏的 test 變量沒有影響。那說明,參數類型是簡單類型的時候,是按值傳遞的。以參數形式傳遞簡單類型的變量時,實際上是將參數的值作了一個拷貝傳進方法函數的,那麼 在方法函數裏再怎麼改變其值,其結果都是隻改變了拷貝的值,而不是源值。
2. 什麼是引用
Java 是傳值還是傳引用,問題主要出在對象的傳遞上,因爲 Java 中簡單類型沒有引用。既然爭論中提到了引用這個東西,爲了搞清楚這個問題,我們必須要知道引用是什麼。
簡單的說,引用其實就像是一個對象的名字或者別名 (alias),一個對象在內存中會請求一塊空間來保存數據,根據對象的大小,它可能需要佔用的空間大小也不等。訪問對象的時候,我們不會直接是訪問對象 在內存中的數據,而是通過引用去訪問。引用也是一種數據類型,我們可以把它想象爲類似 C 語言中指針的東西,它指示了對象在內存中的地址——只不過我們不能夠觀察到這個地址究竟是什麼。
如果我們定義了不止一個引用指向同一個對象,那麼這些引用是不相同的,因爲引用也是一種數據類型,需要一定的內存空間來保存。但是它們的值是相同的,都指示同一個對象在內存的中位置。比如
String a = "Hello";
String b = a;
這裏,a 和 b 是不同的兩個引用,我們使用了兩個定義語句來定義它們。但它們的值是一樣的,都指向同一個對象 "Hello"。也許你還覺得不夠直觀,因爲 String 對象的值本身是不可更改的 (像 b = "World"; b = a; 這種情況不是改變了 "World" 這一對象的值,而是改變了它的引用 b 的值使之指向了另一個 String 對象 a)。那麼我們用 StringBuffer 來舉一個例子:
/* 例 2 */
/**
@(#) Test.java *
@author fancy */
public class Test {
public static void main(String[] args) {
StringBuffer a = new StringBuffer("Hello");
StringBuffer b = a;
b.append(", World");
System.out.println("a is " + a); }
}
運行結果:
a is Hello, World
這個例子中 a 和 b 都是引用,當改變了 b 指示的對象的值的時候,從輸出結果來看,a 所指示的對象的值也改變了。所以,a 和 b 都指向同一個對象即包含 "Hello" 的一個 StringBuffer 對象。
這裏我描述了兩個要點:
- 引用是一種數據類型,保存了對象在內存中的地址,這種類型即不是我們平時所說的簡單數據類型也不是類實例(對象);
- 不同的引用可能指向同一個對象,換句話說,一個對象可以有多個引用,即該類類型的變量。
3. 對象是如何傳遞的呢
關於對象的傳遞,有兩種說法,即“它是按值傳遞的”和“它是按引用傳遞的”。這兩種說法各有各的道理,但是它們都沒有從本質上去分析,即致於產生了爭論。
既然現在我們已經知道了引用是什麼東西,那麼現在不妨來分析一下對象作是參數是如何傳遞的。還是先以一個程序爲例:
/* 例 3 */
/**
@(#) Test.java *
@author fancy */
public class Test {
public static void test(StringBuffer str) {
str.append(", World!");
}
public static void main(String[] args) {
StringBuffer string = new StringBuffer("Hello");
test(string);
System.out.println(string); }
}
運行結果:
Hello, World!
test(string) 調用了 test(StringBuffer) 方法,並將 string 作爲參數傳遞了進去。這裏 string 是一個引用,這一點是勿庸置疑的。前面提到,引用是一種數據類型,而且不是對象,所以它不可能按引用傳遞,所以它是按值傳遞的,它麼它的值究竟是什麼呢? 是對象的地址。
由此可見,對象作爲參數的時候是按值傳遞的,對嗎?錯!爲什麼錯,讓我們看另一個例子:
/* 例 4 */
/** *
@(#) Test.java *
@author fancy */
public class Test {
public static void test(String str) {
str = "World";
}
public static void main(String[] args) {
String string = "Hello";
test(string);
System.out.println(string); }
}
運行結果:
Hello
爲什麼會這樣呢?因爲參數 str 是一個引用,而且它與 string 是不同的引用,雖然它們都是同一個對象的引用。str = "World" 則改變了 str 的值,使之指向了另一個對象,然而 str 指向的對象改變了,但它並沒有對 "Hello" 造成任何影響,而且由於 string 和 str 是不同的引用,str 的改變也沒有對 string 造成任何影響,結果就如例中所示。
其結果是推翻了參數按值傳遞的說法。那麼,對象作爲參數的時候是按引用傳遞的了?也錯!因爲上一個例子的確能夠說明它是按值傳遞的。
結果,就像光到底是波還是粒子的問題一樣,Java 方法的參數是按什麼傳遞的問題,其答案就只能是:即是按值傳遞也是按引用傳遞,只是參照物不同,結果也就不同。
4. 正確看待傳值還是傳引用的問題
要正確的看待這個問題必須要搞清楚爲什麼會有這樣一個問題。
實際上,問題來源於 C,而不是 Java。
C 語言中有一種數據類型叫做指針,於是將一個數據作爲參數傳遞給某個函數的時候,就有兩種方式:傳值,或是傳指針,它們的區別,可以用一個簡單的例子說明:
/* 例 5 */
/** *
@(#) test.c *
@author fancy */
void SwapValue(int a, int b)
{ int t = a;
a = b;
b = t;}
void SwapPointer(int * a, int * b) {
int t = * a;
* a = * b;
* b = t;
}
void main() {
int a = 0, b = 1;
printf("1 : a = %d, b = %d/n", a, b);
SwapValue(a, b);
printf("2 : a = %d, b = %d/n", a, b);
SwapPointer(&a, &b);
printf("3 : a = %d, b = %d/n", a, b);}
運行結果:
1 : a = 0, b = 1
2 : a = 0, b = 1
3 : a = 1, b = 0
大家可以明顯的看到,按指針傳遞參數可以方便的修改通過參數傳遞進來的值,而按值傳遞就不行。
當 Java 成長起來的時候,許多的 C 程序員開始轉向學習 Java,他們發現,使用類似 SwapValue 的方法仍然不能改變通過參數傳遞進來的簡單數據類型的值,但是如果是一個對象,則可能將其成員隨意更改。於是他們覺得這很像是 C 語言中傳值/傳指針的問題。但是 Java 中沒有指針,那麼這個問題就演變成了傳值/傳引用的問題。可惜將這個問題放在 Java 中進行討論並不恰當。
討論這樣一個問題的最終目的只是爲了搞清楚何種情況才能在方法函數中方便的更改參數的值並使之長期有效。
Java 中,改變參數的值有兩種情況,第一種,使用賦值號“=”直接進行賦值使其改變,如例 1 和例 4;第二種,對於某些對象的引用,通過一定途徑對其成員數據進行改變,如例 3。對於第一種情況,其改變不會影響到方法該方法以外的數據,或者直接說源數據。而第二種方法,則相反,會影響到源數據——因爲引用指示的對象沒有變,對 其成員數據進行改變則實質上是改變的該對象。
5. 如何實現類似 swap 的方法
傳值還是傳引用的問題,到此已經算是解決了,但是我們仍然不能解決這樣一個問題:如果我有兩個 int 型的變量 a 和 b,我想寫一個方法來交換它們的值,應該怎麼辦?
結論很讓人失望——沒有辦法!因此,我們只能具體情況具體討論,以經常使用交換方法的排序爲例:
/** 例 6 */
/** *
@(#) Test.java *
@author fancy */
public class Test {
public static void swap(int[] data, int a, int b) {
int t = data[a];
data[a] = data[b];
data[b] = t;
}
public static void main(String[] args) {
int[] data = new int[10];
for (int i = 0; i < 10; i++) {
data[i] = (int) (Math.random() * 100); System.out.print(" " + data[i]);
}
System.out.println();
for (int i = 0; i < 9; i++) {
for (int j = i; j < 10; j++) {
if (data[i] > data[j]) {
swap(data, i, j); }
}
}
for (int i = 0; i < 10; i++) {
System.out.print(" " + data[i]);
}
System.out.println();
}
}
運行結果(情況之一):
78 69 94 38 95 31 50 97 84 1
1 31 38 50 69 78 84 94 95 97
swap(int[] data, int a, int b) 方法在內部實際上是改變了 data 所指示的對象的成員數據,即上述討論的第二種改變參數值的方法。希望大家能夠舉一反三,使用類似的方法來解決相關問題。
第二種說法
關於JAVA中參數傳遞問題有兩種,一種是按值傳遞(如果是基本類型),另一種是按引用傳遞(如果是對象).
首先以兩個例子開始:
1)
public class Test2 {
public static void main (String [] args) {
StringBuffer a = new StringBuffer ("A");
StringBuffer b = new StringBuffer ("B");
operate (a,b);
System.out.println(a+","+b);
}
static void operate(StringBuffer x, StringBuffer y){
x.append(y);
y = x;
}
}
輸出:AB,B
2)
public class Test2 {
public static void add3 (Integer i){
int val=i.intValue();
val += 3;
i = new Integer (val);
}
public static void main (String args [ ] ) {
Integer i = new Integer (0);
add3 (i);
System.out.println (i.intValue ( ));
}
}
輸出:0
首先我們應該明白JAVA中的參數傳遞全是以值傳遞的。
是基本類型,就拷貝一個基本類型傳進方法;
是引用,就拷貝一個引用變量傳進去方法,
理解了這兩點就能理解方法操作對象的相關問題了。最好能畫出引用指向對象的圖出來,就能完全理解了。
第1 題,調用operate方法時,傳入了兩個引用a,b的拷貝x,y,這兩個x,y都指向原a,b引用所指向的對象。x.append(y)對它指向的對象 (即a指向的對象)進行了操作。而x=y,只是兩個拷貝變量在賦值,並沒有影響到原b所指向的對象。所以b所指向的對象仍然爲B。
第2題,i=new Integer(val)只是一個引用的拷貝指向了另外一個對象,而原來的i仍然是指向對象new Integer(0)的。
把握住了JAVA都是傳值並且傳的都是拷貝的話,類似的題大家都能迎刃而解了。
Java中的參數傳遞只有一種方式: by value. 理論說教太麻煩了,直接看些例子吧:
1). 基本類型
public class A{
public static void main(String[] args){
int x = 1;
System.out.println(x); //1
test(x);
System.out.println(x); //還是1==>By value
}
static void test(int a){
a = 2;
}
}
2). 引用類型
public class B{
public static void main(String[] args){
Integer x = new Integer(1);
System.out.println(x);
test(x);
System.out.println(x);
}
static void test(Integer a){
a = new Integer(2);
}
}
理解這裏的關鍵是區分對象和引用。 這裏聲明的x是一個引用,而不是一個對象(只是Java把它設計爲看上去好像是對象一樣)。這個引用它指向了一個對象,這個對象就是後面用new關鍵字生成的對象。因此,可以說x指向了一個Integer對象。
在調用test方法的時候,程序將x作爲參數傳遞給test方法了。這裏仍然是值傳遞,在test調用過程中,會產生一份新的引用(不妨叫做y)。此時,x和y指向了同一個對象。
x和y指向的是同一個對象, 由於Java的設計,我們可以通過操作引用來達到操作對象的目的。因此,如果我們此時使用y來修改對象的屬性 (例如,y.someField++); 你可以看到x指向的對象同時也被修改到了。
另一方面,如果我們讓y指向另外一個對象, y=new Integer(2); 此時x和y就指向了不同的
對象。y修改了它指向的對象的屬性,很顯然不會影響到x指向的對象。
有人說了數組。數組也是一個引用類型,它的參數傳遞方式按照引用類型的參數傳遞一樣可以解釋得通:
import java.util.Arrays;
public class A{
public static void main(String[] args){
int[] aa = {3, 2, 1};
System.out.println(Arrays.toString(aa)); //[3, 2, 1]
test(aa);
System.out.println(Arrays.toString(aa)); //[3, 2, 1]
test2(aa);
System.out.println(Arrays.toString(aa)); //[4, 2, 1]
}
static void test(int[] a){
a = new int[]{1, 2, 3}; //指向了新對象
}
static void test2(int[] a){
if(a != null && a.length > 0)
a[0]++; //修改原來的那個對象
}
}
對象是傳引用,簡單類型是傳值,不要被網上的一些概念所迷惑!!!你可以自己做個試驗。
至於String等類型傳的還是引用。如果你用concat方法,String對象的原值就會被改變。
但你如果按如下方法:
public class Test {
public static void test(String str) {
str = "World";
}
public static void main(String[] args) {
String string = "Hello";
test(string);
System.out.println(string);
}
}
運行結果:Hello
這裏str = "World" 就等同於 String str=new String("World")。所以結果沒有改變!!!
下列程序在1處是否會有異常,如果沒有,輸出是什麼?是否會運行到2處,如果會,輸出是什麼?爲什麼會有這樣的結果?
import java.util.arraylist;
import java.util.list;
public class testclass {
public static void main(string args[]) {
list list = new arraylist();
test2(list);
system.out.println(list.size()); // 1處
test3(list);
system.out.println(list.size()); // 2處
}
public static void test2(list list) {
list = null;
}
public static void test3(list list) {
list.add(“aaaa“);
}
}
plumechen:
不會出錯的。結果是0,1。
因 爲test2(list)傳得是list的引用,我理解成指針置的副本,list=null;只是把那個傳入的值設置爲null,不改變原來list的指 針和內容。test3(list)傳入的一樣,但是執行了list.add()由於傳入指針值的副本也指向原來的那個list的地址,所以原來的那個 list的內容就改變了,size變成了1了
總結:對於第一種說法,注意理解“引用是一種數據類型”,對於第二種說法,要理解“參數是引用的一個拷貝”