基本類型和引用類型在內存中的保存
Java中數(shù)據(jù)類型分為兩大類:基本類型和對象類型
變量也有兩種類型:基本類型和引用類型
基本類型的變量保存原始值遭商,即它代表的值就是數(shù)值本身
引用類型的變量保存引用值整以,"引用值"指向內存空間的地址盘榨,代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。
基本類型包括:byte
,short
,int
,long
,char
,float
,double
,boolean
引用類型包括:類類型辽幌,接口類型和數(shù)組
引用傳遞和值傳遞
值傳遞:
方法調用時,實際參數(shù)把它的值傳遞給對應的形式參數(shù)椿访,函數(shù)接收的是原始值的一個copy乌企,此時內存中存在兩個相等的基本類型,即實際參數(shù)和形式參數(shù)成玫,后面方法中的操作都是對形參這個值的修改加酵,不影響實際參數(shù)的值。
引用傳遞:
也稱為傳地址哭当。方法調用時猪腕,實際參數(shù)的引用(地址,而不是參數(shù)的值)被傳遞給方法中相對應的形式參數(shù)钦勘,函數(shù)接收的是原始值的內存地址陋葡;
在方法執(zhí)行中,形參和實參內容相同彻采,指向同一塊內存地址脖岛,方法執(zhí)行中對引用的操作將會影響到實際對象朵栖。
直接看下例子
public class Test {
public static void main(String args[]) {
int number=123;
Student stu1 = new Student();
stu1.setNumber(number);
Student stu2 =stu1;
stu2.setNumber(321);
System.out.println(number);
System.out.println(stu1 == stu2);
System.out.println("學生1:" + stu1.getNumber());
System.out.println("學生2:" + stu2.getNumber());
}
static class Student{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
}
測試結果:
在
main
方法中:number
是基本類型,采用值傳遞柴梆,不影響實際參數(shù)的值。Student
是類類型终惑,采用引用傳遞绍在,指向同一塊內存地址,對引用的操作將會影響到實際對象雹有。Student stu2 =stu1;
引用傳遞把stu1
和stu2
指向內存堆中同一個對象偿渡。所以當stu2.setNumber(321);
時,stu1
中的number
值也會發(fā)生改變
淺拷貝
想要復制一個新對象霸奕,也就是開辟一塊新的內存地址溜宽,那么就要使用到拷貝
1.需要實現(xiàn)Clonenable
接口,該接口為標記接口(不含任何方法)
2.覆蓋clone()
方法质帅,訪問修飾符設為public
适揉。方法中調用super.clone()
方法得到需要的復制對象
public class Test {
public static void main(String args[]) {
int number=123;
Student stu1 = new Student();
stu1.setNumber(number);
Student stu2 = (Student)stu1.clone();
stu2.setNumber(321);
System.out.println(number);
System.out.println(stu1 == stu2);
System.out.println("學生1:" + stu1.getNumber());
System.out.println("學生2:" + stu2.getNumber());
}
static class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
}
測試結果:
main
方法中用Student stu2 = (Student)stu1.clone();
代替Student stu2 =stu1;
,這樣才會指向不同的內存地址煤惩,stu1 == stu2
為false
說明指向的地址塊是不同的嫉嘀,所以對象中number
值也是不同的
深拷貝
之前在Student對象中的成員屬性都是基本類型的,那如果是引用類型的呢魄揉?
先看測試代碼
public class Test {
public static void main(String args[]) {
int number=123;
Address addr = new Address();
addr.setAdd("A");
Student stu1 = new Student();
stu1.setNumber(number);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
stu2.setNumber(321);
addr.setAdd("B");
System.out.println(number);
System.out.println(stu1 == stu2);
System.out.println(stu1.getAddr()==stu2.getAddr());
System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
static class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
static class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
}
測試結果:
可以看出Student
對象指向不同的內存地址剪侮,但是成員屬性Address
對象在stu1
和stu2
中都是指向同一內存地址
原因是淺拷貝只是拷貝了addr
變量的引用,并沒有真正的開辟另一塊空間洛退,將值復制后再將引用返回給新對象瓣俯。
所以,為了達到真正的拷貝對象兵怯,而不是純粹引用復制彩匕。我們需要將Address
類可復制化,并且修改clone
方法摇零,同時Student
類也要做下修改
static class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone();
return stu;
}
}
static class Address implements Cloneable{
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
重點:
1.在Address
類中實現(xiàn)Cloneable
接口推掸,修改clone
方法
2.在Student
類的clone
方法中加上stu.addr = (Address)addr.clone();
實現(xiàn)深拷貝
關于clone()
JDK API的說明文檔解釋這個方法將返回Object對象的一個拷貝。
和new
的區(qū)別
共同點:都是分配內存驻仅,對象都是指向不同的內存地址
不同點:new
創(chuàng)建一個對象谅畅,clone
復制一個對象。new
是返回的新對象噪服,而調用clone()
方法時毡泻,拷貝對象已經(jīng)包含了一些原來對象的信息,而不是對象的初始信息
復制引用(引用傳遞)和復制對象
引用都存放在棧區(qū)粘优,類對象存放于堆區(qū)
從源碼角度看clone()
protected native Object clone() throws CloneNotSupportedException;
在Object
類的clone()
是一個native
方法仇味,native
方法的效率一般來說都是遠高于Java中的非native
方法呻顽。這也解釋了為 什么要用Object
中clone()
方法而不是先new
一個類,然后把原始對象中的信息賦到新對象中丹墨,雖然這也實現(xiàn)了clone
功能廊遍。Java中所有的類是缺省繼承Object
類的,所以可以直接使用protected
屬性的方法
為什么要實現(xiàn)Cloneable
接口
Cloneable
接口是不包含任何方法的贩挣,其實這個接口僅僅是一個標志喉前,而且這個標志也僅僅是針對 Object
類中clone()
方法的,如果實現(xiàn)clone()
方法的類沒有實現(xiàn)Cloneable接口王财,并調用了Object
的clone()
方法(也就是調用了 super.clone()
方法)卵迂,那么Object
的clone()
方法就會拋出CloneNotSupportedException
異常
內存地址
在以上測試中我都是以==
去判斷兩個對象是否指向同一內存地址,如果要查看具體的內存地址值可以這樣做
1.可以使用hashCode()
方法绒净,在不重寫hashCode()
的情況下见咒,默認是返回內存地址,不同的hash
值代表不同的內存地址
2.使用System.identityHashCode(Object)
方法可以返回對象的內存地址,不管該對象的類是否重寫了hashCode()
方法
源碼展示
Object
類下
public native int hashCode();
System
類下
public static native int identityHashCode(Object x);
拓展
String
類重寫了hashcode()
private int hash;
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
Java中所有類是缺省繼承Object
類的挂疆,所以都有資格去重寫hashcode()
方法
總結:
對象內部只有基本數(shù)據(jù)類型
改览,那用 clone()
方法獲取到的就是這個對象的深拷貝
,
而如果對象內部還有引用數(shù)據(jù)類型
囱嫩,那用 clone()
方法就是一次淺拷貝
操作恃疯。
淺拷貝
對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型進行引用
傳遞般的拷貝
墨闲,此為淺拷貝
深拷貝
對基本數(shù)據(jù)類型進行值傳遞今妄,對引用數(shù)據(jù)類型,會對引用指向的對象
進行拷貝
鸳碧,此為深拷貝盾鳞。也就是在clone()
方法對其內的引用類型的變量再進行一次 clone()