[疯狂Java]基础类库:Object、深拷贝、Objects工具类

1. Object类简介:

    1) 是Java所有类型的基类,如果一个自定义的类没有extends显示指定其父类则它默认继承Object类;

    2) 常用方法(通常需要根据需求覆盖,Object本身对它们的定义极其简单):

         i. 相等判断:

public boolean equals(Object obj) {
    return (this == obj);
}
!!可以看到就是简单的判断是否地址相同;

         ii. 哈希值:public native int hashCode();  // 默认用地址值计算,和地址一一对应

         iii. 返回运行时类:public final native Class<?> getClass();

         iv. 返回字符串表示:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
!!可以看到原始的字符串信息就是"类名@哈希值的16进制表示";

    3) 上面这些方法除了getClass不能覆盖之外其余都可以覆盖!!

    4) 其它几个常用方法(之前用过):

         i. finalize:回收之前调用;

         ii. wait、notify、notifyAll:用于线程通信;


2. 自我克隆简介:clone

    1) 是Object中提供的一种最基础的工具,可以实现对象的自我克隆,和C++的拷贝构造函数是一个道理;

    2) 原型:protected native Object Object.clone() throws CloneNotSupportedException;

    3) 该方法的目的是克隆对象的整个内存空间(克隆出的对象和原对象在内存中是相互隔离的),和C++一样同样存在浅拷贝和深拷贝的问题;

    4) 如果一个类想要实现克隆的功能就必须实现该方法,因为它在Object中是protected,即对外界是隐藏的,只能在子类中使用;

    5) 如何实现克隆呢?需要以下的步骤完成:

         i. 实现类要implements一个Cloneable接口,该接口是一个标记接口,里面没东西;

!!标记接口是给人看的,而不是给编译器看的,仅仅就是用来提醒类库的使用者,该类已经实现了自我克隆的功能,可以放心使用了!仅此而已!只是增强可读性而已;

         ii. 自己重写clone方法,一般clone是要对外开放的,因此重新的时候要把protected改成public!

         iii. 在clone的实现中不用像C++那样非常麻烦地将每个成员都复制给克隆对象一个,只需要调用Object基类的clone方法即可,即:

@Override
public Object clone() throws CloneNotSupportedException {
	// TODO Auto-generated method stub
	return super.clone();
}
!就可以实现克隆了!

    6) 为什么??为什么Object的clone方法那么神奇?都不需要自己手写派生部分的克隆吗?这里我们要讲解以下Object的clone的原理,其实它是这样实现的:

protected native Object clone() throws CloneNotSupportedException {
	getClass一下得到当前的运行时类!  // 即使你是子类也是可以辨别出来的!
	由于在编程语言中类型决定了对象内存空间的大小,因此就决定了要拷贝的空间的大小len了;
	然后在获得this的地址;
	然后将this开始的len大小的内存空间赋值到一个新的len大小的内存空间中;
	最后返回新内存空间的指针(引用)即可;
}
!!也就是说它根据类型确定空间的大小,然后就赋值这么大的内存空间即可(即对象的纯数据区),因此还是非常智能的;

!!由于它是根据内存空间总大小一次性复制,因此其效率特别高(一次性复制类似于C语言的memcpy);

    7) 但是Object的这种clone就有一个非常大的隐患,那就是浅复制:如果对象中还包含其它对象,那么拷贝的时候仅仅拷贝的就是一个指针值,而这两个对象的该指针指向的还是同一段内存,其指向的区域并没有隔离,因此需要自己解决深复制的问题;


3. 深拷贝:

    1) 深拷贝必须由开发者自行实现,而实现的方法就是“递归”克隆;

    2) 递归克隆的要领:总的来说有两点

         i. 对象成员必须也实现了Cloneable接口(这里的实现必须是实现clone方法);

         ii. 在外层对象的clone方法中显示调用该对象成员的clone方法;

    3) 模板示例:

class MemObj implements Cloneable {
	// 数据定义...

	@Override
	public Object clone() throws CloneNotSupportedException {
		// 具体实现,保证该类本身就是深拷贝
	}
	
}

