Object类的方法

155 阅读6分钟

clone()

创建并返回此对象的一个副本。

//定义一个Person类
class Person{
	String name;
	int age;
        
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
}
Person p = new Person("Matrixx", 10);
Person p1 = (Person) p.clone();

System.out.println(p);
System.out.println(p1);

可以从打印结果看到对象的地址是不同的,也就是说clone()创建了新的对象,而不是像下面这样把原对象的引用赋值给引用变量。

Person p1 = p;

在使用clone()需要注意一个深拷贝和浅拷贝的问题,那么什么是深拷贝和浅拷贝呢?

还是以上面的两个对象来举例。我们可以从Person知道,Person有一个存放姓名的String类型的变量name和一个存放年龄的int类型的变量ageint是基本数据类型,在拷贝时直接拷贝数值过来就行,但是name变量的类型是String,是一个引用类型,这时就可能出现两种方式的拷贝了:

一种是把原对象的name属性的引用值拷贝给新对象,这样原对象与新对象的name属性都指向同一个字符串对象,这种方法就是浅拷贝。

另一种是创建一个新的与原对象name属相同内容的字符串对象,把新的字符串对象赋值给新拷贝的对象,这个方法就是深拷贝。

那么使用clone()方法后的新对象是浅拷贝还是深拷贝呢?

String result = p.getName() == p1.getName() ? "clone是浅拷贝的" : "clone是深拷贝的";
System.out.println(result);

通过这种验证方法我们知道clone()是浅拷贝的。所以在编写程序时要注意这个细节。

如果想进行深拷贝的话可以重写clone()方法,如果在拷贝一个对象时,要想让这个拷贝的对象和源对象完全彼此独立,那么在引用链上的每一级对象都要被显式的拷贝。所以创建彻底的深拷贝是非常麻烦的,尤其是在引用关系非常复杂的情况下, 或者在引用链的某一级上引用了一个第三方的对象, 而这个对象没有实现clone()方法, 那么在它之后的所有引用的对象都是被共享的。也就是不彻底的深拷贝。

finalize()

JVM的垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

getClass()

返回一个对象所属类的的类对象,常用于反射。

Java反射机制是在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态调用对象的方法的功能称为Java的反射机制。

public class Test {
    public static void main(String[] args) {
        Person p = new Person("Matrixx", 10);
        System.out.println(p.getClass()); //获得当前对象的类类型,输出:class xxx.xxx.Person
        System.out.println(p.getClass().getName()); //根据当前对象的类类型获得全类名,输出:xxx.xxx.Person
    }
}

class Person {
    String name;
    int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

equals()

用于比较此对象与某个对象是否相等,默认比较对象地址,可重写此方法定制比较规则。

public class Test {
    public static void main(String[] args) {
        Person p = new Person("Matrixx", 10);
        Person p1 = new Person("Matrixx", 10);
		
        System.out.println(p.equals(p1)); //输出:false
    }
}

class Person{
    String name;
    int age;
	
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

虽然pp1对象的属性值相同,但由于equals()默认比较的是对象的地址,所以返回false,如果想要在这种情况下返回true,需要在Person中重写equals()方法编写比较的逻辑。

hashCode()

返回此对象的哈希值(hashCode)

根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)生成一个数值,这个数值就是哈希值,可重写此方法定制生成哈希值的规则,来防止两个对象的equals()相等但hashCode()不相等的情况。

一般来说,如果如果两个对象xy满足x.equals(y) == true,它们的哈希值应当相同。Java对于eqauls()方法和hashCode()方法是这样规定的:

  1. 如果两个对象equals()方法比较相同(equals()方法返回true),那么它们的hashCode一定相同;
  2. 如果两个对象的hashCode相同,则它们并不一定相同。

所以当我们重写了equals()方法确保x.equals(y)返回true时,需要重写一下hashCode(),确保hashCode()也要返回true。 当然,未必要按照要求去做,但是如果在使用有关hash表的数据结构存储这个对象的时候(比如HashMapHashSet等),比如说HashSet,那就有可能在存储的时候存在两个相同的对象,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希值频繁的冲突将会造成存取性能急剧下降)。

wait()

导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法

只能在synchronized块中调用

同步对象:

Object someObject = new Object();
synchronized (someObject){
	//此处的代码只有占有了someObject后才可以执行
}

synchronized表示当前线程,独占 对象 someObject当前线程独占了对象someObject,如果有其他线程试图占有对象someObject,就会等待,直到当前线程释放对someObject的占用。someObject又叫同步对象,所有的对象,都可以作为同步对象。

释放同步对象的方式: synchronized块自然结束,或有异常抛出。

wait(long timeout)

导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。

wait(long timeout, int nanos)

导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

notify()

唤醒在此对象监视器上等待的单个线程。

同步对象的方法,参考wait(),只能在synchronized块中调用。

notifyAll()

唤醒在此对象监视器上等待的所有线程。

toString()

返回该对象的字符串表示,类名+@+十六进制对象地址。一般重写此方法用于查看对象的属性的内容。