java中值传递与引用传递分析
0x01
java中是传值还是传引用(英文:java called by value or called by reference?)
首先搞清楚一点,不管参数的类型是什么,一律传递参数的副本。如果java是传值,那么传递的是值的副本,如果java传引用,那么传递的是引用的副本。
0x02
java中,变量类型分为两类:
①基本类型变量 int,long,double,float,byte,boolean,char,java中是传值的副本,副本变了,自己不变,这一点与C++中相同
②对于一切对象型变量,java都是传引用的副本,其实传引用的副本就是复制指向地址的指针,而不是自己实际值的副本,原因:以为对象类型是放在堆里面的,一方面,速度相对于基本类型比较慢,另一方面,对象类型本身比较大,如果采用重新复制对象值的办法,浪费内存且速度慢。
举个例子:张三(函数)要打开仓库(地址)并且检查库里的货物,有必要新建一座仓库并放入相同货物给张三吗?没有必要,只需要把钥匙(引用)复制一份给张三就好,张三会拿复制的钥匙(引用副本,有时效性,函数结束,钥匙销毁)打开仓库。
只不过java不像C++中有显著的*和&符号(这里java与C++不同,在C++中,当参数是引用类型时,传递的是真实引用而不是引用副本)。
还要注意String,String也是对象型变量,所以它必然是传引用副本,只不过它是一个非可变类,似的传值与传引用显得没什么区别。
关于很多java书籍中说的不管是基本数据类型还是对象类型,都是传值,这种说法也是对啊,以为他们把引用副本看成了一种值。
总结:String可以理解为修改了原来的副本,StringBuffer理解为修改的就是原值。
String是final类型的,不可以继承和修改这个类。
例子如下:
1 package com.day01; 2 3 public class zhiyuyinyong { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 // boolean test=true; 8 // System.out.println("beforetest:test="+test); 9 // test1(test); 10 // System.out.println("aftertest:test="+test); 11 12 //-------------------------- 13 // StringBuffer string=new StringBuffer("hello"); 14 // test2(string); 15 // System.out.println(string); 16 //输出hello,world 17 //原因:StringBuffer是产生一块内存空间,相关的增删改操作都在其中进行,所以添加一句“world”仍然是在同一段内存的地址上进行,str指向的引用并没有改变 18 //-------------------------- 19 String string="hello"; 20 System.out.println(string); 21 System.out.println("修改后的string="+test3(string)); 22 System.out.println(string); 23 24 //输出hello 25 //.ello 这个就是str指向了hello,str=str.replace("h", ".");就是对hello的副本进行了修改 26 //hello 27 //-------------------------- 28 //-------------------------- 29 //String string="hello"; 30 31 System.out.println("修改后的string="+test4(string)); 32 System.out.println(string); 33 34 //输出world 原因:str="world";这个过程是系统自动生成了新String对象,并把这个新对象的值设置为world,然后这个对象的引用是str,可以理解为str这把钥匙原来指向hello仓库,现在指向world仓库。隐含着新建了string对象后,新对象与hello没有关系,函数结束,str作用消失,原来内存地址上的内容未加改变,打印仍是hello 35 //-------------------------- 36 } 37 38 // public static void test1(boolean test){ 39 // boolean test22=true; 40 // test22=!test; 41 // //这个test就是主函数中传过来的bool值,我们对这个bool值就行修改,发现这个test仅仅是主函数中test的一个副本,修改这个test变量,主函数中的test是不变化的 42 // 43 // System.out.println("in test,test="+test22); 44 // } 45 46 public static void test2(StringBuffer str){ 47 str.append(".world"); 48 //这里形参传过来的就是一个StringBuffer的引用,java对于引用形式传递对象类型的变量时,实际上是将引用作为一个副本传进方法函数的,这个副本指向的是原来str对象的地址,通过引用副本找到地址,修改了地址中的值,地址中有对象,对象的值就被修改了 49 50 } 51 public static String test3(String str){ 52 //string.replaceAll(regex, replacement)=".world"; //这里string=“.world”相当于新建了一个String对象,这个与原来形参传过来的string没有任何关系了 53 str=str.replace("h", "."); 54 //这里形参传过来的就是一个String的引用 55 return str; 56 } 57 58 public static String test4(String str){ 59 //string.replaceAll(regex, replacement)=".world"; //这里string=“.world”相当于新建了一个String对象,这个与原来形参传过来的string没有任何关系了 60 str="world"; 61 //这里形参传过来的就是一个String的引用 62 return str; 63 } 64 }
涉及到内容的一些解释:
final是修饰变量的,不是修饰对象本身的
举例说明:
final String s = "hello";
这里final是修饰变量s的,而不是修饰"hello"
至于说String不能修改,是因为String类是immutable的,就是不能修改的意思,immutable也是一个修饰符
final StringBuffer sb = new StringBuffer("hello");
这里final修饰了sb,sb就不能指向其它对象了
比如sb = new StringBuffer("hi");这样是不允许的
但是sb所指向的那个对象本身还是可以改变的
比如sb.append(" world");是允许的,因为sb并没有变,sb仍然指向的是那个对象,变化的是对象本身