java中的hashcode、equals和toString方法都是基类Object的方法。

首先说说toString方法,简单的总结了下API说明就是:返回该对象的字符串表示,信息应该是简明但易于读懂的信息表达式,一般来说大部分的常见类都会重写这个方法。比如Collection会重写的该方法就会在集合前后加上[ ]中间的元素中间会加上。但是如果没有重写的话Object的默认的toString方法是返回:getClass().getName() + '@' + Integer.toHexString(hashCode()) 即这个类的实例的类名+“@”+hashcode的16进制表示。这里就引进了hashcode的概念了。

hashcode方法是返回哈希码的一个方法,这里哈希码可以认为是地址的概念,用处是在某些时候加快哈希表的性能。因此在涉及到哈希表的操作的时候就可能设计到hashcode的调用。

equals方法是比较两个类,默认的效果是和==一样的,即比较引用(地址)看是不是同一个元素,但是因为大部分情况下比较两个元素是不是同一个元素是没有意义的所以会重写这个方法。比如:String会重写为判断两个字符串是不是相同,而自定义的复杂类更要自己定义比较的规则。

而在集合的hashset的时候,因为set中不会存储相同的元素,而是否相同也是你判断的。为了方便,内部的规则是先判断hashcode,如果hashcode不同的话就直接存入。如果hashcode相同的再判断equals是不是true。

所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。

我们针对例子分析。

 1 package TestCollection;
2
3 import java.util.Collection;
4 import java.util.HashSet;
5 import java.util.LinkedList;
6 import java.util.TreeSet;
7
8 import pack.SOP;
9
10 public class TestCollection {
11
12 /**
13 * @param args
14 */
15 public static void main(String[] args) {
16 // TODO Auto-generated method stub
17 Collection c = new HashSet();
18 c.add(new Integer(100));
19 c.add("abcdefg");
20 c.add("abcdefg");
21 c.add(new Cat("米米",3));
22 c.add(new Cat("米米",3));
23 SOP.sop(c.size());
24 SOP.sop("==================");
25 SOP.sop(c);
26
27 }
28
29 }
30
31 class Cat {
32 String name;
33 int age;
34 Cat(){
35
36 }
37 Cat(String name,int age){
38 this.name = name;
39 this.age =age;
40 }
41 public boolean equals(Object obj){
42 if(obj instanceof Cat){
43 Cat c = (Cat)obj;
44 SOP.sop("==================");
45 return false;
46 // return (this.name.equals(c.name))&&(this.age==c.age) ;
47 }
48 return super.equals(obj);
49
50 }
51 public int hashCode() {
52 //return name.hashCode();
53 return 0x68888;
54 }
55 /*public String toString() {
56 //return "是这样吗";
57 return "name :"+name+"age :"+age;
58 }*/
59 }

向hashset中添加元素,hashset有个规则是不存储相同的元素,而相同与否是由各自的规则判断的,比如插入字符串的时候,String的equals规则是只要指向的内容相同就true。所以两个相同的“abcdefg”就只有一个。

接下来就是插入自定义的类了。

首先需要自定义toString方法让print的时候按照自己的需求显示。
重写equals方法和hashcode方法进行是不是相同元素的判断。

然后做如下的测试证明原理的正确性:

首先不写hashcode方法,则即使equals判断了内容规则了,但是set中会存在相同显示的两个元素,这是因为虚拟机会先判断hashCode方法,因为没有写hashCode方法的话会继承父类的hashCode方法,所以两个对象的hashcode值不一样,所以这里是不会判断equals方法就直接认定两个元素是不一样的,即使你“看上去是一样的”。

然后重写了hashcode方法,如果定义了固定了int值,则Object的默认的toString会把你定义的hashcode值显示出来。

然后如果有了相同的hashcode的话就接着判断equals方法,如果自己定义让返回的值始终是false的话,也会两个都显示出来,因为虚拟机会认为他们是两个元素。只有hashcode和equals都一样的话,才会认为是两个相同的元素。插入的时候,只插入一个。

最后得出结论:虚拟机判断两个元素是不是相同首先判断两个元素的hashcode,如果hashcode不同,则直接认为不同;如果hashcode相同,再判断equals方法是不是相同,如果equals不同,则两个元素不同,如果equals相同,则才会最终认为元素相同。因为很多系统的类实现了这些方法,因此我们在自定义的时候可以拿来调用系统类的这些方法。

第6个方法:

  1. public String toString() {
  2. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  3. }

该方法为被public修饰,所有对象都可见。

该方法返回对象的字符串表示形式,开发人员能够清晰的看到一个对象的各种属性。在官方建议上,建议所有的方法都重写这个方法。Eclipse有代码生成器,可以方便的重写该方

