前言
最近在学习红黑树,当我尝试不使用递归来实现时,发现自己的大脑陷入了混乱。
究其原因,是对JAVA中的基本类型和引用类型有所误解。
特地重新搜索+实践一番,涨个姿势。
先放出我参考的文章:
@Intopass的知乎回答 java到底是值传递还是引用传递
@focusChen的博客 JAVA 对象引用,以及对象赋值
一番折腾
先找个对象过年
class Node {
int value;
Node left;
Node right;
Node(int value) {
this.value = value;
}
}
对象赋值,引用还是复制值?
我出现的问题就在于弄混了下面的代码,没有理解清楚传值和传址。
Node root;
//初始化
root = new Node(8);
root.left = new Node(6);
root.right = new Node(10);
//我弄混的地方
root = root.left;
root.left = new Node(6);
经过初始化后,如图所示
然后我理解错误的地方出现了,就是这句
root = root.left;
我的理解出现了偏差,执行后,请看下图
为啥呢?
发现,new出来的对象被存的都是地址,而地址指向实际的对象。
也就是说,对象是引用类型。
再看对已有的对象用“=”赋值
现在,可以确认了,JAVA中对象赋值是传递地址的!
真·引用类型。
之前,我一直以为是传值。
如果将对象的地址想象成房号,变量root原本在463号。
root = root.left;
按我之前的理解,这行代码是告诉root把463号的物品都扔了。
再按照465号房间里的物品,买一份一模一样的放在463号。
实际呢
是告诉root,搬到465号去。
463号不再是它的房间了。
区别就在于
如果原本还住463号,只是复制的物品,那么它跟464号还是邻居,还可以互相串门。
但若搬到465号就不再与464号相邻,没办法串门到464号去,只能剩下465号一个房间。
小结
对对象使用“=”赋值,赋的是地址而不是值。
这么基础的问题,赤果果暴露了我的基础水平- -
还是某领导说得好啊,“实践是检验真理的唯一标准”。
仅仅听说是不够的,还需要亲身实践。
如果是方法之间的参数呢?
上面我们说到对象之间赋值,都是地址。
那么如果把对象当作参数传进方法里,也是传对象的地址吗?
答案是,是的。
不过此处有个坑,请看以下代码
public class TestObject {
public static void main(String[] args) {
TestObject o = new TestObject();
int baseInt = 1;
Integer objInt = 1;
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
o.changeInt(baseInt);
o.changeInteger(objInt);
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
}
public void changeInt(int baseInt) {
baseInt = 3;
}
public void changeInteger(Integer objInt) {
objInt = 3;
}
}
结果:
baseInt: 1
objInt: 1
baseInt: 1
objInt: 1
不科学啊。如果传的是地址进去,为什么objInt没有改变呢!
跟说好的不一样是不是?
来看看到底发生了什么
可以看到objInt确实是引用类型的对象,其地址为@464。
再看看进入到方法时,地址是什么。
还是@464,说明的确按照地址传去的。
但是执行完赋值语句后
地址变成了@468。。
我的猜测
objInt = 3;
的操作是
objInt = new Integer(3);
由于new关键字导致objInt改变了引用,从@464变成了@468。
所以改的不是@464里的值。
执行完该方法后,回到主方法里,地址变回了@464,也就看到了结果没有发生改变。
那么只要不创建新对象,改变是会生效的。
请看如下代码:
public class TestObject {
public static void main(String[] args) {
TestObject o = new TestObject();
int baseInt = 1;
Integer objInt = 1;
MyInt myInt = new MyInt(1);
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
System.out.println("myInt: " + myInt.v);
o.changeInt(baseInt);
o.changeInteger(objInt);
o.changeMyInt(myInt);
System.out.println("改变后....");
System.out.println("baseInt: " + baseInt);
System.out.println("objInt: " + objInt);
System.out.println("myInt: " + myInt.v);
}
public void changeInt(int baseInt) {
baseInt = 3;
}
public void changeInteger(Integer objInt) {
objInt = 3;
}
public void changeMyInt(MyInt myInt) {
myInt.setV(3);
}
}
class MyInt {
int v;
MyInt(int v) {
this.v = v;
}
public void setV(int v) {
this.v = v;
}
}
结果:
baseInt: 1
objInt: 1
myInt: 1
改变后....
baseInt: 1
objInt: 1
myInt: 3
生效了。
说明传进方法里的参数,只要不用new创建新对象,引用的就是传进来的对象。
总结
对象之间使用=号进行赋值时,赋的是地址的值,拥有该地址的都是指向该地址对应的对象。
即多对一的关系。
如果用了new关键字,一定会有新对象产生,有新对象也肯定有新地址。
至于Integer这些包装类,我猜它们的=号就是new新对象,同时其没有提供对外的方法更改对象里的值,所以可以看作基本类型。
我算是明白了一丢丢,继续写红黑去- -