Java赋值操作符注意事项及方法调用中的别名问题

·  阅读 165
Java赋值操作符注意事项及方法调用中的别名问题

让我们先来回顾一下Java中的一些特性 ~~

一切都是对象

“如果我们说另一种不同的语言,那么我们就会发觉一个有些不同的世界。”

——Luduing Wittgerstein(1889-1951)

尽管Java是基于C++的,但是相比之下,Java是一种更“纯粹”的面向对象程序设计语言。

Java语言假设我们只是进行面向对象的程序设计。也就是说,在开始用Java进行设计之前,必须将思想转化到面向对象的世界中来。这个入门基本功,可以使你具备使用这样一门编程语言编程的能力,这种语言学习起来更简单,也比许多其他OOP语言更易用。

用引用操作对象

每种编程语言都有自己的操作内存中元素的方式。有时候,程序员必须注意将要处理的数据是什么类型。你是直接操作元素, 还是用某种给予特殊语法的间接表示(例如C和C++里的指针)来操作对象?

所有这一切在Java里都得到了简化。一切都被视为对象,因此可采用单一固定的语法。尽管一切都看做对象,但操作的标识符实际上是对象的一个“引用”(reference)。

特例(基本类型)

在程序设计中经常用到一系列类型,他们需要特殊对待。可以把它们想象成“基本”类型。之所以特殊对待,是因为new将对象存储在“堆”里,故用new创建一个对象——特别是小的、简单的变量,往往不是很有效。因此,对于这些类型,Java采用与C和c++相同的方法。也就是说,不用new来创建变量,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更加高效。

举例(有关赋值操作)

在开始问题之前我们先来看这样一段代码

class Tank {
	int level;
}

public class Assignment {
	public static void main(String[] args) {
		Tank t1 = new Tank();
		Tank t2 = new Tank();
		t1.level = 9;
		t2.level = 47;
		System.out.println("No.1: t1.level:" + t1.level + ", t2.level:"
				+ t2.level);
		t1 = t2;
		System.out.println("No.2: t1.level:" + t1.level + ", t2.level:"
				+ t2.level);
		t1.level = 27;
		System.out.println("No.3: t1.level:" + t1.level + ", t2.level:"
				+ t2.level);
	}
}

复制代码

运行结果

No.1: t1.level:9, t2.level:47
No.2: t1.level:47, t2.level:47
No.3: t1.level:27, t2.level:27
复制代码

为什么是这样一个结果呢?

其实 Tank类非常简单,它的两个实例(t1和t2)是在main()里创建的。对每个Tank类对象的level域都赋予了一个不同的值,然后,将t2赋给t1, 接着又修改了t1。在许多编程语言中,我们可能会期望t1和t2总是相互独立的。但由于赋值操作的是一个对象的引用,所以修改t1的同时也改变了t2!这是由于t1和t2包含的是相同的引用,它们指向相同的对象。(原本t1包含的对对象的引用,是指向一个值为9的对象。在对t1赋值的时候,这个引用被覆盖了,也就是丢失了;而那个不再被引用的对象会由“垃圾回收器”自动清理。)

这种特殊的现象通常称作“别名现象”,是Java操作对象的一种基本方式。在这个例子中,如果想避免别名问题应该怎么办呢?可以这样写:

t1.level = t2.level;
复制代码

这样便可以保持两个对象彼此独立,而不是将t1和t2绑定到相同的对象。但你很快就会意识到,直接操作对象内的域容易导致混乱,并且,违背了良好的面向对象程序设计的原则。这可不是一个小问题,所以从现在开始大家就应该留意,为对象赋值可能会产生意想不到的结果。

方法调用中的别名问题

将一个对象传递给方法时,也会产生别名问题:

class Letter{
	char c;
}

public class PassObject {
	static void f (Letter y){
		y.c = 'z';
	}
	public static void main(String[] args){
		Letter x = new Letter();
		x.c = 'a';
		System.out.println("1: x.c: " + x.c);
		f(x);
		System.out.println("2: x.c: " + x.c);
	}
}
复制代码

输出结果

1: x.c: a
2: x.c: z
复制代码

在很多编程语言中,方法f()似乎要在他的作用域内复制其参数Letter的一个副本;但实际上只是传递了一个引用。所以代码行

y.c = 'z';
复制代码

实际改变的是f()之外的对象。

别名引起的问题及其解决方法是很复杂的话题,但是你现在就应该知道它的存在,并在使用中注意这个陷阱。

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改