1. clone()
克隆的目的:快速创建一个已有对象的副本。
克隆的步骤:
创建一个对象
将原有对象的数据导入到新创建的数据中
- Object的clone()源代码简介
/**
* Creates and returns a copy of this {@code Object}. The default
* implementation returns a so-called "shallow" copy: It creates a new
* instance of the same class and then copies the field values (including
* object references) from this instance to the new instance. A "deep" copy,
* in contrast, would also recursively clone nested objects. A subclass that
* needs to implement this kind of cloning should call {@code super.clone()}
* to create the new instance and then create deep copies of the nested,
* mutable objects.
*
* @return a copy of this object.
* @throws CloneNotSupportedException
* if this object's class does not implement the {@code
* Cloneable} interface.
*/
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class doesn't implement Cloneable");
}
return internalClone((Cloneable) this);
}
/*
* Native helper method for cloning.
*/
private native Object internalClone(Cloneable o);
clone方法首先会判对象是否实现了Cloneable接口,若无则抛出CloneNotSupportedException, 最后会调用internalClone. intervalClone是一个native方法,一般来说native方法的执行效率高于非native方法。
当某个类要复写clone方法时,要继承Cloneable接口。通常的克隆对象都是通过super.clone()方法来克隆对象。
浅克隆
克隆就是复制一个对象的复本.若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象的reference。
例子:
public class ShadowClone implements Cloneable{
private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
ShadowClone sc = null;
try
{
sc = (ShadowClone) super.clone();
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}
public class Test{
public static void main(String[] args) throws CloneNotSupportedException{
ShadowClone c1 = new ShadowClone();
//对c1赋值
c1.setA(100) ;
c1.setB(new int[]{1000}) ;
System.out.println("克隆前c1: a="+c1.getA()+" b="+c1.getB()[0]);
//克隆出对象c2,并对c2的属性A,B,C进行修改
ShadowClone c2 = (ShadowClone) c1.clone();
//对c2进行修改
c2.setA(50) ;
int []a = c2.getB() ;
a[0]=5 ;
c2.setB(a);
System.out.println("克隆前c1: a="+c1.getA()+" b="+c1.getB()[0]);
System.out.println("克隆后c2: a="+c2.getA()+ " b[0]="+c2.getB()[0]);
}
}
结果为:
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=5
克隆后c2: a=50 b[0]=5
可以看出,基本类型可以使用浅克隆,而对于引用类型,由于引用的是内容相同,所以改变c2实例对象中的属性就会影响到c1。所以引用类型需要使用深克隆。另外,在开发一个不可变类的时候,如果这个不可变类中成员有引用类型,则就需要通过深克隆来达到不可变的目的。
3.深克隆(deep clone)
深克隆与浅克隆的区别在于对复合数据类型的复制。若对象中的某个字段为复合类型,在克隆对象的时候,需要为该字段重新创建一个对象。
public class DeepClone implements Cloneable {
private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
DeepClone sc = null;
try
{
sc = (DeepClone) super.clone();
int[] t = sc.getB();
int[] b1 = new int[t.length];
for (int i = 0; i < b1.length; i++) {
b1[i] = t[i];
}
sc.setB(b1);
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}
结果为:
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=1000
克隆后c2: a=50 b[0]=5
总结:
- 克隆方法用于创建对象的拷贝,为了使用clone方法,类必须实现java.lang.Cloneable接口重写protected方法clone,如果没有实现Clonebale接口会抛出CloneNotSupportedException.
- 在克隆java对象的时候不会调用构造器
- java提供一种叫浅拷贝(shallow copy)的默认方式实现clone,创建好对象的副本后然后通过赋值拷贝内容,意味着如果你的类包含引用类型,那么原始对象和克隆都将指向相同的引用内容,这是很危险的,因为发生在可变的字段上任何改变将反应到他们所引用的共同内容上。为了避免这种情况,需要对引用的内容进行深度克隆。
2.equals()
equals 方法既然是基类 Object 的方法,我们创建的所有的对象都拥有这个方法,并有权利去重写这个方法。该方法返回一个 boolean 值,代表比较的两个对象是否相同。
public boolean equals(Object obj) {
return (this == obj);
}
String 类重写了 equals 方法否则两个 String 对象内存地址肯定不同。我们简单看下 String 类的 equals 方法:
public boolean equals(Object anObject) {
//首先判断两个对象的内存地址是否相同
if (this == anObject) {
return true;
}
// 判断连个对象是否属于同一类型。
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//长度相同的情况下逐一比较 char 数组中的每个元素是否相同
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
从源码我们也可以看出, equals 方法已经不单单是调用 this==obj来判断对象是否相同了。事实上所有 Java 定义好的一些现有的引用数据类型都重写了该方法。当我们自己定义引用数据类型的时候我们应该依照什么原则去判定两个对象是否相同,这就需要我们自己来根据业务需求来把握。但是我们都需要遵循以下规则:
自反性(reflexive)。对于任意不为 null 的引用值 x,x.equals(x) 一定是 true。
对称性(symmetric)。对于任意不为 null 的引用值 x 和 y ,当且仅当x.equals(y)是 true 时,y.equals(x)也是true。
传递性(transitive)。对于任意不为 null 的引用值x、y和z,如果 x.equals(y) 是 true,同时 y.equals(z) 是 true,那么x.equals(z)一定是 true。
一致性(consistent)。对于任意不为null的引用值x和y,如果用于equals比较的对象信息没有被修改的话,多次调用时 x.equals(y) 要么一致地返回 true 要么一致地返回 false。
对于任意不为 null 的引用值 x,x.equals(null) 返回 false。
例子:
String str1 = "abc";
String str2 = "abc";
str1.equals(str2);//true
3.hashCode()
hashCode():返回该对象的哈希码值。
注意:哈希值是根据哈希算法计算出来的一个值,这个值和地址值有关,但是不是实际地址值。你可以理解为地址值。(对象名不同,哈希值也不同)
hash 法简介
hash 算法,又被成为散列算法,基本上,哈希算法就是将对象本身的键值,通过特定的数学函数运算或者使用其他方法,转化成相应的数据存储地址的。而哈希法所使用的数学函数就被称为 『哈希函数』又可以称之为散列函数。
如果我们能在数组存放的时候就按一定的规则放入元素,在我们想找某个元素的时候在根据之前定好的规则,就可以很快的得到我们想要的结果了。换句话说之前我们在数组中存放元素的顺序可能是依照添加顺序进行的,但是如果我们是按照一种既定的数学函数运算得到要放入元素的值,和数组角标的映射关系的话。那么我们在想取某个值的元素的时候就使用映射关系就可以找到对应的角标了。
在常见的 hash 函数中有一种最简单的方法交「除留余数法」,操作方法就是将要存入数据除以某个常数后,使用余数作为索引值。 下面看个例子:
将 323 ,458 ,25 ,340 ,28 ,969, 77 使用「除留余数法」存储在长度为11的数组中。我们假设上边说的某个常数即为数组长度11。 每个数除以11以后存放的位置如下图所示:

试想一下我们现在想要拿到 77 在数组中的位置,是不是只需要 arr[77%11] = 77
就可以了。
但是上述简单的 hash 算法,缺点也是很明显的,比如 77 和 88 对 11 取余数得到的值都是 0,但是角标为 0 位置已经存放了 77 这个数据,那88就不知道该去哪里了。上述现象在哈希法中有个名词叫碰撞:
碰撞:若两个不同的数据经过相同哈希函数运算后,得到相同的结果,那么这种现象就做碰撞。
- 如果两个对象相同,那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同(这里说的对象相同指的是用equals方法比较)。
- equals()相等的两个对象,hashCode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashCode()不相等。
4. toString()
toString()方法,它用于返回标识对象值的字符串。toString 返回的是「类名@十六进制内存地址」,由源码如下可以看出内存地址与 hashCode() 返回值相同。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
例子:
public class Student {
private String name = "Mary";
private int age = 21;
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.toString());//输出com.netty.test23.Student@24367013
}
}
随处可见toString()的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,java编译器就会自动地调用toString方法,以便获得这个对象的字符串描述。
5. getClass()
getClass() 方法返回对象所属的类,是一个 Class 对象。通过 Class 对象可以获取该类的各种信息,包括类名、父类以及它所实现接口的名字等。
Object类中包含一个方法名叫getClass,利用这个方法就可以获得一个实例的class对象。这个对象指的是代表一个类型的对象,因为一切皆是对象,类型也不例外,在Java使用class类的对象来表示一个类型。所有的类型类都是Class类的实例。
A a = new A();
if(a.getClass()==A.class)
System.out.println("equal");
else System.out.println("unequal");
结果就是打印出 “equal”。
因此,获取类的class对象的两种方式:
① 如果你知道一个实例,那么你可以通过实例的“getClass()”方法获得该对象的class对象
② 如果你知道一个类型(String名称),那么你可以使用“类名.class”的方法获得该类型的class对象
网友评论