java值传递和不可变对象

由递归引发的java值传递和不可变对象问题

最近翻看二叉树,想着实现二叉树的各种相关方法。再写java递归时出现小插曲。

最开始的实现形式:

试图函数式接口递归

想通过利用函数式接口在方法内定义index的全局变量;

很明显,编译报错。

修改后仍有问题的递归:

在这里插入图片描述

一般递归的成立条件:

1、明确的递归出口,存在边界;

2、能使问题规模变小,也就是递归方法实际参数需要变化。

递归方法的index值不会如预期的进一层递归方法就+1变化;

因为java只有值传递没有引用传递,导致以上递归方法的index最多只能变化到2;

因为index++这一行 实际上相当于,

int temp = index.intValue();

tamp++;

index = Integer.valueOf(temp);

index是一个新的对象,于是index在以上方法最多加到2;

1、java的值传递

首先抛出结论:java没有引用传递,只有值传递;java方法只是复制了实际参数的值,基本类型直接复制基本类型值,引用类型复制引用地址;

以前错误的认为,java中参数是基本类型的是值传递,引用类型即为引用传递。

举例:

public void sawp(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

public void test() {
	int a = 10;
	int b = 20;
	System.out.println("交换前:a:" + a + "  b:" + b);
	sawp(a,b);
	System.out.println("交换后:a:" + a + "  b:" + b);
}
//交换前:a:10  b:20
//交换后:a:10  b:20

很明显不会变;

class User {
    int age;
    String sex;
    //省略构造和get/set方法
}

public void sawp(User a, User b) {
	User temp = a;
	a = b;
	b = temp;
}

public void test() {
	User bob = new User(18,"M");
	User lily = new User(20,"F");
	System.out.println("交换前:bob:" + bob.toString() + "  lily:" + lily.toString());
	sawp(bob,lily);
	System.out.println("交换前:bob:" + bob.toString() + "  lily:" + lily.toString());
}
//交换前:bob:age:18 sex:M  lily:age:20 sex:F
//交换后:bob:age:18 sex:M  lily:age:20 sex:F

结果也不会变;

因为swap方法的实参是复制了bob和lily对象的地址

假如bob的地址是**@7dc5e7b4** lily的地址是**@3d012ddd**

那么swap方法只是把地址复制传给了形式参数 a = @7dc5e7b4

b = @3d012ddd;

swap 方法中 a 和 b 变量互相交换地址,方法结束之后并不能改变bob和lily对象。

执行swap方法:

在这里插入图片描述

swap方法结束后,临时副本user1和user2被回收:

在这里插入图片描述

那么很显然,java只有值传递,但我们可以利用方法复制实参地址去改变引用对象内部成员属性;

例如:

public void update(User a) {
	a.setAge(20);
	a.setSex("F");
}
public void test() {
	User bob = new User(18,"M");
	System.out.println("修改前:bob:" + bob.toString());
    update(bob);
    System.out.println("修改后:bob:" + bob.toString());
}
//修改前:bob:age:18 sex:M
//修改后:bob:age:20 sex:F

成功修改了bob对象;

但是:

public void update(String a) {
	a = "lily";
}
public void test() {
	String bob = "bob";
	System.out.println("修改前:bob:" + bob);
    update(bob);
    System.out.println("修改后:bob:" + bob);
}
//修改前:bob:bob
//修改后:bob:bob

没有修改bob对象;

这是因为a = “lily”; 实际上是 a = new String(“lily”)

像String,Integer等都不能通过这种方式来改变实参的值;

因为String,Integer类的对象的值不能被改变;

在这里插入图片描述

在这里插入图片描述

String,Integer属于不可变类;

不可变类是指所有的成员变量都由final修饰;

2、不可变类

什么是不可变类

一旦一个类的对象被创建出来,在其整个生命周期中,它的成员变量就不可以被修改,java库中包含的不可变类有:String、BigInteger、BigDecimal,Integer,Long。

不可变类的优缺点(为什么要不可变)

1、效率,例如字符串常量池,将一些字符常量放在常量池中重复使用,为了避免相同的字符串重新创建相同的对象,节省存储空间。

2、线程安全,不可变对象天生是线程安全的,在不同线程共享对象,不需要同步机制,因为对象的值是固定的。

缺点:

资源开销,对象需要频繁的修改属性,则每一次修改都会新创建一个对象,产生大量的资源开销。

如何设计不可变类

1、类使用final修饰符修饰,保证类不能被继承。

因为如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。

2、类的成员变量都应该是private final的,保证成员变量不可改变。

3、不提供修改成员变量的方法,例如setter方法。

4、getter方法不能返回对象本身,要返回对象的拷贝,防止对象外泄。

例如String 的char[]属性,返回的是char[]的拷贝;

5、如果提供修改方法,修改对象的属性时要返回新对象。

6、对成员变量的初始化通过构造器进行,并进行深拷贝。

反射改变不可变类的属性

但是不可变并不是真的不可变,通过反射仍然可以改变其属性的值。

3、解决问题

1、此时立即想到利用全局变量,定义index全局变量,再去执行;实现很简单但不优雅;

2、定义一个可变对象,对象中存放index属性,达到修改的目的;

3、

都不优雅,在不改成非递归的前提能不能规避这个问题,

一种解决方案:

// 先序 递归 带#字符串 创建
	public BiTreeNode PreAInitBitTree(String biTreeStringA) {
		BiTreeNode root = new BiTreeNode();
		
		PreAInitBitTreeChildMethod(root, biTreeStringA.toCharArray(), new int[1]);
		return root;
	}

	public void PreAInitBitTreeChildMethod(BiTreeNode node, char[] biTreeChar, int[] indexArr) {
		int index = indexArr[0];
		char data = biTreeChar[index];
		indexArr[0] = ++index;
		if (data == 35 || data == '#') {
			node = null;
			return;
		} else {
			BiTreeNode lNode = new BiTreeNode();
			BiTreeNode rNode = new BiTreeNode();
			node.data = data;
			node.lChild = lNode;
			node.rChild = rNode;
		}
		PreAInitBitTreeChildMethod(node.lChild, biTreeChar, indexArr);
		PreAInitBitTreeChildMethod(node.rChild, biTreeChar, indexArr);
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值