前言
在Java开发中,Object类是一个非常重要的类。它是所有类的超类,也就是说,所有的对象都是Object类的实例。
本文将详细介绍Object类的相关知识,包括其定义、常用方法源码分析和应用场景等。
简介
在Java中,每个类都必须有一个父类。如果没有明确指定父类,则默认继承自Object类。因此,所有的Java类都可以调用Object类的方法。
进入object类中
public class Object {
public Object() {}
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj);
protected native Object clone() throws CloneNotSupportedException;
public String toString();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
public final void wait() throws InterruptedException;
protected void finalize() throws Throwable { }
}
方法解析
equals
定义
equals()方法用于比较两个对象是否相等。默认情况下,equals()方法比较两个对象的引用是否相等,也就是它们是否指向了内存中的同一个对象。具体实现如下:
public boolean equals(Object obj) {
return (this == obj);
}
当我们需要比较两个对象的内容是否相等时,就要重写equals()方法,示例如下:
String类重写equals()源码分析
public boolean equals(Object anObject) {
//判断两个引用是否指向相同的对象
if (this == anObject) {
return true;
}
//判断前面的对象(anObject)是否属于后面的类,或者属于其子类(String)
if (anObject instanceof String) {
String anotherString = (String)anObject;
//String底层维护一个private final char value[]数组
int n = value. Length;
//长度不等,直接false
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;
}
hashCode()
定义
hashCode() 的作用是获取哈希码(int
整数),也称为散列码。hashCode()方法的默认实现返回对象的内存地址。
- 如果两个对象的
hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。
- 如果两个对象的
hashCode
值不相等,我们就可以直接认为这两个对象不相等。
所以在重写equals()方法时,通常也需要重写hashCode()方法,以确保两个相等的对象具有相同的哈希码。
hashCode()方法的定义:
public native int hashCode();
native介绍
native关键字表示一个方法的实现是由本地代码(如C或C++)实现的,而不是由Java虚拟机(JVM)提供的。
上述方法hashCode()方法被声明为native,这意味着它的具体实现是由底层的本地代码提供的,而不是由Java代码实现的。
String类重写hashCode()源码分析
public int hashCode() {
//hash:String中维护的哈希码默认为0 private int hash;
int h = hash;
//检查缓存的哈希码是否为0且字符串的长度大于0。如果缓存的哈希码不为0,表示已经计算过哈希码,直接返回;如果字符串长度为0,则哈希码也为0,直接返回。
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
//采用了经典的哈希算法,具体来说,将之前的哈希码乘以31(左移5位,相当于乘以32再减去自身),然后加上字符数组中当前位置的字符值。
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
toString()
定义
toString()方法返回对象的字符串表示。默认情况下,toString()方法返回的是对象的类名和哈希码。toString()方法的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString()也可以重写成你想要的字符串格式
private int id;
private String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
/*@Override
public String toString() {
return "MyClass{id=" + id + ", name='" + name + "'}";
}*/
public static void main(String[] args) {
Person obj = new Person(1, "John");
//输出:com.ccnu.equals.Person@1b6d3586
//重写后输出:MyClass{id=1, name='John'}
System.out.println(obj);
}
使用场景
- 进制转换
有兴趣可以看看这几个方法如何实现比较简单 。
- 生成随机验证码
public static void main(String[] args) {
String randomString = getRandomString(4);
System.out.println(randomString);
}
public static String getRandomString(int length) {
String chars = "abcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
int index = random.nextInt(chars.length());
sb.append(chars.charAt(index));
}
return sb.toString();
}
- 单元测试
在单元测试中,toString()
方法可以用于验证对象的状态是否符合预期。通过打印对象的字符串表示形式,可以方便地检查对象的属性值和状态。(见定义)
getClass()
定义
getClass()方法返回对象的运行时类(反射)。运行时类是指在运行时确定的类,而不是在编译时确定的类。定义:
public final native Class<?> getClass();
使用
Person person = new Person(18,"张三");
System.out.println(person.getClass().getName());//com.ccnu.equals.Person
反射
3种获取class的方式
Person p = new Person();
Class cls1 = p.getClass();//对象.getClass()
Class cls2 = Person.class;//类名.class
Class cls3 = Class.forName("com.ccnu.equals.Person");//Class.forName("全类名")
2种创建类对象的方式 :
//获取class对象
Class clazz = Class.forName("com.ccnu.equals.Person");
//1.通过Class对象的newInstance()方法
Person p1 = (Person) = clazz.newInstance();
//2.通过Constructor对象的newInstance()方法。
//如果有默认构造方法,就使用这个,先通过Class对象获取指定的Constructor对象
Person p1 = (Person) clazz.getConstructor().newInstance();
//指定构造方法
Person p2 = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 1);
举例:通过反射创建数组
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,5);
// 向数组添加内容
Array.set(array,0,"Hello");
Array.set(array,1,"CCNU");
Array.set(array,2,"HXJ");
Array.set(array,3,"study");
Array.set(array,4,"Java");
// 获取数组中指定位置的内容
System.out.println(Array.get(array,2));
获取类属性、方法、构造器:
Field[] fields = clazz.getFields();
for(Field field : fields){
System.out.println(field.getName());
}
Method[] methods = clazz.getMethods();
for(Method method : methods){
System.out.println(method.getName());
}
//构造器的获取上述已经涉及到了
wait()、notify()、notifyAll()
定义
- wait()方法用于等待某个条件的发生。wait()方法有多个重载版本,可以指定等待的时间和唤醒的条件等。
在获取到对象的
synchronized
锁之后,通过调用wait()
方法可以释放当前线程对对象的锁,同时当前线程会被加入到等待池中,当前的线程就会进入WAITING
状态。只有等其他的线程调用notify()
/notifyAll()
方法后,这个线程会被移动到锁池中,在锁池中重新竞争获取到锁后才会把线程状态变为RUNNING
状态,然后继续执行。 - notify()方法用于唤醒一个等待中的线程。
在获取到对象的synchronized
锁之后,通过调用notify()
方法可以将等待池中的一个线程移动到锁池中(如果等待池为空,那就没有可移动的线程)。这个方法调用后不会释放对当前对象的锁,只有在当前线程对当前的锁释放后,锁池中的线程才重新竞争。 - notifyAll()
其他特性都和notify()
方法类似,不过调用notifyAll()
方法后可以将所有的等待池中的线程移动到锁池中。
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
public final native void notify();
public final native void notifyAll();
锁池与等待池
- 锁池
假如有一个对象ObjectA
,有三个线程T1
、T2
和T3
都通过synchronized
的方式去竞争锁,假如第一次竞争由T2
线程获取了锁,那么T1
和T3
就会被加入对象ObjectA
的锁池,当T2
线程释放锁之后,锁池里面的T1
与T3
线程再重新竞争锁。简单来说就是对象的锁池里面的线程在持有锁的线程释放后他们会自动再去竞争锁。 -
等待池
假如线程T2
通过synchronized
的方式获取了ObjectA
对象的锁后,T2
线程通过ObjectA
对象的wait()
方法释放对线对象ObjectA
的锁,并且会把T2
线程添加到ObjectA
对象的等待池中。等待池中的线程和锁池中的线程的区别是就算在对象ObjectA
的锁被释放后也不会去主动去竞争锁。只有当等待池中的线程被移动到锁池后才会去重新竞争锁,通过notify()
或者notifyAll()
方法可以把等待池中的线程移动到锁池中。
使用
// 创建一个将被两个线程同时访问的共享对象
public static Object object = new Object();
// Thread0线程,执行wait()方法
static class Thread0 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "初次获得对象锁,执行中,调用共享对象的wait()方法...");
try {
// 共享对象wait方法,会让线程释放锁。
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "再次获得对象锁,执行结束");
}
}
}
// Thread1线程,执行notify()方法
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
// 线程共享对象,通过notify()方法,释放锁并通知其他线程可以得到锁
object.notify();
System.out.println(Thread.currentThread().getName() + "获得对象锁,执行中,调用了共享对象的notify()方法");
}
}
}
// 主线程
public static void main(String[] args) {
Thread0 thread0 = new Thread0();
Thread1 thread1 = new Thread1();
thread0.start();
try {
// 保证线程Thread0中的wait()方法优先执行,再执线程Thread1的notify()方法
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.start();
}
输出
clone()
定义
clone()方法:创建并返回对象的一个副本。默认实现是浅拷贝,即复制对象的引用值,而不是实际的内容。
如果需要自定义复制行为,可以重写clone()方法。
//要使用clone()方法,必须实现Cloneable接口,否则会抛出CloneNotSupportedException异常。
protected native Object clone() throws CloneNotSupportedException;
使用
浅拷贝:创建副本对象和原对象都指向同一块内存空间,副本对象的改变会影响到原对象
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
public static void main(String[] args) {
Person obj = new Person("Alice", 30);
try {
Person cloneObj = (Person) obj.clone(); // 创建obj的一个浅拷贝
System.out.println(obj);//Person{name='Alice', age=30}
System.out.println(cloneObj);//Person{name='Alice', age=30}
// 修改拷贝对象的属性
cloneObj.setAge(25);
System.out.println("Modified cloned object: " + cloneObj);//Modified cloned object: Person{name='Alice', age=25}
System.out.println("Original object after modification: " + obj);Original object after modification: Person{name='Alice', age=25}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
深拷贝:副本对象和原对象不指向通一块内存空间,会重新开辟一块内存空间给副本对象进行指向,副本对象的改变会影响到原对象
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// 创建新的 Person 对象
Person clonedPerson = (Person) super.clone();
// 对引用类型字段进行深拷贝,age 是基本类型,无需额外处理
clonedPerson.name = new String(this.name);
return clonedPerson;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
public static void main(String[] args) {
Person obj = new Person("Alice", 30);
try {
Person cloneObj = (Person) obj.clone(); // 创建obj的一个深拷贝
// 输出原始对象和深拷贝对象
System.out.println(obj);//Person{name='Alice', age=30}
System.out.println(cloneObj);//Person{name='Alice', age=30}
// 修改拷贝对象的属性
cloneObj.setAge(25);
System.out.println("Modified cloned object: " + cloneObj);//Modified cloned object: Person{name='Alice', age=25}
System.out.println("Original object after modification: " + obj);Original object after modification: Person{name='Alice', age=30}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
finalize()
定义
finalize()
- 在垃圾收集器决定回收对象之前,由垃圾收集器调用此方法。这个 finalize 机制是不确定的,不保证会被调用,且在Java 9中已经被弃用。
总结
Object类是Java语言中最基本的类之一,它是所有类的祖先。正确使用,重写object类帮你解决不少问题,同时包含许多小知识点值得学习如:native,反射,clone及一些小案例等等。
分享、积累、沉淀! 加油!!!