Object methods in Java include: 1. [getClass()] is a public method; 2. [hashCode()] is a public method that can be called directly through the object; 3. [equals() ] Used to compare whether the current object and the target object are equal.
Related free learning recommendations: java basic tutorial
The object methods in java are:
1. Introduction
Object is the base class of all classes in java and is the top of the entire class inheritance structure. It is also the most abstract class. Everyone uses toString(), equals(), hashCode(), wait(), notify(), getClass() and other methods every day. Maybe they don’t realize that they are methods of Object, and they don’t look at what other methods Object has. And think about why these methods should be placed in Object. This article discusses the specific functions of each method, rewriting rules, and some of my own understanding.
2. Detailed explanation of Object method
Object contains: registerNatives(), getClass(), hashCode(), equals(), clone(), toString(), notify(), notifyAll (), wait(long), wait(long,int), wait(), and finalize(), a total of twelve methods. This order is listed in the order in which the methods are defined in the Object class. I will also explain them in this order below.
1.1, registerNatives()
public class Object { private static native void registerNatives(); static { registerNatives(); } }
What the hell? Hahaha, I just saw this method and was confused. From the name, I understand that this method is to register a native method (local method, implemented by JVM, the bottom layer is implemented by C/C). Who should you register it with? Of course, it is to the JVM. When a program calls a native method, the JVM can find these underlying methods and call them.
Native method in Object, and use registerNatives() to register with the JVM. (This belongs to the category of JNI. Jiulong is not aware of it yet. If you are interested, you can check it out by yourself.)
static JNINativeMethod methods[] = { {"hashCode", "()I", (void *)&JVM_IHashCode}, {"wait", "(J)V", (void *)&JVM_MonitorWait}, {"notify", "()V", (void *)&JVM_MonitorNotify}, {"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll}, {"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone}, };
Why should we use static methods and put them in static blocks?
We know that when a class is initialized, the class variables from the parent class to this class and the class variables and methods in the class initialization block will be placed in the < clinit> method in the order of definition, this can ensure The class variables and methods of the parent class must be initialized before the subclass. Therefore, when a subclass calls the corresponding native method, such as calculating hashCode, it is guaranteed to be able to call the JVM's native method.
1.2. getClass()
public final native Class getClass(): This is a public method that we can call directly through the object.
The first stage of class loading. Class loading is the process of loading the .class file into memory and generating a java.lang.Class object. The getClass() method is to obtain this object, which is a collection of all information about the runtime class of the object of the current class. This method is one of the three reflection methods.
1.2.1. Three reflection methods:
getClass() of the object;
Class name.class;
Class.forName(); class extends ObjectTest { private void privateTest(String str) { System.out.println(str); } public void say(String str) { System.out.println(str); } } public class ObjectTest { public static void main(String[] args) throws Exception { ObjectTest = new (); //获取对象运行的Class对象 Class<? extends ObjectTest> aClass = .getClass(); System.out.println(aClass); //getDeclaredMethod这个方法可以获取所有的方法,包括私有方法 Method privateTest = aClass.getDeclaredMethod("privateTest", String.class); //取消java访问修饰符限制。 privateTest.setAccessible(true); privateTest.invoke(aClass.newInstance(), "private method test"); //getMethod只能获取public方法 Method say = aClass.getMethod("say", String.class); say.invoke(aClass.newInstance(), "Hello World"); } } //输出结果: //class test. //private method test //Hello World
Reflection is mainly used to obtain Runtime information can make a static language like Java dynamic. You can assign a child object to a reference of the parent class when writing code. All the information of the runtime object can be obtained through reflection at runtime, that is, polymorphic. reflect. There is still a lot of knowledge about reflection, so I won’t go into details here.
1.3. hashCode()
public native int hashCode(); This is a public method, so subclasses can override it. This method returns the hashCode value of the current object, which is a number in the integer range (-2^31 ~ 2^31 - 1).
There are the following constraints for hashCode
During the execution of a Java application, when the hashCode method is called multiple times on the same object, the same integer must be returned consistently, provided that the object is The information used in equals comparison has not been modified;
If the x.equals(y) method of two objects returns true, the hashCode of the two objects x and y must be equal.
If the x.equals(y) method of two objects returns false, the hashCode of the two objects x and y can be equal or different. However, generating different integer results for unequal objects can improve hash table performance.
The default hashCode is the hash value converted from the memory address. After rewriting, it is a customized calculation method; the original hashCode can also be returned through System.identityHashCode(Object).
public class HashCodeTest { private int age; private String name; @Override public int hashCode() { Object[] a = Stream.of(age, name).toArray(); int result = 1; for (Object element : a) { result = 31 * result + (element == null ? 0 : element.hashCode()); } return result; } }
It is recommended to use the Objects.hash(Object... values) method. I believe that when you look at the source code, you will see that 31 is used as the basic multiplier to calculate hashCode. Why is 31 used? I agree and understand result * 31 = (result<<5) - result. The bottom layer of the JVM can automatically optimize bit operations, which is very efficient; and because the hashCode calculated by 31 has fewer conflicts, it is conducive to the distribution of hash bucket bits.
1.4, equals()
public boolean equals(Object obj); is used to compare whether the current object and the target object are equal. The default is to compare whether the reference points to the same object. It is a public method and can be overridden by subclasses.
public class Object{ public boolean equals(Object obj) { return (this == obj); } }
Why do we need to override the equals method?
因为如果不重写equals方法,当将自定义对象放到map或者set中时;如果这时两个对象的hashCode相同,就会调用equals方法进行比较,这个时候会调用Object中默认的equals方法,而默认的equals方法只是比较了两个对象的引用是否指向了同一个对象,显然大多数时候都不会指向,这样就会将重复对象存入map或者set中。这就破坏了map与set不能存储重复对象的特性,会造成内存溢出。
重写equals方法的几条约定:
自反性:即x.equals(x)返回true,x不为null;
对称性:即x.equals(y)与y.equals(x)的结果相同,x与y不为null;
传递性:即x.equals(y)结果为true, y.equals(z)结果为true,则x.equals(z)结果也必须为true;
一致性:即x.equals(y)返回true或false,在未更改equals方法使用的参数条件下,多次调用返回的结果也必须一致。x与y不为null。
如果x不为null, x.equals(null)返回false。
我们根据上述规则来重写equals方法。
public class EqualsTest{ private int age; private String name; //省略get、set、构造函数等 @Override public boolean equals(Object o) { //先判断是否为同一对象 if (this == o) { return true; } //再判断目标对象是否是当前类及子类的实例对象 //注意:instanceof包括了判断为null的情况,如果o为null,则返回false if (!(o instanceof )) { return false; } that = () o; return age == that.age && Objects.equals(name, that.name); } public static void main(String[] args) throws Exception { EqualsTest1 equalsTest1 = new EqualsTest1(23, "9龙"); EqualsTest1 equalsTest12 = new EqualsTest1(23, "9龙"); EqualsTest1 equalsTest13 = new EqualsTest1(23, "9龙"); System.out.println("-----------自反性----------"); System.out.println(equalsTest1.equals(equalsTest1)); System.out.println("-----------对称性----------"); System.out.println(equalsTest12.equals(equalsTest1)); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println("-----------传递性----------"); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println(equalsTest12.equals(equalsTest13)); System.out.println(equalsTest1.equals(equalsTest13)); System.out.println("-----------一致性----------"); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println("-----目标对象为null情况----"); System.out.println(equalsTest1.equals(null)); } } //输出结果 //-----------自反性---------- //true //-----------对称性---------- //true //true //-----------传递性---------- //true //true //true //-----------一致性---------- //true //true //-----目标对象为null情况---- //false
从以上输出结果验证了我们的重写规定是正确的。
注意:instanceof 关键字已经帮我们做了目标对象为null返回false,我们就不用再去显示判断了。
建议equals及hashCode两个方法,需要重写时,两个都要重写,一般都是将自定义对象放至Set中,或者Map中的key时,需要重写这两个方法。
1.4、clone()
protected native Object clone() throws CloneNotSupportedException;
此方法返回当前对象的一个副本。
这是一个protected方法,提供给子类重写。但需要实现Cloneable接口,这是一个标记接口,如果没有实现,当调用object.clone()方法,会抛出CloneNotSupportedException。
public class CloneTest implements Cloneable { private int age; private String name; //省略get、set、构造函数等 @Override protected CloneTest clone() throws CloneNotSupportedException { return (CloneTest) super.clone(); } public static void main(String[] args) throws CloneNotSupportedException { CloneTest cloneTest = new CloneTest(23, "9龙"); CloneTest clone = cloneTest.clone(); System.out.println(clone == cloneTest); System.out.println(cloneTest.getAge()==clone.getAge()); System.out.println(cloneTest.getName()==clone.getName()); } } //输出结果 //false //true //true
从输出我们看见,clone的对象是一个新的对象;但原对象与clone对象的String类型的name却是同一个引用,这表明,super.clone方法对成员变量如果是引用类型,进行是浅拷贝。
那什么是浅拷贝?对应的深拷贝?
浅拷贝:拷贝的是引用。
深拷贝:新开辟内存空间,进行值拷贝。
那如果我们要进行深拷贝怎么办呢?看下面的例子。
class Person implements Cloneable{ private int age; private String name; //省略get、set、构造函数等 @Override protected Person clone() throws CloneNotSupportedException { Person person = (Person) super.clone(); //name通过new开辟内存空间 person.name = new String(name); return person; } } public class CloneTest implements Cloneable { private int age; private String name; //增加了person成员变量 private Person person; //省略get、set、构造函数等 @Override protected CloneTest clone() throws CloneNotSupportedException { CloneTest clone = (CloneTest) super.clone(); clone.person = person.clone(); return clone; } public static void main(String[] args) throws CloneNotSupportedException { CloneTest cloneTest = new CloneTest(23, "9龙"); Person person = new Person(22, "路飞"); cloneTest.setPerson(person); CloneTest clone = cloneTest.clone(); System.out.println(clone == cloneTest); System.out.println(cloneTest.getAge() == clone.getAge()); System.out.println(cloneTest.getName() == clone.getName()); Person clonePerson = clone.getPerson(); System.out.println(person == clonePerson); System.out.println(person.getName() == clonePerson.getName()); } } //输出结果 //false //true //true //false //false
可以看到,即使成员变量是引用类型,我们也实现了深拷贝。如果成员变量是引用类型,想实现深拷贝,则成员变量也要实现Cloneable接口,重写clone方法。
1.5、toString()
public String toString();这是一个public方法,子类可重写,建议所有子类都重写toString方法,默认的toString方法,只是将当前类的全限定性类名+@+十六进制的hashCode值。
public class Object{ public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } }
我们思考一下为什么需要toString方法?
我这么理解的,返回当前对象的字符串表示,可以将其打印方便查看对象的信息,方便记录日志信息提供调试。
我们可以选择需要表示的重要信息重写到toString方法中。为什么Object的toString方法只记录类名跟内存地址呢?因为Object没有其他信息了,哈哈哈。
1.6、wait()/ wait(long)/ waite(long,int)
这三个方法是用来线程间通信用的,作用是阻塞当前线程,等待其他线程调用notify()/notifyAll()方法将其唤醒。这些方法都是public final的,不可被重写。
注意:
此方法只能在当前线程获取到对象的锁监视器之后才能调用,否则会抛出IllegalMonitorStateException异常。
调用wait方法,线程会将锁监视器进行释放;而Thread.sleep,Thread.yield()并不会释放锁。
wait方法会一直阻塞,直到其他线程调用当前对象的notify()/notifyAll()方法将其唤醒;而wait(long)是等待给定超时时间内(单位毫秒),如果还没有调用notify()/nofiyAll()会自动唤醒;waite(long,int)如果第二个参数大于0并且小于999999,则第一个参数+1作为超时时间;
public final void wait() throws InterruptedException { wait(0); } 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); }
1.7、notify()/notifyAll()
前面说了,如果当前线程获得了当前对象锁,调用wait方法,将锁释放并阻塞;这时另一个线程获取到了此对象锁,并调用此对象的notify()/notifyAll()方法将之前的线程唤醒。这些方法都是public final的,不可被重写。
public final native void notify(); 随机唤醒之前在当前对象上调用wait方法的一个线程
public final native void notifyAll(); 唤醒所有之前在当前对象上调用wait方法的线程
下面我们使用wait()、notify()展示线程间通信。假设9龙有一个账户,只要9龙一发工资,就被女朋友给取走了。
//账户 public class Account { private String accountNo; private double balance; private boolean flag = false; public Account() { } public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } /** * 取钱方法 * * @param drawAmount 取款金额 */ public synchronized void draw(double drawAmount) { try { if (!flag) { //如果flag为false,表明账户还没有存入钱,取钱方法阻塞 wait(); } else { //执行取钱操作 System.out.println(Thread.currentThread().getName() + " 取钱" + drawAmount); balance -= drawAmount; //标识账户已没钱 flag = false; //唤醒其他线程 notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void deposit(double depositAmount) { try { if (flag) { //如果flag为true,表明账户已经存入钱,取钱方法阻塞 wait(); } else { //存钱操作 System.out.println(Thread.currentThread().getName() + " 存钱" + depositAmount); balance += depositAmount; //标识账户已存入钱 flag = true; //唤醒其他线程 notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } } //取钱者 public class DrawThread extends Thread { private Account account; private double drawAmount; public DrawThread(String name, Account account, double drawAmount) { super(name); this.account = account; this.drawAmount = drawAmount; } @Override public void run() { //循环6次取钱 for (int i = 0; i < 6; i++) { account.draw(drawAmount); } } } //存钱者 public class DepositThread extends Thread { private Account account; private double depositAmount; public DepositThread(String name, Account account, double depositAmount) { super(name); this.account = account; this.depositAmount = depositAmount; } @Override public void run() { //循环6次存钱操作 for (int i = 0; i < 6; i++) { account.deposit(depositAmount); } } } //测试 public class DrawTest { public static void main(String[] args) { Account brady = new Account("9龙", 0); new DrawThread("女票", brady, 10).start(); new DepositThread("公司", brady, 10).start(); } } //输出结果 //公司 存钱10.0 //女票 取钱10.0 //公司 存钱10.0 //女票 取钱10.0 //公司 存钱10.0 //女票 取钱10.0
例子中我们通过一个boolean变量来判断账户是否有钱,当取钱线程来判断如果账户没钱,就会调用wait方法将此线程进行阻塞;这时候存钱线程判断到账户没钱, 就会将钱存入账户,并且调用notify()方法通知被阻塞的线程,并更改标志;取钱线程收到通知后,再次获取到cpu的调度就可以进行取钱。反复更改标志,通过调用wait与notify()进行线程间通信。实际中我们会时候生产者消费者队列会更简单。
注意:调用notify()后,阻塞线程被唤醒,可以参与锁的竞争,但可能调用notify()方法的线程还要继续做其他事,锁并未释放,所以我们看到的结果是,无论notify()是在方法一开始调用,还是最后调用,阻塞线程都要等待当前线程结束才能开始。
为什么wait()/notify()方法要放到Object中呢?
因为每个对象都可以成为锁监视器对象,所以放到Object中,可以直接使用。
1.8、finalize()
protected void finalize() throws Throwable ;
此方法是在垃圾回收之前,JVM会调用此方法来清理资源。此方法可能会将对象重新置为可达状态,导致JVM无法进行垃圾回收。
我们知道java相对于C++很大的优势是程序员不用手动管理内存,内存由jvm管理;如果我们的引用对象在堆中没有引用指向他们时,当内存不足时,JVM会自动将这些对象进行回收释放内存,这就是我们常说的垃圾回收。但垃圾回收没有讲述的这么简单。
finalize()方法具有如下4个特点:
永远不要主动调用某个对象的finalize()方法,该方法由垃圾回收机制自己调用;
finalize()何时被调用,是否被调用具有不确定性;
当JVM执行可恢复对象的finalize()可能会将此对象重新变为可达状态;
当JVM执行finalize()方法时出现异常,垃圾回收机制不会报告异常,程序继续执行。
public class FinalizeTest { private static FinalizeTest ft = null; public void info(){ System.out.println("测试资源清理得finalize方法"); } public static void main(String[] args) { //创建FinalizeTest对象立即进入可恢复状态 new FinalizeTest(); //通知系统进行垃圾回收 System.gc(); //强制回收机制调用可恢复对象的finalize()方法 // Runtime.getRuntime().runFinalization(); System.runFinalization(); ft.info(); } @Override public void finalize(){ //让ft引用到试图回收的可恢复对象,即可恢复对象重新变成可达 ft = this; throw new RuntimeException("出异常了,你管不管啊"); } } //输出结果 //测试资源清理得finalize方法
我们看到,finalize()方法将可恢复对象置为了可达对象,并且在finalize中抛出异常,都没有任何信息,被忽略了。
1.8.1、对象在内存中的状态
对象在内存中存在三种状态:
可达状态:有引用指向,这种对象为可达状态;
可恢复状态:失去引用,这种对象称为可恢复状态;垃圾回收机制开始回收时,回调用可恢复状态对象的finalize()方法(如果此方法让此对象重新获得引用,就会变为可达状态,否则,会变为不可大状态)。
不可达状态:彻底失去引用,这种状态称为不可达状态,如果垃圾回收机制这时开始回收,就会将这种状态的对象回收掉。
1.8.2、垃圾回收机制
垃圾回收机制只负责回收堆内存种的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源);
程序无法精确控制垃圾回收的运行,垃圾回收只会在合适的时候进行。当对象为不可达状态时,系统会在合适的时候回收它的内存。
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能会将对象置为可达状态,导致垃圾回收机制取消回收。
1.8.3、强制垃圾回收
上面我们已经说了,当对象失去引用时,会变为可恢复状态,但垃圾回收机制什么时候运行,什么时候调用finalize方法无法知道。虽然垃圾回收机制无法精准控制,但java还是提供了方法可以建议JVM进行垃圾回收,至于是否回收,这取决于虚拟机。但似乎可以看到一些效果。
public class GcTest { public static void main(String[] args){ for(int i=0;i<4;i++){ //没有引用指向这些对象,所以为可恢复状态 new GcTest(); //强制JVM进行垃圾回收(这只是建议JVM) System.gc(); //Runtime.getRuntime().gc(); } } @Override public void finalize(){ System.out.println("系统正在清理GcTest资源。。。。"); } } //输出结果 //系统正在清理GcTest资源。。。。 //系统正在清理GcTest资源。。。。
System.gc(),Runtime.getRuntime().gc()两个方法作用一样的,都是建议JVM垃圾回收,但不一定回收,多运行几次,结果可能都不一致。
The above is the detailed content of What are the object methods in java. For more information, please follow other related articles on the PHP Chinese website!