java中Object 类
一. Object类简介
Object类是Java.java.lang包下的核心类,Object类是所有类的父类,任何一个类如果没有明确的继承一个父类的话,那么它就是Object的子类;
(使用无需导包,它所属JDK -> SRC.ZIP -> java -> lang 包下)
二. Object的方法
Object 提供9种方法(Clone、equals、 hashcode、wait、notify、notifyall、finalize、toString、getClass)
1 public final native Class<?> getClass(); 2 public native int hashCode(); 3 public boolean equals(Object obj); 4 protected native Object clone() throws CloneNotSupportedException; 5 public String toString(); 6 public final native void notify(); 7 public final native void notifyAll(); 8 public final native void wait(long timeout) throws InterruptedException; 9 public final void wait(long timeout, int nanos) throws InterruptedException; 10 public final void wait() throws InterruptedException ; 11 protected void finalize() throws Throwable;
三.方法的作用
1. getclass() public final native Class<?> getClass(); final修饰,用于反射机制中获取对象,返回Class类型。
1 User user = new User(); 2 Class<? extends User> userClass = user.getClass(); 3 System.out.println(userClass); 打印结果: class org.xinhua.entity.User
2. hashCode() public native int hashCode(); hash code是一种编码方式,在Java中,每个对象都会有一个hashcode,Java可以通过这个hashcode来识别一个对象。hashtable等结构,就是通过这个哈希实现快速查找键对象。返回值是int类型的散列码。
1 User user = new User(); 2 int userHashCode = user.hashCode(); 3 System.out.println(userHashCode); 打印结果: 549374472
--------------------------------------------
List<String> strList = new ArrayList<>();
strList.add("个人分类")
每次往List中添加元素,都会得到不同的hashCode值。
即:对于可变对象,hashCode
应该在它们以某种方式改变时改变,使它们不等于它们以前的状态。
3. equals(Object obj) public boolean equals(Object obj); 用于比较对象是否相等,可通过重写equals()获取不同效果。返回布尔类型的值。
1 String str1 = "aaa", str2 = "bbb"; 2 boolean equals = str1.equals(str2); 3 System.out.println(equals); 4 打印结果: false
注意:如果需要重写equals方法,应该重写hashcode方法.原生equals是比较对象是否相同,hashcode内存地址换算出来的一个值,如果只重写equals方法,而不重写hashCode方法的话,就回出现因为equals()两个对象相同,但是原先的hashCode()对应的值不同
4. clone() protected native Object clone() throws CloneNotSupportedException; 用于创建并返回一个对象的拷贝。clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。建议使用深拷贝.
① 类实现Cloneable接口,
②重写Object中clone() 方法
③有异常CloneNotSupportedException 抛出
④对象.clone()
ClassName类 // 实现Cloneable接口 public class User implements Cloneable { private String name;// 略去构造方法、getter()、setter()、toString(); // 重写clone方法 @Override protected User clone() throws CloneNotSupportedException { return (User) super.clone(); // 这里做了强转 } } main方法 public static void main(String[] args) throws CloneNotSupportedException { User user= new User("张三"); User u2= user.clone(); System.out.println(user); System.out.println(u2); }
5. toString() public String toString(); 其返回值是String类型,返回类名和它 的引用地址。可以通过重写toString(),在打印对象时获得理想的格式。
User user = new User(); user.setRealName("张三"); user.setEmail("zhangsan@163.com"); System.out.println(user); 重写toString前: 打印输入: org.xinhua.entity.User@bdf5e5 // 输入的是类名+地址 重写toString: @Override public String toString() {return "获取到的姓名是:" + this.getRealName() + ",邮箱是:" + this.getEmail(); } 打印输出: 获取到的姓名是:张三,邮箱是:zhangsan@163.com
6. notify() public final native void notify(); 多线程编程中比较常见,从当前对象锁的等待队列中获取一个线程(如果有多个线程则获取优先级高的,优先级都一样则随机),移入到同步队列中,参与锁的竞争。
①搭配 synchronized 来使用,脱离synchronized使用notify会抛出IllegalMonitorStateException异常,目的是防止死锁或永久等待发生
②调用notify方法后,当前线程不会马上释放该对象锁,要等到同步块或者同步方法执行完后,当前线程才会释放锁。
public static void main(String[] args) throws InterruptedException { Object o = new Object(); new Thread(() -> { System.out.println("第一步:wait执行,"); synchronized (o) { try { o.wait(); // 使当前执行代码的线程进行等待,把线程放在等待队列中,若没有唤醒,则持续等待。 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("第四步:wait执行后"); }).start(); Thread.sleep(1000); new Thread(() -> { System.out.println("第二步:notify执行前"); synchronized (o) { o.notify(); // 用于唤醒 o.wait的线程 } System.out.println("第三步:notify执行后"); }).start(); }
7. notifyAll() public final native void notifyAll(); 和notify一样,都是用来唤醒处于等待状态的线程参与锁的竞争,但也有一些区别。notifyAll会将当前对象锁的等待队列的所有线程,都移入到同步队列中,但与锁的竞争。
①搭配 synchronized 来使用,脱离synchronized使用notify会抛出IllegalMonitorStateException异常,目的是防止死锁或永久等待发生。
②调用notifyAll方法后,当前线程不会马上释放该对象锁,要等到同步块或者同步方法执行完后,当前线程才会释放锁。
public static void main(String[] args) throws InterruptedException { Object o = new Object(); new Thread(() -> { System.out.println("wait1执行前."); synchronized (o) { try { o.wait(); // 使当前执行代码的线程进行等待,把线程放在等待队列中,若没有唤醒,则持续等待。 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("notifyAll执行后,wait1"); }).start(); Thread.sleep(1000); new Thread(() -> { System.out.println("wait2执行前.."); synchronized (o) { try { Thread.sleep(1000); o.wait(); // 使当前执行代码的线程进行等待,把线程放在等待队列中,若没有唤醒,则持续等待。 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("notifyAll执行后,wait2"); }).start(); Thread.sleep(1000); new Thread(() -> { System.out.println("notify线程执行前"); synchronized (o) { o.notifyAll(); // 用于唤醒所有当前o锁的等待线程, } System.out.println("notify线程执行后"); }).start(); }
8. wait() public final void wait() throws InterruptedException ; 让当前线程进入等待(阻塞)WAITTING 状态,将线程放入到等待队列中,并释放对象锁。
①搭配 synchronized 来使用,脱离synchronized使用notify会抛出IllegalMonitorStateException异常,目的是防止死锁或永久等待发生。
②调用wait方法后,会立即释放当前线程锁占有的对象锁。会暂停执行wait后面的代码块,需要等待,其它拿到该锁对象的线程执行notify或notifyAll方法唤醒该线程后,才会继续执行wait后面的代码块。
代码如上所示。
9. wait(long timeout) public final native void wait(long timeout) throws InterruptedException; timeout(毫秒)时间后会被自动唤醒 wait(0)等同于wait()
1 public static void main(String[] args) { 2 Object o = new Object(); 3 4 new Thread(() -> { 5 long start = System.currentTimeMillis(); 6 System.out.println("wait开始执行"); 7 synchronized (o) { 8 try { 9 o.wait(1000); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 } 14 long end = System.currentTimeMillis(); 15 System.out.println("wait执行结束"); 16 System.out.println("执行时间:" + (end - start) + "毫秒"); 17 }).start(); 18 } 19 20 打印输出: 21 wait开始执行 22 wait执行结束 23 执行时间:1010毫秒
10. wait(long timeout, int nanos) public final void wait(long timeout, int nanos) throws InterruptedException; nanos 这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫微秒。 wait(0, 0) 与 wait(0) 相同。 wait(500, 50000),先执行5000毫微秒,执行结束后调用timeout执行。
1 public static void main(String[] args) { 2 Object o = new Object(); 3 4 new Thread(() -> { 5 long start = System.currentTimeMillis(); 6 System.out.println("wait开始执行"); 7 synchronized (o) { 8 try { 9 o.wait(5000,50000); // 先执行5000毫微秒,执行结束后调用timeout执行。 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 } 14 long end = System.currentTimeMillis(); 15 System.out.println("wait执行结束"); 16 System.out.println("执行时间:" + (end - start) + "毫秒"); 17 }).start(); 18 } 19 20 打印输出: 21 wait开始执行 22 wait执行结束 23 执行时间:513毫秒
11. finalize() protected void finalize() throws Throwable; finalize()垃圾回收机器,方法负责回收Java对象所占用的内存,该方法一般是在对象被垃圾收集器回收之前调用。通常我们会在finalize()方法中,指定对象销毁时要执行的操作,比如关闭对象打开的文件、IO流、释放内存资源等清理垃圾碎片的工作。
垃圾回收机器有以下的特点:
①当对象不再被程序所使用的时候,垃圾回收器将会将其回收;
②垃圾回收是在后台运行的,我们无法命令垃圾回收器马上回收资源,但可以通过System.gc()或Runtime.getRuntime().gc()方法提示垃圾回收器执行回收操作。
③垃圾回收器在回收某个对象的时候,首先会调用该对象的finalize()方法
④GC主要针对堆内存
⑤垃圾回收的时机具有不确定性,finallize()也可能自始自终都不被调用。
1 public class User { 2 3 private Integer count; 4 5 public User(Integer count) { 6 this.count = count; 7 } 8 9 // 重写finalize()方法,用于标记count计数。 10 @Override 11 protected void finalize() throws Throwable { 12 System.out.println("gc标记,销毁。" + count++); 13 super.finalize(); 14 } 15 } 16 17 public class GCTest{ 18 public static void main(String[] args) throws InterruptedException { 19 User user = null; 20 for (int i = 1; i <= 100; i++) { 21 Thread.sleep(100); 22 user = new User(i); 23 System.gc(); // 调用GC方法配合finalize()使用。 24 } 25 26 } 27 } 28 29 打印输出: 30 ......... 31 gc标记,销毁。91 32 gc标记,销毁。92 33 gc标记,销毁。93 34 gc标记,销毁。94 35 gc标记,销毁。95 36 gc标记,销毁。96 37 gc标记,销毁。97 38 gc标记,销毁。98 39 gc标记,销毁。99
// 从打印角度没有看到gc标记,销毁。100 有两种可能,第一种是java进程走完了,导致还没有执行回收就被停止了;第二种可能,已标记,暂时未回收。
// 项目中若有其他线程引用了该对象的引用值,会出现标记不清理的情况,长时间不适用,就会自动释放。
三. 拓展
1. 为什么需要重写equals和hashCode方法?
首先我们看下源码中的equals方法;
public boolean equals(Object obj) { return (this == obj); // 比较地址值是否相同 }
Java中规定:
(1)如果两个对象通过equals方法比较是相等的,那么它们的hashCode方法结果值也是相等的。
(2)如果两个对象通过equals方法比较是不相等的,那么它们的hashCode方法结果值不一定不相等。
如果只重写equals,而不重写hashcode,会导致地址值相同,而hashcode不同。在使用hashMap、hashSet中,通过计算hashcode来筛重,会出现错误。
public class User { private String name; public User(String name) { this.name = name; } // 重写equals @Override public boolean equals(Object o) { if (o instanceof User) { User u = (User) o; if (this.name.equals(u.name)) return true; else return false; } else { return false; } } } public class Main { public static void main(String[] args) { User u1 = new User("张三"); User u2 = new User("张三"); System.out.println(u1.equals(u2)); System.out.println(u1.hashCode()); System.out.println(u2.hashCode()); } } 打印输出: true 23934342 22307196
2. clone() 浅拷贝和深拷贝
浅拷贝:只拷贝栈内存中的数据,不拷贝堆内存的数据。(只拷贝基本类型数据,使用引用类型数据原地址);
深拷贝:既拷贝栈内存中的数据,又拷贝堆内存中的数据。(拷贝基本数据类型,同时在堆内存中开辟一块空间,用于拷贝引用类型的数据);
特殊类型:String类型。clone() 并不会克隆Sting类;String是基于堆内存、常量池,这种比较特殊,本身没有实现CloneAble,自身是由final修饰,每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。
浅拷贝实现:
User类,没有实现Cloneable接口 public class User { private int age; private String name; // 略:构造方法、getter、setter、toString... // 没有重写clone()方法 } UserDto类,实现Cloneable接口 public class UserDto implements Cloneable { private String phone; //引用user类 private User user; // 略:构造方法、getter、setter、toString... @Override protected UserDto clone() throws CloneNotSupportedException { return (UserDto) super.clone(); } } Main方法 public static void main(String[] args) throws CloneNotSupportedException { User u1 = new User(18, "张三"); UserDto d1 = new UserDto("110",u1); UserDto d2 = d1.clone(); // clone() 克隆 System.out.println(d1.getUser()); //打印d1的引用类型地址: System.out.println(d2.getUser());// 打印d2的引用类型地址 System.out.println(System.identityHashCode(d1.getPhone())); // String类型的地址值 System.out.println(System.identityHashCode(d2.getPhone())); } 打印输出: clone.User@16d3586 // 类名+地址值 clone.User@16d3586 22307196 22307196
可以看到引用类型地址值没有变化。
深拷贝实现:
User类,实现Cloneable接口 public class User implements Cloneable { private int age; private String name; // 略:构造方法、getter、setter、toString // 引用类中重写clone()。 @Override protected User clone() throws CloneNotSupportedException { return (User) super.clone(); } } UserDto类中,实现CloneAble接口 public class UserDto implements Cloneable { private String phone; private User user; // 略:构造方法、getter、setter、toString // 重写clone() @Override protected UserDto clone() throws CloneNotSupportedException { UserDto userDto = (UserDto) super.clone(); userDto.user = user.clone(); return userDto; } } Main类:未做改动 public static void main(String[] args) throws CloneNotSupportedException { User u1 = new User(18, "张三"); UserDto d1 = new UserDto("110",u1); UserDto d2 = d1.clone(); // clone() 克隆 System.out.println(d1.getUser()); //打印d1的引用类型地址: System.out.println(d2.getUser());// 打印d2的引用类型地址 System.out.println(System.identityHashCode(d1.getPhone())); // String类型的地址值 System.out.println(System.identityHashCode(d2.getPhone())); } 打印输出: clone.User@16d3586 // 类名+地址值 clone.User@154617c 10568834 10568834 引用类型地址值发生变化
clone() 过去繁琐,推荐使用 BeanUtils.copyProperties() 方法。 包:import org.springframework.beans.BeanUtils;
3. notify()和 notifyAll()有什么区别?
① 唤醒数量不同,notify()方法只会随机唤醒等待队列中的一个线程,而notifyAll()方法则会唤醒等待队列中的所有线程。
② 调用方式不同,notify()和notifyAll()方法都必须在同步代码块中调用,并且必须包含在synchronized块中,且必须是该对象的监视器对象才能够调用。而且只有在获取了锁之后才能调用,否则会抛出IllegalMonitorStateException异常。
③ 竞争情况不同,notify()方法只会唤醒等待队列中的一个线程,并使其与其他线程竞争获取锁,这可能会导致某些线程无法被唤醒或者一直处于等待状态。 notifyAll()方法则会唤醒等待队列中的所有线程,并使它们竞争获取锁,这样可以使所有线程都有机会获取锁并进入运行状态,从而避免了一些线程一直处于等待状态。
因此,在多线程编程中,如果需要唤醒所有等待该对象的线程,则可以使用notifyAll()方法;如果只需要随机唤醒一个等待该对象的线程,则可以使用notify()方法。
完!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix