在编码过程中,如果你经常使用Intellij IDEA中的抽取方法操作(Ctrl+Alt+M)对某个模块进行代码抽取,那么要小心无形之中的引用传递引发的bug。
首先我们要说明什么是值传递,什么是引用传递。
在Java中基本类型以及他们的包装类,包含String类,都是值传递。也就是说,在构造一个处理参数的函数时,如果传入了这些类型的句柄当做函数的输入参数引用,在函数内部对于输入参数引用进行重新赋值,不会影响外部原参数的值。看以下代码:
public static void main(String[] args) {
String origin = "ORIGIN";
int i = 1;
Integer integer = 2;
getString(origin);
System.out.println(origin);
System.out.println("origin out method:" + System.identityHashCode(integer));
System.out.println("-------------------------------------");
getNumber(i);
System.out.println(i);
System.out.println("-------------------------------------");
getInteger(integer);
System.out.println(integer);
System.out.println("integer out method:" + System.identityHashCode(origin));
}
private static Integer getInteger(Integer integer) {
integer = 20;
System.out.println("integer in method:" + System.identityHashCode(integer));
return integer;
}
private static int getNumber(int i2) {
i2 = 10;
return i2;
}
private static String getString(String origin) {
origin = "TEST CHANGE";
System.out.println("origin in method:" + System.identityHashCode(origin));
return origin;
}
//origin in method:558638686
//ORIGIN
//origin out method:1149319664
//-------------------------------------
//1
//-------------------------------------
//integer in method:2093631819
//2
//integer out method:2074407503
可以看到,尽管将三个参数作为输入参数放入方法中进行了重新的赋值,但最终方法外部打印出来的值还是原先的值。方法体外和引入方法体内的引用identityHashCode是不同的,所以可以认为一旦引入某个方法体,它就已经会被分配新的内存,是一个全新的版本。
但是对于对象,数组这一类型的数据,通常是引用传递,即传入的是该类型数据的地址或地址相关的字段,可以看看以下代码:
class Father {
private Integer age;
private String name;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Father{" +
"age=" + age +
", name=‘" + name + ‘\‘‘ +
‘}‘;
}
}
public static void main(String[] args) {
int[] intArray = {1, 2, 3};
String[] stringArray = {"origin1", "origin2", "origin3"};
Father father = new TestObjectAddress().new Father();
changeIntArray(intArray);
System.out.println(Arrays.toString(intArray));
System.out.println(System.identityHashCode(intArray));
System.out.println("---------------------------------");
changeStringArray(stringArray);
System.out.println(Arrays.toString(stringArray));
System.out.println(System.identityHashCode(stringArray));
System.out.println("---------------------------------");
changeObject(father);
System.out.println(father.toString());
System.out.println(System.identityHashCode(father));
}
private static void changeObject(Father father) {
if (father != null) {
father.age = 50;
}
System.out.println(System.identityHashCode(father));
}
private static void changeStringArray(String[] stringArray) {
if (stringArray != null && stringArray.length > 0) {
stringArray[0] = "changed1";
}
System.out.println(System.identityHashCode(stringArray));
}
private static void changeIntArray(int[] intArray) {
if (intArray != null && intArray.length > 0) {
intArray[0] = 10;
}
System.out.println(System.identityHashCode(intArray));
}
//558638686
//[10, 2, 3]
//558638686
//---------------------------------
//1149319664
//[changed1, origin2, origin3]
//1149319664
//---------------------------------
//2093631819
//Father{age=50, name=‘null‘}
//2093631819
注意,System.identityHashCode(Object obj)这个方法,如果两个对象的结果不一样,那么就可以肯定它们不是一个对象,但两个对象的计算结果一样,也不能完全证明他们是一样的,但是这个碰撞的概率还是很小,我们这里不是很准确,但是这个结论还是可靠的(你可以使用obj1==obj2来判断两个对象是否相同)。当类型是数组(包含基本类型的数组),对象被当作参数传入一个方法之后,那么这个传入的是它的地址或者地址相关的参数,此时如果在方法体中对其内部的变量做出任何修改,方法执行完后,外部的对象也会受影响。也就是说,我们在方法体内处理的就是这个对象本身。
对于向上转型和向下转型,其实我们也可以用实验代码来看看,内部的引用到底是怎么变化的。看如下代码:
class Son extends Father {
}
class GrandSon extends Son {
}
class Father {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
}
public static void main(String[] args) {
testCast();
}
private static void testCast() {
GrandSon grandSon = new TestObjectAddress().new GrandSon();
Son son = (Son) grandSon;
Father father = (Father) son;
System.out.println("--------向上转型--------");
System.out.println(System.identityHashCode(grandSon) + "----" + grandSon);
System.out.println(System.identityHashCode(son) + "----" + son);
System.out.println(System.identityHashCode(father) + "----" + father);
System.out.println("--------向上转型后再向下转型--------");
Son anotherSon = (Son) father;
System.out.println(System.identityHashCode(anotherSon));
System.out.println("--------向下转型--------");
Father daddy = new TestObjectAddress().new Father();
try {
Son daddySon = (Son) daddy;
} catch (Exception e) {
e.printStackTrace();
}
}
//--------向上转型--------
//558638686----com.algorithm.learn.test.unit3.address.TestObjectAddress$GrandSon@214c265e
//558638686----com.algorithm.learn.test.unit3.address.TestObjectAddress$GrandSon@214c265e
//558638686----com.algorithm.learn.test.unit3.address.TestObjectAddress$GrandSon@214c265e
//--------向上转型后再向下转型--------
//558638686
//--------向下转型--------
//java.lang.ClassCastException: com.algorithm.learn.test.unit3.address.TestObjectAddress$Father cannot be cast to com.algorithm.learn.test.unit3.address.TestObjectAddress$Son
// at com.algorithm.learn.test.unit3.address.TestObjectAddress.testCast(TestObjectAddress.java:40)
// at com.algorithm.learn.test.unit3.address.TestObjectAddress.main(TestObjectAddress.java:23)
可以看到,所谓的向上转型,其实引用过去还是这个对象本身,但这个引用只能按照被修饰的类型进行方法调用,即父类是不能调用子类的方法的,虽然这个引用其实就是这个对象,但表现出来还是得遵循被修饰类的特性。也就比较好理解向上转型之后,再进行向下转型是可以的,因为这就是这个对象本身。但如果一开始new的就是父类,这个时候是没办法进行向下转型的,因为类型已经发生了变化。
原文:https://www.cnblogs.com/bruceChan0018/p/15093461.html