`

深入探讨java中引用的行为

阅读更多
在java和C++中都采用了引用这个概念,但是二者的行为并不一样。java的引用其实更象c++中的指针,而非c++中的引用,的确c++的指针给我们带来强大威力的同时,也给我们带来了危险,大多的攻击也都是利用指针让系统崩溃。在许多书上都没有见到关于java中reference行为的更详细的探讨,本文便从与c++的的区别谈起:
一.先看java中的引用:
class Person
{
 private String name;
 public Person(String name)
 {
  this.name=name;
 }
 public String getName()
 {
  return name;
 }
 public void setName(String name)
 {
  this.name = name;
 }
}

现产生两个对象 :
Person p1=new Person("Zhangsan");
Person p2=new Person("Lisi");

引用Person p=p1,p1=p2;
测试结果p.getName(): Zhangsan;
p1.getName():Lisi;
P2.getName :Lisi;
而在c++的引用中其结果应该都是Lisi即p2的name(因为p1的改变会影响p);
上述结果显示:在java中referece其行为就是c++中的指针,而不是c++中的引用.
但是java的referece是一种安全的指针,不仅受到编译器的保护,还受到执行系统的保护。但java没有“指针”运算。c++中的引用感觉叫做别名更合适,许多语言都用alias关键字来定义别名。
二、java中的参数传递问题--By value or By reference
java中的参数传递都是采用by value.其实这句话是个笼统的说法,如果不加思索或许不能理解其真正的含义。
首先说什么是by value,by value是指在参数结合的时候,形参是否是实参的一个副本,如果产生这个副本那么我们说是by value,否则我们说是by reference.
java中的基本类型和c、c++语言一样采用by value传参是毋庸置疑的。
那么java有在参数传递的时候有没有by reference?回答是否定的。java中的一切类型在传递参数的时候都是传值的。
   首先,我们想想c++和java中的对象有什么区别?其实没有什么区别,只是java中的对象必须由reference来持有,而c++的对象可以单独存在。那好,那就让我们看看java是怎么传递对象的:
public static void swap(Person person1, Person person2) // doesn't work
{
   Person temp = person1;
   person1 = person2;
   person2 = temp;
}
Person a = new Person("Alice");
Person b = new Person("Bob");
swap(a, b);

能完成交换工作么?不能。为什么?
我们看看:
当我们传递参数的时候,我们传递的是对象么?不,是对象的一个句柄(引用、“指针”)。java的对象只能
由引用来持有。如果你对c++熟悉,那么上面的过程我们翻译过来:
static void swap(Person *person1,Person *person2)
{
  Person *temp = person1;
  person1 = person2;
  person2 = temp;
}
Person *a = new Person("Alice");
Person *b = new Person("Bob");
swap(a,b);

这两段代码产生的行为是一样的,都不能正常工作(即无法完成我们的交换工作)。
那我们先想想c++是怎么实现对象的交换的?你或许一口就说出使用引用和指针做参数。但是上面的参数是指针
作为参数呀!原因在于:在c++中我们传递的参数的确能是对象:
Person a("Alice");
Person b("Bob");
swap(a,b);

这样可以成功完成交换动作.
前两段的代码不能工作的原因在于:
  但我们传递实参的时候,我们传递的是指针("指针"),传递的过程,指针("指针")进行了复制,如下图示:
a--->对象1<----a'(其中a',b'分别为a,b在传递参数的时候产生的副本)
b--->对象2<----b'
进入函数体后副本发生了交换,如下图示:
a-->对象1<---b'
b-->对象2<---a'
我们可以看出a和b安然无恙的指向了原来的对象.这就是产生上述结果的原因.
但java可以在函数体内修改对象的值:
static void changName(Person person){
 person.setName("Bob");
}
changName(a);

对java传递参数总结:
1)一个方法不能够修改一个基本类型的实参的值
2)一个方法可以改变对象的状态.
3)一个方法不能让实参引用指向另一个对象.

分享到:
评论
7 楼 kevinStar 2010-09-19  
明白你的意思了,与你交流很愉快。
6 楼 fuliang 2010-09-18  
引用

public static void swap(Person person1, Person person2) {  
        Person temp = person1;  
        person1 = person2;  
        person2 = temp;  
        System.out.println("输出 : " + person1.getName() + " -- " + person2.getName());  
   }  

在里面打印,结果肯定是交换了。但是swap的意思是指实参发生交换,而不是形参。
调用:
Person a("Alice");   
Person b("Bob");   
swap(a,b);  

调用swap之后,a的name还是Alice,b的name还是Bob,所以没有交换。
5 楼 kevinStar 2010-09-18  
你说的第1点,我理解你是在说Java基本类型是传值的(即创建了副本,所以当发生改变的时候不影响外部的基本类型值。
第2点,我理解你是在说java对象是传址的(所以像你说的能改变对象的状态)。
那么你的第三点解释的
fuliang 写道
第三点是说由于是值传递,所以实参在参数结合的时候,生成了副本,改变副本的指向,不影响原来的实参。
不是和第1点重复了么?

另:你公布的不是全部代码,所以我是猜测着写的。所以完成了交换能正常运行。
public class Person {

	private String name;
	
	public Person(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public static void swap(Person ...persons) {
		Person temp = persons[0];
		persons[0] = persons[1];
		persons[1] = temp;
		System.out.println("输出 : " + persons[0].getName() + " -- " + persons[1].getName());
	}
	
	public static void swap(Person person1, Person person2) {
		Person temp = person1;
		person1 = person2;
		person2 = temp;
		System.out.println("输出 : " + person1.getName() + " -- " + person2.getName());
	}
	
	public static void changed(int i) {
		i = 12;
		System.out.println(i);
	}
	
	public static void changed(Person person) {
		person.setName("wangwu");
		System.out.println(person.getName());
	}
	
	public static void main(String[] args) {
		Person p1 = new Person("zhangsan"); 
		Person p2 = new Employee("lisi");
//		int i = 0;
//		changed(i);
//		System.out.println(i);
//		changed(p1);
//		System.out.println(p1.getName());
//		Person p = p1;
//		p1 = p2;
//		System.out.println(p.getName());
//		swap(new Person[]{p1, p2});
		swap(p1, p2);
	}

}

class Employee extends Person {

	public Employee(String name) {
		super(name);
	}

	@Override
	public String getName() {
		return super.getName();
	}

	@Override
	public void setName(String name) {
		super.setName(name);
	}
	
}

这个是我写的测试代码,能运行通过。不知道你是怎么写的?
BTW:哥们交流下,交个朋友。我好久不来论坛了,有表达不对的地方,请见谅。
4 楼 fuliang 2010-09-18  
第三点是说由于是值传递,所以实参在参数结合的时候,生成了副本,改变副本的指向,不影响原来的实参。
3 楼 kevinStar 2010-09-18  
class Employee extends Person {

	public Employee(String name) {
		super(name);
	}

	@Override
	public String getName() {
		return super.getName();
	}

	@Override
	public void setName(String name) {
		super.setName(name);
	}
	
}

这种情况依然还是能完成交换。BTW:你的第3点的意思是不是想说明,方法中不能交换对象的引用?
2 楼 fuliang 2010-09-18  
kevinStar 写道
public static void swap(Person person1, Person person2) // doesn't work
{
   Person temp = person1;
   person1 = person2;
   person2 = temp;
}
Person a = new Person("Alice");
Person b = [color=red]new Employee("Bob");//应该是Person吧?[/color]
swap(a, b);

如果是Person类的话,可以运行是可以完成交换的。

所以你总结的第3点是不正确的。

你可以试验一下
1 楼 kevinStar 2010-09-18  
public static void swap(Person person1, Person person2) // doesn't work
{
   Person temp = person1;
   person1 = person2;
   person2 = temp;
}
Person a = new Person("Alice");
Person b = [color=red]new Employee("Bob");//应该是Person吧?[/color]
swap(a, b);

如果是Person类的话,可以运行是可以完成交换的。

所以你总结的第3点是不正确的。

相关推荐

Global site tag (gtag.js) - Google Analytics