首先,放一句话。引用类型都是传递引用。但是对于Integer这种包装类型来说,可能会让人产生误区,比如看下面代码片段:
Integer i = new Integer(1);
Integer j = i;
System.out.println(j);
i = 2;
System.out.println(j);
System.out.println(i);
j的输出结果都是1,i的输出结果最后是2。
这是因为i这个引用指向的对象改变了,i=2这条语句你可以看成i=new Integer(2),而不是修改i最开始所指向的对象的值,这个值也不能改变。因为在Integer内部也是封装了一个final修饰的int类型的值,这里和String类型大同小异。也就是说包装类和String类型一样的,不可以改变这个包装类的实例的值,我们对包装类的赋值操作实际上是创建了一个新的对象,然后把这个对象交给这个引用去管理,因为包装类的自动拆箱和装箱,所以看起来这个操作和基本类型的赋值差不多,但是这里确确实实是创建了一个新的对象。下面语句是包装类中找到的一行代码:
private final int value;
那么,如何证明包装类确实是采用的地址传递呢?我们可以采用synchronize这个关键字来证明,具体思路如下:两个线程对同一个数字进行自增操作,如果自增到一定大小时停止自增,并且输出每次自增后的值。如果不使用synchronize这个关键字,那么输出的值可能会出现一些预料不到的情况,比如下面代码:
class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new A());
Thread t2 = new Thread(new A());
t1.start();
t2.start();
}
}
class A implements Runnable {
static Integer a = 100;
static int val = 0;
@Override
public void run() {
while (val < 100) {
val++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(val);
}
}
}
部分运行结果:
如果使用synchronize关键字,确保传入的monitor是同一个对象,那么线程运行的时候将会一切正常。要想确保传入的是同一个对象,那么肯定只能使用地址传递的对象才可以。因此,我们把Integer的对象作为这个monitor,具体代码如下:
class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new A());
Thread t2 = new Thread(new A());
t1.start();
t2.start();
}
}
class A implements Runnable {
static Integer a = 100;
static int val = 0;
@Override
public void run() {
synchronized (a) {
while (val < 100) {
val++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(val);
}
}
}
}
最后运行结果和预想的一样,从1到一百挨着输出,没有重复,也没有漏掉某些值的输出。所以证明了传入的是同一个对象,既然是同一个对象,那么肯定就是地址传递了。