法。

看方法体的内容,可以知道,最终返回getClass().getName() + ' @' + Integer.toHexString(hashCode())。下面用一个例子看一下:

举例:

  1. package edu.java.test;
  2. public class TestToString {
  3. public static void main(String[] args) {
  4. TestClone tc = new TestClone();
  5. tc.setName("my name is java");
  6. tc.setAge("70 yeas");
  7. System.out.println("未重写之前的toString方法:"+tc.getClass().getName()+"@"+Integer.toHexString(tc.hashCode()));
  8. System.out.println("重写之后的toString方法:"+tc.toString());
  9. }
  10. }

上面示例了两个打印语句,一个是原生态的toString方法,一个是重写后的toString方法。下面是结果:

  1. 未重写之前的toString方法:edu.java.test.TestClone@1e57e8f
  2. 重写之后的toString方法:TestClone [name=my name is java, age=70 yeas]

可以看出,原生态的toString方法,不能很好的显示出对象的属性,所以在toString源码里,java开发人员建议每个类都应该重写toString方法的原因。下面是重写后的toString方法。

  1. @Override
  2. public String toString() {
  3. return "TestClone [name=" + name + ", age=" + age + "]";
  4. }

第7个方法:wait()

  1. public final void wait() throws InterruptedException {
  2. wait(0);
  3. }

该方法会导致当前线程等待,直到另一个线程调用notify()或者notifyAll()方法或者是设定的等待时间过去。

当前线程必须拥有此对象监视器。如果线程等待,有以下四种方式可以解除等待。

第一种方式:其他线程调用notify()方法,然后该线程刚好被唤醒。

第二种方式:其他线程中断。

第三种方式:其他线程调用notifyAll()方法。

第四种方式:超时时间为0。

一旦发生上面四件事情,就会把该线程从等待设置中删除。

第8个方法:notify()

  1. public final native void notify();

notify本意在中文中是通知的意思。该方法由final和native修饰,表明该方法是依赖于本地,不被java实现,而且不能被子类重写。

这个方法主要是唤醒一个等待中的线程。而且,这个唤醒后的线程和其他正常活跃的线程一样。

在这里要提一下wait方法,wait方法是使当前线程等待,而notify则是唤醒,而且当前运行的线程必须具有该对象的对象监视器,监视器的获得有三种方式:

第一种方式:通过执行该对象的同步的方法。在此大致介绍一下同步,同步就相当于给一个对象上了一把锁,在一段时间内,这个对象只能被一个线程访问,其他任何线程都不能进入。比如说,我现在进入了我家,然后把门锁了,这个时候其他任何没有钥匙的人都进不来。屋里面的东西只能自己动。

synchronized(object)。

第二种方式:在同步代码块中执行。

synchronized void method(){}。

第三种方式:执行一个Class类型的同步静态方法。

而这三种方式都必须要求当前线程拥有对象的监视权。

第9个方法:notifyAll();

唤醒所有线程,可能是按照线程等待时间倒叙唤醒。

