Object中方法的使用探究
Object类中自带方法的使用探究
我们可以通过new一个Object对象查看其方法。
可以看见里面有9个方法。
equals()
equals意为比较。我们查看jdk1.8中自带的src压缩包中Object类并找到equals的源码
public boolean equals(Object obj) {
return (this == obj);
}
默认情况下它是将两个对象进行地址的比较,如果地址相同,返回true否则返回false。
但是在很多情况下,我们自定义的类并不能直接调用Object 提供的equals方法来进行判断两个对象在实际意义上是否相同。
class Person{
public int id;
public String name;
public int phoneNo;
public Person() {
// TODO Auto-generated constructor stub
}
Person(int id,String name,int phone){
this.id=id;
this.name=name;
this.phoneNo=phone;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "姓名:"+this.name+" 身份证号码"+this.id+" 手机号码"+this.phoneNo;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return this.name.hashCode()+this.id*31+this.phoneNo*31;
}
@Override
public boolean equals(Object arg0) {
// TODO Auto-generated method stub
if (arg0==this) {
return true;
}
if (arg0 instanceof Person) {
Person person=(Person)arg0;
if (person.hashCode()==this.hashCode()) {
return true;
}
if (person.getId()==this.getId()&&person.getName().equals(this.getName())&&person.getPhoneNo()==this.getPhoneNo()) {
return true;
}
}
return false;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPhoneNo() {
return phoneNo;
}
public void setPhoneNo(int phoneNo) {
this.phoneNo = phoneNo;
}
}
public class DEmo {
public static void main(String[] args) {
Person p1=new Person(123456,"王邦彦",13311111);
Person p2=p1;
Person p3=new Person(123456,"王邦彦",13311111);
Person p4=new Person(123456,"王邦彦",13311112);
if (p1.equals(p2)) {
System.out.println(1);
}
if (p1.equals(p3)) {
System.out.println(3);
}
if (p1.equals(p4)) {
System.out.println(4);
}
}
}
此时我们有4个引用对象,其中p1指向(123456,“王邦彦”,13311111)这个对象,p2只是将p1的引用地址拿了过来所以一样指向p1的对象,p3指向一个new的(123456,“王邦彦”,13311111)对象,p4指向(123456,“王邦彦”,13311112)对象。如果按照原来Object提供的默认equals方法,那么p3和p1是返回false的,这不满足我们实际生产需要。所以我们需要将equals方法进行重写(覆写)。
public boolean equals(Object arg0) {
// TODO Auto-generated method stub
if (arg0==this) {
return true;
}
if (arg0 instanceof Person) {
Person person=(Person)arg0;
if (person.hashCode()==this.hashCode()) {
return true;
}
if (person.getId()==this.getId()&&person.getName().equals(this.getName())&&person.getPhoneNo()==this.getPhoneNo()) {
return true;
}
}
return false;
}
重写时套路都是一个模板,先进行地址判断,如果和自己相同那肯定是true,然后判断对象是不是自己所在类的实例,如果是则将其转型为自己类对象,判断hash值是否一致,最后再使用“杀手锏”判断类中各个属性是否相同。注意代码中person.getName().equals调用的是String类中重写的equals方法。
我们再来看下String类是如何重写equals方法的:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
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;
}
```
是不是同样的套路?,不过String类的“杀手锏”是通过将字符数组中的字符进行单独比较。
getClass()
从字面上看,应该是获取该对象的类。
查看源码
public final native Class<?> getClass();
可以看到,它是有native关键字进行修饰的,什么是native关键字呢?凡是一种语言,都希望是纯。比如解决某一个方案都喜欢就单单这个语言来写即可。Java平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Java本地接口)。native的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。所以native关键字的函数都是操作系统实现的,java只能调用。[1]
既然从源码方面我们看不到什么,那么只好实际调用一番来观察getClass到底是什么东西。以equals的代码为例
public class DeepCopy {
public static void main(String[] args) throws Exception {
School_D school_D=new School_D("武汉大学");
Person_D p1=new Person_D("小明", 11, "张家界",school_D);
System.out.println(p1.getClass());
System.out.println(school_D.getClass());
}
}
结果是 这和我们从字面上看是一致的。
以下是我对其实现的猜测,如有错误还望大家能够指出。
jvm在加载一个类的时候,会先在方法区中寻找是否有这个类信息,如果没有则到/path路径下寻找.class文件,找不到则产生异常ClassNotFonudException,找到了则将其通过类加载器加载至方法区。而实例一个类对象的时候,会将此类的类加载器的引用一并存在方法区中。getClass方法很可能是通过这种方式找到类信息。
hashCode()
从字面上看是获取此对象的哈希值
源码
public native int hashCode();
和getClass一样都是本地方法。
public class DeepCopy {
public static void main(String[] args) throws Exception {
School_D school_D=new School_D("武汉大学");
Person_D p1=new Person_D("小明", 11, "张家界",school_D);
System.out.println(p1.hashCode());
System.out.println(school_D.hashCode());
}
}
可以看出,hashCode方法是将对象进行hash运算,产生一串int类型的值。
HashMap中内部类Node里面有个hash属性,指的就是对象的hash值,那它又是怎么利用hashCode方法呢,观察源码
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
它将key的hash值和value的hash值进行了异或操作,进一步避免了哈希冲突的发生。充分利用了HashMap中桶的数量。
Notify(),NotifyAll(),Wait()
用于多线程编程中,对线程进行休眠及唤醒操作。
一个线程中的某个同步共享对象执行wait方法,该线程就释放了该对象的对象锁,进入对象等待池,等待被唤醒;在另一个线程中,这个同步共享变量执行notify方法,唤醒因wait而正在等待使用该对象的线程,使其进入对象锁等待池,有机会获得对象锁,等到获取对象锁,该线程获得CPU调度,继续运行。需要注意wait、notify以及notifyall方法必须在synchronized代码块中,切记![2]
public class WaitAndNotify{
ReentrantLock rLock=new ReentrantLock();
private List<Integer> MyList=new ArrayList<Integer>();
public static void main(String[] args) {
WaitAndNotify waitAndNotify=new WaitAndNotify();
threadA t1=new threadA(waitAndNotify.rLock,waitAndNotify.MyList);
threadB t2=new threadB(waitAndNotify.rLock,waitAndNotify.MyList);
Thread thread1=new Thread(t1);
Thread thread2=new Thread(t2);
thread1.start();
thread2.start();
}
}
class threadA implements Runnable{
ReentrantLock rLock=null;
List<Integer> MyList=new ArrayList<Integer>();
public threadA(ReentrantLock lock,List<Integer> list) {
// TODO Auto-generated constructor stub
this.rLock=lock;
}
public void run() {
try {
synchronized (rLock) {
if (MyList.size() != 5) {
System.out.println("wait begin "
+ System.currentTimeMillis());
Thread.sleep(2000);
rLock.wait(); //wait()方法执行后,将锁释放,threadB获得锁开始循环
System.out.println("wait end "
+ System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class threadB implements Runnable{
ReentrantLock rLock=null;
List<Integer> MyList=new ArrayList<Integer>();
public threadB(ReentrantLock lock,List<Integer> list) {
// TODO Auto-generated constructor stub
this.rLock=lock;
}
public void run() {
try {
synchronized (rLock) {
for (int i = 0; i < 10; i++) {
MyList.add(i);
if (MyList.size() == 5) {
rLock.notify(); //notify方法调用之后,不会马上释放锁,而是运行完该同步方法或者是运行完该同步代码块的代码
System.out.println("已发出通知!");
}
System.out.println("添加了" + (i + 1) + "个元素!");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
notify之后并不像wait一样立即释放资源,而是等线程剩余任务完成后才交出锁对象和CPU。
wait()方法可以传入参数也可以不传入参数,传入参数就是在参数结束的时间后开始等待,不传入参数就是直接等待。
Sleep()与Wait()区别
sleep方法是Thread中的静态方法。public static native void sleep(long millis) throws InterruptedException;
导致此线程暂停执行指定时间,给其他线程执行机会,但是依然保持着监控状态,过了指定时间会自动恢复,调用sleep方法不会释放锁对象。
当调用sleep方法后,当前线程进入阻塞状态。目的是让出CPU给其他线程运行的机会。但是由于sleep方法不会释放锁对象,所以在一个同步代码块中调用这个方法后,线程虽然休眠了,但其他线程无法访问它的锁对象。这是因为sleep方法拥有CPU的执行权,它可以自动醒来无需唤醒。而当sleep()结束指定休眠时间后,这个线程不一定立即执行,因为此时其他线程可能正在运行。
wait方法是Object类里的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时释放了锁对象,等待期间可以调用里面的同步方法,其他线程可以访问,等待时不拥有CPU的执行权,否则其他线程无法获取执行权。当一个线程执行了wait方法后,必须调用notify或者notifyAll方法才能唤醒,而且是随机唤醒,若是被其他线程抢到了CPU执行权,该线程会继续进入等待状态。由于锁对象可以时任意对象,所以wait方法必须定义在Object类中,因为Obeject类是所有类的基类。[3]
toString()
返回对象的信息,默认情况下是返回对象的类信息+hash值(16进制)
public class DeepCopy {
public static void main(String[] args) throws Exception {
School_D school_D=new School_D("武汉大学");
Person_D p1=new Person_D("小明", 11, "张家界",school_D);
System.out.println(p1.hashCode());
System.out.println(p1.toString());
int a=p1.hashCode();
System.out.println(a);
}
}
当有特殊需要时,可以对其进行重写。
public class DeepCopy {
public static void main(String[] args) throws Exception {
School_D school_D=new School_D("武汉大学");
Person_D p1=new Person_D("小明", 11, "张家界",school_D);
System.out.println(p1.toString());
}
}
@Override
public String toString() {
return "姓名是:"+this.name+"年龄:"+this.age+"住址"+this.address+" 学校是"+this.school;
}
参考资料
[1]https://www.cnblogs.com/KingIceMou/p/7239668.html
[2]https://www.cnblogs.com/hzhtracy/p/4636185.html
[3]https://www.cnblogs.com/lyx210019/p/9427146.html