《On Java 8》-赋值(对象传递和返回)(值传递和引用传递)

本文详细介绍了Java中的赋值操作,包括基本类型和对象类型的赋值差异。在Java中,对象赋值实际上是引用的传递,可能导致别名现象,即多个引用指向同一对象。通过示例代码展示了如何避免别名问题,并提到了对象的本地拷贝、控制克隆和不可变类等概念。附录中进一步探讨了对象的传递和返回,帮助读者理解Java中对象引用的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

On Java 8

《On Java 8》中的内容

赋值

对象传递和返回

理解:值传递和引用传递

第四章 运算符-赋值

赋值

​ 运算符的赋值是由符号 = 完成的。它代表着获取 = 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。也就是说,必须要有一个物理的空间来存放右边的值。举个例子来说,可将一个常数赋给一个变量(A = 4),但不可将任何东西赋给一个常数(比如不能 4 =A)。

(值传递和引用传递:)基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。举个例子,a = b ,如果 b 是基本类型,那么赋值操作会将 b 的值复制一份给变量 a,此后若 a 的值发生改变是不会影响到 b 的。作为一名程序员,这应该成为我们的常识。

如果是为对象赋值,那么结果就不一样了。对一个对象进行操作时,我们实际上操作的是它的引用。所以我们将右边的对象赋予给左边时,赋予的只是该对象的引用。此时,两者指向的堆中的对象还是同一个。代码示例:

// operators/Assignment.java
// Assignment with objects is a bit tricky*

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("1: t1.level: " + t1.level +", t2.level: " + t2.level);
    
    t1 = t2;
    System.out.println("2: t1.level: " + t1.level +", t2.level: " + t2.level);
   
    t1.level = 27;
    System.out.println("3: t1.level: " + t1.level +", t2.level: " + t2.level);
	} 
}

输出结果:

1: t1.level: 9, t2.level: 47

2: t1.level: 47, t2.level: 47

3: t1.level: 27, t2.level: 27

​ 这是一个简单的 Tank 类,在 main() 方法创建了两个实例对象。两个对象的 level属性分别被赋予不同的值。然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。这是因为 t1 和 t2 此时指向的是堆中同一个对象(引用传递)。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。

​ 这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。但是假若你不想出现这里的别名引起混淆的话,你可以这么做。代码示例:

t1.level = t2.level;

​ 较之前的做法,这样做保留了两个单独的对象,而不是丢弃一个并将 t1 和 t2 绑定到同一个对象。但是这样的操作有点违背 Java 的设计原则。对象的赋值是个需要重视的环节,否则你可能收获意外的 “惊喜”。

// 方法调用中的别名现象
//当我们把对象传递给方法时,会发生别名现象。

// operators/PassObject.java
// 正在传递的对象可能不是你之前使用的

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但是一旦传递了一个引用,那么实际上 y.c =‘z’; 是在方法 f() 之外改变对象。别名现象以及其解决方案是个复杂的问题,在附录中有包含:对象传递和返回。意识到这一点,我们可以警惕类似的陷阱。

附录: 对象传递和返回

​ 到现在为止,你已经对 “传递” 对象实际上是传递引用这一想法想法感到满意。

​ 在许多编程语言中,你可以使用该语言的 “常规” 方式来传递对象,并且大多数情况下一切正常。但是通常会出现这种情况,你必须做一些不平常的事情,突然事情变得更加复杂。Java 也不例外,当您传递对象并对其进行操作时,准确了解正在发生的事情很重要。本附录提供了这种见解。

​ 提出本附录问题的另一种方法是,如果你之前使用类似 C++ 的编程语言,则是 “Java 是否有指针?” Java 中的每个对象标识符(除原语外)都是这些指针之一,但它们的用法是不仅受编译器的约束,而且受运行时系统的约束。换一种说法,Java 有指针,但没有指针算法。这些就是我一直所说的 “引用”,您可以将它们视为 “安全指针”,与小学的安全剪刀不同-它们不敏锐,因此您不费吹灰之力就无法伤害自己,但是它们有时可能很乏味。

传递引用

当你将引用传递给方法时,它仍指向同一对象。一个简单的实验演示了这一点:

// references/PassReferences.java

public class PassReferences {
	public static void f(PassReferences h) {
		System.out.println("h inside f(): " + h);
	}

  public static void main(String[] args) {
    PassReferences p = new PassReferences();
    System.out.println("p inside main(): " + p);
    f(p);
  } 
}

/* Output:
p inside main(): PassReferences@15db9742
h inside f(): PassReferences@15db9742
*/

​ 方法 toString() 在打印语句中自动调用,并且 PassReferences 直接从 Object继承而无需重新定义 toString()。因此,使用的是 Object 的 toString()版本,它打印出对象的类,然后打印出该对象所在的地址(不是引用,而是实际的对象存储)。

本地拷贝

控制克隆

不可变类

本章小结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值