#clone
Java中要想自定义类的对象可以被复制,自定义类就必须实现Cloneable中的clone()方法,如下:
public class Person implements Cloneable {
private String name;
private Animal pet;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Animal getPet() {
return this.pet;
}
public void setPet(Animal pet) {
this.pet = pet;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String toString() {
return "Person[ name: " + name + ", pet: " + pet.toString() + "]";
}
}
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Anmial[ name: " + name + "]";
}
}
public class Test {
public static void main(String[] args) {
Person ming = new Person("小明");
Animal kitty = new Animal("喵");
ming.setPet(kitty);
System.out.println(ming.toString());
try {
Person hong = (Person) ming.clone(); // 复制ming
hong.setName("小红");
hong.getPet().setName("汪");
System.out.println(hong.toString());
System.out.println(ming.toString());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
结果:
结果发现,hong的名字可以改,不会影响ming,但是,改了hong的宠物的名字后,ming的宠物的名字也随之变了。
为什么?
浅拷贝:
调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内 容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用(引用指向堆内存中同一个对象),这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。因此,ming和hong指向的实际是同一个Animal类对象。
这就是浅拷贝。
那么怎么能使得拷贝得到的原始对象中的非基本类型变量也是一个新的对象(内存地址不同,内容相同)呢?这就要深拷贝。
深拷贝:
上面的例子要实现深拷贝需要完成两点:
Animal类也要实现clone方法;
Person类的clone方法里,拷贝一个克隆人后,还要调用Animal的clone(),拷贝当前Person的宠物,然后将克隆宠物赋给克隆人的宠物,然后再返回这个真正的克隆人。
代码:
public class Person implements Cloneable {
private String name;
private Animal pet;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Animal getPet() {
return this.pet;
}
public void setPet(Animal pet) {
this.pet = pet;
}
@Override
public Object clone() throws CloneNotSupportedException {
Person clonePerson = (Person) super.clone();
clonePerson.pet = (Animal) pet.clone();
return clonePerson;
}
public String toString() {
return "Person[ name: " + name + ", pet: " + pet.toString() + "]";
}
}
public class Animal implements Cloneable{
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String toString() {
return "Anmial[ name: " + name + "]";
}
}
public class Test {
public static void main(String[] args) {
Person ming = new Person("小明");
Animal kitty = new Animal("喵");
ming.setPet(kitty);
System.out.println(ming.toString());
try {
Person hong = (Person) ming.clone(); // 复制ming
hong.setName("小红");
hong.getPet().setName("汪");
System.out.println(hong.toString());
System.out.println(ming.toString());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
运行结果:
#equals & == & hashCode
- euqls & hashCode
equals 方法是基类 Object 的方法,我们创建的所有的对象都拥有这个方法,并有权利去重写这个方法。该方法返回一个 boolean 值,代表比较的两个对象是否相同。
判断相等,还有一个方法是使用==,equals和==有什么区别呢?其实它们两个都是通过比较内存地址(准确的说是堆内存地址)来比较两个数据是否相等,但是为什么实际使用时好像不一样呢,这是因为equals很重要的一个特点,可以被重写。
先举一个字符串的例子:
public class Test {
public static void main(String[] args) {
String str1 = "123";
String str2 = "123";
String str3 = new String("123");
String str4 = new String("123");
System.out.println("str1==str2: " + (str1 == str2));
System.out.println("str3==str4: " + (str3 == str4));
System.out.println("str1==str3: " + (str1 == str3));
System.out.println("str1.equals(str2): " + str1.equals(str2));
System.out.println("str3.equals(str4): " + str3.equals(str4));
System.out.println("str1.equals(str3): " + str1.equals(str3));
}
}
运行结果:
使用==的三组字符串比较结果为什么是不同的?
前面说到,==比较的是数据的内存地址,使用用new()来创建对象时,对象会存放在堆中,每调用一次就会创建一个新的对象,因此堆内存地址不同;而第二种形如String str = "abc",是先在栈中创建一个对String类的对象引用变量str,然后查找堆中有没有存放String对象"abc",如果没有,则将"abc"存放进堆,并令str 指向"abc",如果已经有"abc",则直接令str 指向"abc"。因此str1和str2指向同一个值为"123"的String对象,而str3和str4则分别指向指向两个值为"123"的String对象,地址不同,str1和str3自然也不同。和字符串一样,也有这两种创建方式的还有,上一节提到的基本数据类型的包装类型。
那equals的比较结果为什么都是true呢?因为Java String类的equals方法重写了,它比较的两个字符串的内容是否相等,符合大多数时候人们对于字符串比较的需求。Java中equals方法重写了的还有Integer, Data等等。我们也可以根据自己的需求重写当前类的equals方法。
- hashCode
hash 法简介
hash 算法,又被成为散列算法,基本上,哈希算法就是将对象本身的键值,通过特定的数学函数运算或者使用其他方法,转化成相应的数据存储地址的。
在常见的 hash 函数中有一种最简单的方法叫「除留余数法」,操作方法就是将要存入数据除以某个常数后,使用余数作为索引值。
例如:
将 323 ,458 ,25 ,340 ,28 ,969, 77 使用「除留余数法」存储在长度为11的数组中。我们假设上边说的某个常数即为数组长度11。每个数除以11后的余数即为该数在数组中的位置。
则现在想要拿到 77 ,只需要 访问arr[77%11] 就可以了。
hashCode 方法
Java 中的有所的对象都拥有的hashCode 方法其实就是一种 hash 算法,只是有的类覆写好提供给我们了,有些就需要我们手动去覆写。
hashCode 方法的作用和意义
在 Java 中 hashCode 的存在主要是用于提高容器查找和存储的快捷性,如 HashSet, Hashtable,HashMap 等,hashCode是用来在散列存储结构中确定对象的存储地址的。
当容器中数据很多时,通过hashCode可以快速查找数据(如上面所说的除留余数法),存储数据时也可快速查看要存储的数据是否已经存在(查重)。
要注意的是,重复的数据hashCode一定相等,但是hashCode相等的数据不一定相同。还以上面的除留余数法相同,,77除以11都余0,但是,除以11余0的不一定是77。碰撞的概率与具体的算法有关。而hashset在查重的时候则是先用hashcode来缩小寻找范围,最后还要用equals()来确定是否真的为相同的对象。
#getClass
java有两个获得类型类的方法:getClass()和class()。然后再调用该类的方法可以获取该类的相关信息,比如父类的类型类getSuperclass(),该类的名字getName()。
这两个方法涉及到了java中的反射和类型类。
反射
反射,可以理解为在运行时期获取对象类型信息的操作。
类型类
类型类指的是代表一个类型的类,因为一切皆是对象,类型也不例外,在Java使用类型类来表示一个类型。所有的类型类都是Class类的实例。
getClass()和class()最直接的区别就是,getClass() 是一个类的实例所具备的方法,而class() 方法是一个类的方法。
另外getClass() 是在运行时才确定的 (反射) ,而class() 方法是在编译时就确定了。
public class Test {
public static void main(String[] args) {
Cat kitty = new Cat("喵");
Animal doggy = new Animal("汪");
System.out.println("kitty的类型类:" + kitty.getClass() + " ; Cat的类型类:" + Cat.class);
System.out.println("doggy的类型类:" + doggy.getClass() + " ; Animal的类型类:" + Animal.class);
System.out.println("kitty的类名:" + kitty.getClass().getName() + " ; Cat的类名:" + Cat.class.getName());
System.out.println("kitty父类的类型类:" + kitty.getClass().getSuperclass());
}
}
结果:
#toString
toString()是Object类的一个公有方法,而所有类都继承自Object类。所以所有类即使不实现toString方法,也会存在从Object类继承来的toString,也可以重写。
而八种基本数据类型没有toString()方法,只能使用相应的包装类才能使用toString()。
例子:
public class Test {
public static void main(String[] args) {
Cat kitty = new Cat("喵");
System.out.println(new Double(1.2).toString()); // 1.2
System.out.println(new Integer(12).toString()); // 12
System.out.println(new Boolean(true)); // true
System.out.println(new Character('a').toString()); // a
System.out.println(kitty.toString()); // Cat@14ae5a5
System.out.println(kitty); // Cat@14ae5a5
}
}
当通过System.out.println()时,参数为类对象。这时就会调用类对象的toString方法。
而类对象的toString方法返回的是对象的对象名+@+对象的十六进制的哈希值。