class Obj implements Cloneable {
	// 基本类型数据定义
	MemObj memObj; // 引用类型数据定义

	@Override
	public Object clone() throws CloneNotSupportedException {
		Obj obj = null;
		
		try {
			obj = (Obj)super.clone(); // 先暂时获取一个浅拷贝的对象
		}
		catch (CloneNotSupportedException e) { // 拷贝过程中可能出现异常
			e.printStackTrace();
		}
		
		// 如果没有引用类型成员该句就不用,其它的所有代码都是模板代码
		obj.memObj = (MemObj)memObj.clone(); // 再利用成员对象已经实现好的深拷贝clone单独拷贝该成员
		
		return obj; // 最终返回
	}
	
	
}
!!可以看到上面的定义是一种递归定义,一个对象要能深拷贝就必须保证其成员也能深拷贝;

    4) Java类库中有些类(引用类型的)可以像int等基本类型一样可以直接用Object的clone进行深拷贝!!

         i. String就是一个典型的例子,如果包含它的类想要实现深拷贝就直接super.clone就行了,无需递归显式调用String成员对象的clone!

         ii. 这种类在底层其实已被Java转化成了基本数据类型;

         iii. 那应该怎样辨别Java类库中哪些类可以自动深拷贝(像String一样)哪些由跟普通用户自定义类一样需要递归深拷贝呢?

         iv. 方法很简单:只要该类的clone方法是可见的,那就必须显式递归深拷贝,如果clone方法是不可见的那就可以自动深拷贝(super.clone就行了);

         v. 示例:

class A implements Cloneable {
	public String name; // clone可见,因此可以自动深拷贝
	public int[] arr = {1, 2, 3, 4, 5}; // 数组类型的clone不可见,必须手动递归深拷贝
	
	public A (String name) {
		this.name = name;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		A a = (A)super.clone();
		a.arr = arr.clone(); // arr必须手动调用clone深拷贝!!
		return a;
	}
	
}

class B implements Cloneable { // 再嵌套一层
	public A a;
	public B(A a) {
		this.a = a;
	}
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		B b = (B)super.clone();
		b.a = (A)a.clone();
		return b;
	}
	
}

public class Test {
	
	public static void main(String[] args) throws FileNotFoundException, IOException, CloneNotSupportedException {
		B b1 = new B(new A("lala"));
		B b2 = (B)b1.clone();
		b1.a.name = "xxx";
		b1.a.arr[0] = 999;
		System.out.println(b2.a.name); // lala
		System.out.println(Arrays.toString(b2.a.arr)); // 1, 2, 3, 4, 5 第一个并不是999,说明内存隔离
		System.out.println(b1.a == b2.a); // false,引用成员a的内存也是隔离的
	}
}


4. Objects工具类:

    1) 里面包含了和Object一样的方法,只不过都是静态的工具方法,并且参数是一个Object对象;

    2) 其目的就是防止空指针异常,设想你要调用一个对象的toString方法,如果该对象引用还是一个null那岂不是会抛出空指针异常吗?

    3) Objects就是为了解决当对象调用Object基础方法时发生的空指针异常问题!!

    4) Object类中对应的几个:如果参数就是null的,那么将返回null

         i. static String toString(Object o);  // 内部调用o的toString方法,只不过空指针安全

         ii. static int hashCode(Object o);  // 内部调用o的hashCode方法,只不过空指针时返回0

    5) 其它几个常用的工具方法:

         i. 非空赋值:static <T> T requireNonNull(T obj);  // 要求obj不为空,否则抛出异常,如果不为空就返回obj本身

!!!类似于C++的assert先判断一个东西不为空,只有不为空的时候才能拿它赋值:

Obj obj = Objects.requireNonNull(otherObj);

// 相当于
assert(otherObj);
obj = otherObj;
         ii. 标准非空判断:static boolean isNull(Object obj);  // 比obj == null看上去更加正规!

!!其实其实现就是:

public static boolean isNull(Object obj) {
    return obj == null;
}





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值