以上3个方法举例:

  1. package edu.java.test;
  2. import java.text.DateFormat;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Calendar;
  5. public class TestNotify01 {
  6. private Object monitor = new Object();
  7. DateFormat format = new SimpleDateFormat("yyyy-MM-dd:hh:mm:ss");
  8. private String getTime(){
  9. return format.format(Calendar.getInstance().getTime());
  10. }
  11. /**
  12. * 0.首先获得对象的监视器
  13. * 1.打印等待前后线程的名称和等待时间
  14. * 2.线程开启
  15. * @param thread
  16. * @param ms
  17. */
  18. public void waitOnce(String thread, final Long ms) {
  19. Thread waitThread = new Thread() {
  20. public void run() {
  21. // 对对象上锁,获得对象的监视器,用的第二种方式,对同步代码块进行上锁
  22. synchronized (monitor) {
  23. try {
  24. System.out.println("Thread等待之前 " + Thread.currentThread().getName() + " Wait at" + getTime());
  25. monitor.wait(ms);
  26. System.out.println("Thread等待之后 " + Thread.currentThread().getName() + " Wait at" + getTime());
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. };
  33. waitThread.setName(thread);
  34. waitThread.start();
  35. }
  36. /**
  37. * 0.获得对象的监视器
  38. * 1.唤醒一个线程
  39. * 2.睡眠两次
  40. * 3.开启线程
  41. * @param thread
  42. * @param ms
  43. */
  44. public void awake(String thread, final Long ms) {
  45. Thread notifyThread = new Thread() {
  46. public void run() {
  47. // 对对象上锁,获得对象的监视器,用的第一种方式
  48. synchronized (monitor) {
  49. monitor.notify();
  50. System.out.println("Thread" + Thread.currentThread().getName() + " 唤醒 at " + getTime());
  51. try {
  52. Thread.sleep(ms);
  53. } catch (InterruptedException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. try {
  58. Thread.sleep(ms);
  59. } catch (InterruptedException e) {
  60. e.printStackTrace();
  61. }
  62. };
  63. };
  64. notifyThread.setName(thread);
  65. notifyThread.start();
  66. }
  67. /**
  68. * 唤醒全部线程
  69. * @param thread
  70. */
  71. public void awakeAll(String thread) {
  72. Thread notifyThread = new Thread() {
  73. public void run() {
  74. // 对对象上锁,获得对象的监视器,用的第一种方式
  75. synchronized (monitor) {
  76. monitor.notifyAll();
  77. System.out.println("Thread" + Thread.currentThread().getName() + " 唤醒 at " + getTime());
  78. }
  79. };
  80. };
  81. notifyThread.setName(thread);
  82. notifyThread.start();
  83. }
  84. public static void main(String[] args) {
  85. /**
  86. * 0.首先建立了一个对象
  87. * 1.然后开启三个等待中的线程
  88. * 2.睡眠两秒后唤醒一个线程,从线程的名称中可以看出,唤醒线程的顺序可能是按照线程开启时间来的.
  89. */
  90. TestNotify01 test = new TestNotify01();
  91. test.waitOnce("1",Long.MAX_VALUE);
  92. test.waitOnce("2",Long.MAX_VALUE);
  93. test.waitOnce("3",Long.MAX_VALUE);
  94. try {
  95. Thread.sleep(2000);
  96. } catch (InterruptedException e) {
  97. e.printStackTrace();
  98. }
  99. test.awake("100",2000L);
  100. //test.awakeAll("100");
  101. }
  102. }

这个测试主要是先等待,然后再唤醒,可以从结果直观的感受一下:

  1. Thread等待之前 1 Wait at2016-07-15:12:28:19
  2. Thread等待之前 2 Wait at2016-07-15:12:28:19
  3. Thread等待之前 3 Wait at2016-07-15:12:28:19
  4. Thread100 唤醒 at 2016-07-15:12:28:21
  5. Thread等待之后 1 Wait at2016-07-15:12:28:23

先等待3个线程,然后2S后唤醒,按照顺序唤醒。接下来我们看一下如果是唤醒全部,会是什么结果:

  1. Thread等待之前 1 Wait at2016-07-15:12:29:35
  2. Thread等待之前 2 Wait at2016-07-15:12:29:35
  3. Thread等待之前 3 Wait at2016-07-15:12:29:35
  4. Thread100 唤醒 at 2016-07-15:12:29:37
  5. Thread等待之后 3 Wait at2016-07-15:12:29:37
  6. Thread等待之后 2 Wait at2016-07-15:12:29:37
  7. Thread等待之后 1 Wait at2016-07-15:12:29:37

可以看出,唤醒是倒叙唤醒的。

第10个方法:

  1. public final native void wait(long timeout) throws InterruptedException;

这个方法就是设置时间唤醒,一段时间过后唤醒,上面的wait()方法,其实就是这个方法里的参数设置为0,也就是理解唤醒。

第11个方法:

  1. public final void wait(long timeout, int nanos) throws InterruptedException {
  2. if (timeout < 0) {
  3. throw new IllegalArgumentException("timeout value is negative");
  4. }
  5. if (nanos < 0 || nanos > 999999) {
  6. throw new IllegalArgumentException(
  7. "nanosecond timeout value out of range");
  8. }
  9. if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
  10. timeout++;
  11. }
  12. wait(timeout);
  13. }

这个方法和wait()方法很类似,不过它能够更精确的控制时间,并且抛出对应的异常。

如果超时时间<0,就会抛出超时时间为负的异常,因为时间必须>=0。

如果时间最小单位超过999999,就会抛出纳秒时间超出范围异常。

第12个方法:

  1. protected void finalize() throws Throwable { }

Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前被自动调用的。
垃圾收集器只知道释放那些由new分配的内存,所以不知道如何释放对象的“特殊”内存。为解决这个问题,Java提供了一个名为finalize()的方法,它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作(如关闭流等操作)。但JVM(Java虚拟机)不保证此方法总被调用。

【Java基础之Object类(一)】Java中Object类中的所有方法(toString、equals、hashCode、clone、finalize、wait和notify等)详解(转载)的更多相关文章

  1. Java基础:三步学会Java Socket编程

    Java基础:三步学会Java Socket编程 http://tech.163.com 2006-04-10 09:17:18 来源: java-cn 网友评论11 条 论坛        第一步 ...

  2. Java 反射 设计模式 动态代理机制详解 [ 转载 ]

    Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...

  3. 如约而至,Java 10 正式发布! Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十四)Redis缓存正确的使用姿势 努力的孩子运气不会太差,跌宕的人生定当更加精彩 优先队列详解(转载)

    如约而至,Java 10 正式发布!   3 月 20 日,Oracle 宣布 Java 10 正式发布. 官方已提供下载:http://www.oracle.com/technetwork/java ...

  4. java基础课程笔记 static 主函数 静态工具类 classpath java文档注释 静态代码块 对象初始化过程 设计模式 继承 子父类中的函数 继承中的构造函数 对象转型 多态 封装 抽象类 final 接口 包 jar包

    Static那些事儿 Static关键字 被static修饰的变量成为静态变量(类变量) 作用:是一个修饰符,用于修饰成员(成员变量,成员方法) 1.被static修饰后的成员变量只有一份 2.当成员 ...

  5. Java基础(42):Java中主类中定义方法加static和不加static的区别(前者可以省略类名直接在主方法调用,后者必须先实例化后用实例调用)

    package lsg.ap.april4th2; /* 知识点:1.Getter and Setter 的应用 2.局部变量与成员变量(也可叫做全局变量) 3.Static关键字的用法 a.成员变量 ...

  6. java基础知识(五)java类

    类是java的核心和本质,是java语言的基础. 一.java中的类 1.类class 一个类可以包含局部变量.实例变量.类变量(静态变量) 一个类至少有一个构造方法,如果没有,系统会默认给出一个无参 ...

  7. Java基础15:深入剖析Java枚举类

    更多内容请关注微信公众号[Java技术江湖] 这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux ...

  8. Java基础之序列化对象——将对象写入到文件中(SerializeObjects)

    控制台程序. 首先定义一个含有任意不同数据类型域的可序列化类: import java.io.Serializable; public class Junk implements Serializab ...

  9. Java基础学习总结(49)——Excel导入导出工具类

    在项目的pom文件中引入 <dependency> <groupId>net.sourceforge.jexcelapi</groupId> <artifac ...

  10. 夯实Java基础(十五)——Java中Comparable和Comparator

    1.前言 对于Java中的对象,我们只能使用基本运算符==.!=来判断一下地址是否相等,不能使用>.<来比较大小.但是在实际的开发中,我们需要对对象进行排序,也就是比较大小,那么应该如何实 ...

随机推荐

  1. Entity Framework 不支持DefaultValue

    http://stackoverflow.com/questions/18506088/entityframework-not-updating-column-with-default-value Y ...

  2. Django中的分页

    直接看代码吧,还算比较简单: 先确认数据量有多少 根据页面显示数据的多少来分割数据,得到页面的开始数据和结束数据 根据开始和截止数据去切片数据,并且得到总共的页码数 根据一页显示多少页码和当前页码数, ...

  3. HDU 2098 分拆素数和

    HDU 2098 分拆素数和 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768K (Java/Others) [题目描述 ...

  4. DataGridView常用操作

    一.DataGridView列右击菜单事件处理 (1). 添加一个快捷菜单contextMenuStrip1:(2). 给dataGridView1的CellMouseDown事件添加处理程序: pr ...

  5. ubuntu下配置hosts

    由于Chrome浏览器访问问题,需要配置hosts. 在Ubuntu系统下,需要修改/etc/hosts文件,修改完之后要重启网络.具体过程如下:1.修改hostssudo vi /etc/hosts ...

  6. poj1859The Perfect Symmetry

    链接 按x或y排序,假如有对称点的话,头尾相对. #include <iostream> #include<cstdio> #include<cstring> #i ...

  7. Vnc viewer与windows之间的复制粘贴

    用VNC连接到Linux之后,最纠结的问题就是无法复制粘贴.其实很简单,在Linux里面,打开一个终端,然后输入命令: vncconfig 之后,会弹出一个窗口 不要关闭那个小窗口 之后,就可以愉快的 ...

  8. 【Todo】单例模式各种实现方式及并发安全

    Java 40道面试题不错:http://www.tuicool.com/articles/VRVFZb 其中有一道题目: 单例模式的线程安全性 老生常谈的问题了,首先要说的是单例模式的线程安全意味着 ...

  9. JavaWeb 6 Http

    6 Http 2 Http协议入门        2.1 什么是http协议                http协议: 对浏览器客户端 和  服务器端 之间数据传输的格式规范 2.2 查看http ...

  10. 设置TextView下划线并响应点击事件(SpannableString)

    下面是一个20行的完整Demo代码:基本原理是使用一个SpannableString并设置其ClickableSpan来响应点击事件. TextView useInfo = (TextView) fi ...