理解Java中的值傳遞和引用傳遞,形參和實參
1. 兩大數據類型以及特殊的String
- 8種基本數據類型
int char byte boolean long short float double - 對象類型
1、8種基本數據類型的包裝類型
2、數組等
3、定義的對象 - 特殊: String
2. 兩大引用類型
- 基本類型
- 對象的引用
這兩者是有區別的
基本類型 | 對象的引用 |
---|---|
聲明定義的時候就爲實際的數據分配了空間 | 聲明時只是分配了一個引用的空間,一般是4個字節,實際對象的空間未分配 |
特殊的: String 類型只是定義的話,也只是分配了4個字節的引用空間
int a; // 已經爲存放a分配了內存
a=10;//正確,因爲聲明a時就分配了空間
Date a; //在內存開闢一個引用空間, 即實際才使用了4個字節
a = new Date();//開闢存儲Date對象的數據空間,並把該空間的首地址賦給a
String a; //在內存開闢一個引用空間
3. 值傳遞和引用傳遞
- 1.形參:
形式參數,用來接收調用該方法時傳遞的參數, 相當於一個實際參數的副本,只有在被調用的時候才分配內存空間,一旦調用結束,就釋放內存空間。因此僅僅在方法內有效 - 2.實參:
傳遞給被調用方法的值,預先創建並賦予確定值。
值傳遞:
方法調用時,實際參數把它的值傳遞給對應的形式參數,函數接收的是 原始值的一個copy,此時內存中存在兩個相等的基本類型,即實際參數和形式參數,後面方法中的操作都是 對形參這個值的修改,不影響實際參數的值。
引用傳遞
也稱爲傳地址。方法調用時,實際參數的引用(地址,而不是參數的值)被傳遞給方法中相對應的形式參數,函數接收的是原始值的內存地址;
在方法執行中,形參和實參內容相同,指向同一塊內存地址,方法執行中對引用的操作將會影響到實際對象。
具體例子說明一下:
public class A {
public static void main(String[] args) {
A t = new A();
int a = 99;
t.test1(a);//這裏傳遞的參數a就是按值傳遞
System.out.println(a);
MyObj obj=new MyObj();
obj.b = 10;
t.test2(obj);//這裏傳遞的參數obj就是引用傳遞
System.out.println(obj.b);
}
public void test1(int a){
a = a + 1;
System.out.println(a);
}
public void test2(MyObj obj){
obj.b = 100;
System.out.println(obj.b);
}
}
// 輸出結果
100
99
100
100
特殊的:對於String 類型, 以及8中基本類型的包裝類型等immutable不可變的Java類型,其傳遞方式爲值傳遞
4. 總結
- 1.java的基本數據類型是值傳遞,對象引用類型是引用傳遞,但是特殊的:String, Integer, Double等immutable的不可變類型可以理解爲值傳遞。
- 2.當傳值調用時,改變的是形參的值,並沒有改變實參的值,實參的值可以傳遞給形參,但是,這個傳遞是單向的,形參不能傳遞迴實參。
- 3.當引用調用時,傳遞的是地址,指向的是同一個對象,方法中的操作會改變實參對象的內容。
本文參考了博客: http://www.cnblogs.com/binyue/p/3862276.html
https://www.cnblogs.com/jaylon/p/5721571.html
這裏面牽扯到了 不可變類,簡單介紹一下:
不可變類
所謂的不可變類是指這個類的實例一旦創建完成後,就不能改變其成員變量值。如JDK內部自帶的很多不可變類(String+ 8種基本數據類型):Interger、Long 和 String等。
String類型如何設計成不可變類
- 1、類使用final修飾符,設計成不可被繼承的
- 2、成員變量,使用final修飾符,設計成不可以被修改的
- 3、成員變量設計成私有的private,而且不設置setter方法
- 4、通過構造器初始化所有成員,進行深拷貝(deep copy) 在getter方法中,不要直接返回對象本身,而是克隆對象,並返回對象的拷貝, String(char value[])
- 5、獲取,在getter方法中,不要直接返回對象本身,而是克隆對象,並返回對象的拷貝,toCharArray()方法
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; //存儲字符串
/** Cache the hash code for the string */
private int hash; // Default to 0 字符串的hash值
// 構造函數,使用深克隆
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
// 獲取時,不直接返回對象的引用,而是返回複製後的副本,防止外界進行修改
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
}
String 設計成不可變類的好處
- 1、設計成不可變的,方便 線程安全,可以被多個線程共享
- 2、字符串常量池 可以將一些字符常量放在常量池中重複使用,避免每次都重新創建相同的對象、節省存儲空間。但如果字符串是可變的,此時相同內容的String還指向常量池的同一個內存空間,當某個變量改變了該內存的值時,其他遍歷的值也會發生改變。所以不符合常量池設計的初衷
- 3、類加載器要用到字符串,不可變性提供了安全性,以便正確的類被加載。譬如你想加載java.sql.Connection類,而這個值被改成了myhacked.Connection,那麼會對你的數據庫造成不可知的破壞。
- 4、支持hash映射和緩存。
因爲字符串是不可變的,所以在它創建的時候hashcode就被緩存了,不需要重新計算。這就使得字符串很適合作爲Map中的鍵,字符串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字符串
String 的intern方法的作用:
如果常量池中存在字符,則返回常量池中字符的引用,
如果常量池中不存在字符,則將字符串添加到常量池中(在新的jdk1.7以上,在常量池中創建的字符串存儲的是堆中字符串的引用),返回該對象的引用。
更加詳細的請查看這篇博客
http://blog.csdn.net/seu_calvin/article/details/52291082
特殊的是,可以通過反射機制改變這一不可變的特性,通過反射,我們可以改變String類型的值的內容