JAVA值传递 & 引用传递
发现篇好的文章,在此记录下:
https://www.jianshu.com/p/d3edb33e2f73
https://www.cnblogs.com/duanxz/archive/2013/04/24/3040139.html
JAVA值传递 & 引用传递
先解释一下分别是什么意思,
值传递:作为参数传递时只传递了值,参数本身没有传递,如果被传递的方法改变传递后的变量参数值,原变量不会改变。在JAVA中显式的值传递参数是各类型变量例如: int double char String Integer 等都是。
引用传递: 作为参数传递时传递的是参数本身,当被传递方法改变传递后的变量参数值,原参数也会发生改变。一般来说我们自定义的类基本都属于这种情况,那么为什么String类型变量本质也是对象但是没有出现这种情况,这个后边会详细解释。
堆 & 栈 的定义在之前已经说过,那么这里说一下,基本类型变量 int boolean char float double等这些基本类型在定义后其数据是存在栈里这里说过,那么基本类型变量传递的时候是只传递了一个值,新变量本身除了值和原型一致就没有任何其他关系,所以基本类型变量传递是值传递。(注意虽然java是面向对象编程,但是基本类型变量不是对象(数组是对象),为了解决这个问题有了封装类。)
那么String呢?我们将 str传递过去实际上相当于 String str=“呵呵”; String st=str;那么传递的时候这里注意了,这里传递的时“地址”这个值,所以 st=str 只是将str所指向的地址赋值给st,按理说既然是st和str公用一个地址那么当st改变时str也应该改版对吧,那么为什么原变量str无改变呢。这里涉及到String 及 Integr 等封装类的源代码,看下Stirng的源代码
发现没,这里value是final常量不可改的,而且在源代码里没有set方法,也就意味着不能一旦定义不能修改值,那么也就意味着平时我们修改值实际上是相当于在堆里新划分出了一块空间,创建了一个新的String对象,然后修改变量的指向使其指向新对象,所以值改变了。
但是我们平时定义类的时候很少讲成员变量定义为final并且不设定set方法,所以我们自定义类传递时传递的是堆的指向也就地址,并且成员变量是可改的那么这时效果是引用传递效果(虽然本质是值传递)。
使用反射可以修改String的实际值
import java.lang.reflect.Field;
public class SwapStringValue {
public static void main(String[] args) throws Exception {
String a = "hello";
String b = "world";
System.out.println("交换前");
System.out.println(a);
System.out.println(b);
swap(a,b);
System.out.println("交换后");
System.out.println(a);
System.out.println(b);
}
static void swap(String a, String b) throws NoSuchFieldException, IllegalAccessException {
Class<? extends String> aClass = a.getClass();
Class<? extends String> bClas = b.getClass();
Field aValue = aClass.getDeclaredField("value");
Field bValue = aClass.getDeclaredField("value");
aValue.setAccessible(true);
bValue.setAccessible(true);
char[] aChars = (char[]) aValue.get(a);
char[] bChars = (char[]) bValue.get(b);
for (int i = 0; i < aChars.length; i++) {
char temp = aChars[i];
aChars[i] = bChars[i];
bChars[i] = temp;
}
}
Java中,String类型和包装类型作为参数传递时,是属于值传递还是引用传递呢?
原理知识:
如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的。如果在函数中改变了副本的 值不会改变原始的值.
如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的 值,那么在函数内的改变会影响到传入的参数。
如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的 地址,所以不会改变参数的值。
public class Test {
public static void test1(Integer num){
num = new Integer(5);
}
public static void test2(String str){
str.replace("1", "4");
}
public static void main(String[] args) {
Integer num = new Integer(1);
test1(num);
// 输出结果为1
System.out.println(num.intValue());
String str = new String("123");
test2(str);
// 输出结果为123
System.out.println(str);
}
}
分析:
上述程序很容易让人误以为String类型和包装类型是值传递。
其实我们认真想一下:
String类型和包装类型都是对象类型,所以必然是引用传递。
但是由于String类和包装类都没有提供value对应的setter方法,我们无法改变其内容,所以导致我们看起来好像是值传递。
基本类型的数组对象,可以作为载体带回返回值
package com.dfs.util;
public class Test5 {
public static void main(String[] args) {
int[] a = {1,2,3};
test(a);
for(int item : a) {
System.out.println(item);
}
}
private static void test(int[] a) {
a[1] = 100;
}
}
结果:
1
100
3
总结
string、包装类、基本类型参数传递不变
char数组,自定义类参数传递变