关于Object类(Java 10)中的方法,根据其所涉及的知识点,分为以下4个部分:html
2018-09-15java
今天写:equals、hashCode、toString、clone、getClass,后面的方法等学到相关知识再做补充。sql
1 public boolean equals(Object obj) { 2 return (this == obj); 3 }
equals用来判断两个对象是否”相等“,而对“相等”这个词的定义不一样,得出的结果也不一样:编程
要实现上面的需求,咱们就须要重写equals方法,以下:api
1 public class Point { 2 int x; 3 int y; 4 5 Point(int x, int y) { 6 this.x = x; 7 this.y = y; 8 } 9 10 @Override 11 public boolean equals(Object obj) { 12 if (this == obj) { 13 return true; 14 } 15 if (obj instanceof Point) { 16 Point p = (Point)obj; 17 return (this.x == p.x && this.y == p.y); 18 } 19 return false; 20 } 21 }
测试:安全
1 public static void main(String[] args) { 2 Point p1 = new Point(1, 1); 3 Point p2 = new Point(1, 1); 4 System.out.println("p1.equals(p2) : " + p1.equals(p2)); 5 p2.x = 2; 6 System.out.println("p1.equals(p2) : " + p1.equals(p2)); 7 } 8 /* 输出 9 p1.equals(p2) : true 10 p1.equals(p2) : false 11 */
另记:String类也重写了equals方法,实现了:只要两个字符串长度相等及字符串中对应位置的字符相等,则两字符串相等。能够参考String类中的equals方法。数据结构
注意:在重写equals方法时,方法定义要写为:public boolean equals(Object obj) {....} ,参数类型是:Object obj
并发
回到顶部oracle
1 public native int hashCode();
hashCode,官方解释:返回对象的一个哈希码,为基于哈希表的数据结构提供便利,如:HashMap等。ide
Object类中的hashCode方法为本地方法,在重写hashCode方法时,需遵循如下规则:
关于第2条规则,咱们继续Point类这个例子。首先,在未重写hashCode方法的状况下,咱们测试两个对象的hashCode()输出值:
1 public static void main(String[] args) { 2 Point p1 = new Point(9483, 89382); 3 Point p2 = new Point(9483, 89382); 4 System.out.println("p1.hashCode() : " + p1.hashCode()); 5 System.out.println("p2.hashCode() : " + p2.hashCode()); 6 } 7 /* 输出: 8 p1.hashCode() : 166239592 9 p2.hashCode() : 991505714 10 */
能够看到,在咱们定义的equals方法下相等的两个对象,获得的hashCode是不一样的,如此不一致会形成什么后果呢?咱们知道 HashMap 在存储<Key, Value>时,若是Key1等于Key2,那么存储的键值对为:<Key1, Value2>,即:只会存储一个Key,使用的是最新的Value。而 HashMap 中在判断 Key1是否等于Key2时,就使用到了它们的hashCode。在未重写hashCode方法的状况下,看以下测试:
1 public static void main(String[] args) { 2 Point p1 = new Point(9483, 89382); 3 Point p2 = new Point(9483, 89382); 4 5 HashMap<Point, Integer> map = new HashMap<Point, Integer>(); 6 map.put(p1, p1.hashCode()); 7 map.put(p2, p2.hashCode()); 8 for (Map.Entry<Point, Integer> m : map.entrySet()) { 9 System.out.println(m); 10 } 11 } 12 /* 输出 13 Point@9e89d68=166239592 14 Point@3b192d32=991505714 15 */
根据咱们对Point类相等的定义,p1与p2相等,而在 HashMap 中却存入了两个键值对,显然不符合咱们的意图。(equals与hashCode的不一致,会形成使用时产生歧义,从而致使意想不到的错误。因此,咱们在重写equals方法后,也要重写hashCode方法,使其意义一致)如今咱们来重写hashCode方法,再进行如上测试:
1 @Override 2 public int hashCode() { 3 return (x & y) | (x ^ y); 4 } 5 /* 输出 6 Point@17d2f=97583 7 */
根据咱们对hashCode方法的定义,对象的hashCode只与(x, y)相关,因此 p1.hashCode() == p2.hashCode() 为 true。这样一来,HashMap 中只会存入一个键值对,符合咱们的预期。
1 public String toString() { 2 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 3 }
源码中直接返回:对象类型名@对象hashCode的十六进制,举个例子:
1 public static void main(String[] args) { 2 Point p1 = new Point(9483, 89382); 3 System.out.println(p1.toString()); 4 } 5 /* 输出 6 Point@17d2f 7 */
不少状况下,咱们都要重写toString()方法,就好比Point类,咱们想知道的是点的横纵坐标(x, y),而不是 Point@17d2f 这串不知所云的字符。
1 @Override 2 public String toString() { 3 return "(" + x + ", " + y + ")"; 4 } 5 /* 输出 6 (9483, 89382) 7 */
1 protected native Object clone() throws CloneNotSupportedException;
从方法定义入手:
如今,咱们对以前的Point类进行部分修改,为了节省空间,我只贴出修改部分的代码:
首先,定义Data类,用来记录一个点所包含的相关信息;
1 public class Data { 2 int weight; 3 String name; 4 5 Data(int weight, String name) { 6 this.weight = weight; 7 this.name = name; 8 } 9 }
而后,Point类实现Cloneable接口,而且Point类中包含一个Data类型字段,以下:
1 public class Point implements Cloneable { 2 int x; 3 int y; 4 Data data; 5 6 Point(int x, int y, Data data) { 7 this.x = x; 8 this.y = y; 9 this.data = data; 10 } 11 ... 12 }
测试:
1 public static void main(String[] args) throws Exception { 2 Data data = new Data(20, "A"); 3 Point p1 = new Point(1, 2, data); 4 Point p2 = (Point)p1.clone(); 5 6 System.out.println("p1 == p2 : " + (p1 == p2)); 7 System.out.println("p1.(x, y) = " + p1.toString() + ", p2.(x, y) = " + p2.toString()); 8 System.out.println("p1.data == p2.data : " + (p1.data == p2.data)); 9 } 10 /* 输出 11 p1 == p2 : false 12 p1.(x, y) = (1, 2), p2.(x, y) = (1, 2) 13 p1.data == p2.data : true 14 */
对于测试的输出,咱们能够发现:
对于第3条,即Object类的clone方法是浅拷贝,理解如图:
在一些并发编程情景下,咱们经常须要操做 不可变对象 来保证并发安全性。不可变对象,顾名思义就是你建立的对象不会改变,你能够理解为:
(更详细的内容,能够参考《Java并发编程实战》)
如今,假如我要在并发环境下使用p1.clone()出来的对象p2,并要求p2是不可变的。而事实上,其余线程能够经过 p1.data 来改变 p2.data 的状态,以破坏p2的不可变性。
要想使p2不可变,咱们就须要对Point类进行深拷贝,即:对Piont类中的Data类型字段也建立一个新的对象,使得 p1.data != p2.data,以下:
1 public class Data { 2 ... 3 // 自定义的clone(),并不是重写Object类中的clone() 4 public Data clone() { 5 return new Data(weight, name); 6 } 7 } 8 public class Point implements Cloneable { 9 ... 10 @Override 11 protected Object clone() throws CloneNotSupportedException { 12 Point p = (Point)super.clone(); 13 p.data = data.clone(); // 这里的data.clone()与Object类中的clone()无关 14 return p; 15 } 16 ... 17 } 18 /* 重复上面的测试,输出: 19 p1 == p2 : false 20 p1.(x, y) = (1, 2), p2.(x, y) = (1, 2) 21 p1.data == p2.data : false 22 */
思考:若是一个类中一直嵌套着包含引用类型字段,那么咱们该怎么才能作到深拷贝呢?很明显,对于类中每个引用类型对象都作深拷贝。(递归处理)
1 public final native Class<?> getClass();
getClass方法,返回对象的类对象,在反射中常用,例如:
Data类中有个私有方法 printInfo(),该方法在Point类中没法正常调用,可是咱们能够经过反射机制来调用该方法。
1 public class Data { 2 ... 3 private void printInfo() { 4 System.out.println("weight = " + weight); 5 System.out.println("name : " + name); 6 } 7 } 8 // 在Point类中 9 public static void main(String[] args) throws Exception { 10 Data data = new Data(20, "A"); 11 Class<?> clz = data.getClass(); 12 Method m = clz.getDeclaredMethod("printInfo"); 13 m.setAccessible(true); // 抑制Java的访问控制检查 14 m.invoke(data); 15 } 16 /* 输出 17 weight = 20 18 name : A 19 */
这里只是简单提一下,更多关于反射的知识,会在后期总结。
2018-10-06
今天更新:wait系列、notify系列、finalize。
(我尽可能以简单清晰的方式来展示个人内容,对于涉及到的知识点,这里只是抛砖引玉,若想深刻研究,你们能够进一步去查阅资料)
来看字面意思的解释:
1000000*timeout+nanos)过去了;(注:线程被唤醒后,须要等待当前线程释放锁资源,而后与其余请求该锁的线程竞争,获取锁后才能获得执行)
字面解释就这些,下面要写的是我在阅读API(JavaSE 10 & JDK10)中该部份内容时的困惑及解答。先来看看这些方法的源码吧:
1 /************************** wait **************************/ 2 public final void wait() throws InterruptedException { 3 wait(0L); // 调用 wait(long timeout) 4 } 5 6 public final native void wait(long timeout) throws InterruptedException; 7 8 public final void wait(long timeout, int nanos) throws InterruptedException { 9 if (timeout < 0) { 10 throw new IllegalArgumentException("timeout value is negative"); 11 } 12 if (nanos < 0 || nanos > 999999) { 13 throw new IllegalArgumentException( 14 "nanosecond timeout value out of range"); 15 } 16 /* 17 timeout 是ms(毫秒)计时 18 nanos 是ns(纳秒)计时 19 本来我觉得某些领域须要更精确的时间控制,因此才提供wait(long timeout, int nanos)这个方法 20 而当我看到源码的时候,这不就多了个timeout++吗?这个判断和加法在外部也能够作啊。 21 因此,为何要有这个方法呢? - -?(这个问题这里不深刻讨论) 22 */ 23 if (nanos > 0) { 24 timeout++; 25 } 26 wait(timeout); // 调用 wait(long timeout) 27 } 28 29 /************************** notify ************************/ 30 @HotSpotIntrinsicCandidate 31 public final native void notify(); 32 33 @HotSpotIntrinsicCandidate 34 public final native void notifyAll();
在API中,能够看到wait的3个方法中都抛出如下异常:
1. IllegalMonitorStateException
若是当前线程不是该对象锁的持有者时,抛出该异常。如何理解呢,看下面的代码:
1 class T1 extends Thread { 2 3 @Override 4 public void run() { 5 try { 6 this.wait(1000); 7 System.out.println("wait over"); 8 } catch (InterruptedException e) {} 9 } 10 } 11 12 public class IllegalMonitorStateTest { 13 14 public static void main(String[] args) { 15 T1 t = new T1(); 16 t.start(); 17 } 18 } 19 20 /* 异常 21 Exception in thread "Thread-0" java.lang.IllegalMonitorStateException 22 at java.base/java.lang.Object.wait(Native Method) 23 at T1.run(IllegalMonitorStateTest.java:6) 24 */
抛出异常的缘由:[线程 t] 执行 this.wait(),但其并未获取 this锁。wait操做是要释放当前锁资源的,都没有获取如何释放呢?
官方给出的说明:在如下3种状况下,线程为对象锁的持有者:
因此,针对上面的例子,作如下修改(只贴出修改的部分),确保其不会抛出IllegalMonitorStateException异常。
1 @Override 2 public void run() { 3 synchronized (this) { 4 try { 5 this.wait(1000); 6 System.out.println("wait over"); 7 } catch (InterruptedException e) {} 8 } 9 } 10 /* 再进行测试,输出 11 wait over 12 */
2. InterruptedException
1 class T2 extends Thread { 2 T2(String name) { 3 super(name); 4 } 5 6 @Override 7 public void run() { 8 synchronized (this) { 9 try { 10 this.wait(); 11 System.out.println("wait over"); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 } 17 } 18 19 public class InterruptedExceptionTest { 20 public static void main(String[] args) throws Exception { 21 T2 t = new T2("[线程 t]"); 22 t.start(); 23 System.out.println(t.getName() + "中断状态:" + t.isInterrupted()); 24 System.out.println("[线程 main]执行 t.interrupt();"); 25 t.interrupt(); 26 System.out.println(t.getName() + "中断状态:" + t.isInterrupted()); 27 28 System.out.println("------------------------------------------"); 29 Thread mainThread = Thread.currentThread(); 30 System.out.println("[线程 main]中断状态:" + mainThread.isInterrupted()); 31 System.out.println("[线程 main]执行 mainThread.interrupt();"); 32 mainThread.interrupt(); 33 System.out.println("[线程 main]中断状态:" + mainThread.isInterrupted()); 34 System.out.println("[线程 main]running......"); 35 } 36 } 37 38 /* 输出 & 异常,两个线程都有信息要输出到控制台,因此也可能异常信息先输出 39 [线程 t]中断状态:false 40 [线程 main]执行 t.interrupt(); 41 [线程 t]中断状态:true 42 ------------------------------------------ 43 [线程 main]中断状态:false 44 [线程 main]执行 mainThread.interrupt(); 45 [线程 main]中断状态:true 46 [线程 main]running...... 47 java.lang.InterruptedException 48 at java.base/java.lang.Object.wait(Native Method) 49 at java.base/java.lang.Object.wait(Unknown Source) 50 at T3.run(InterruptedExceptionTest.java:10) 51 */
在当前线程等待前或等待期间,若是有其余线程中断当前线程,则抛出该异常。看下面的例子:
在这个示例中,咱们须要注意两点:
3. Note that only the locks on this object are relinquished; any other objects on which the current thread may be synchronized remain locked while the thread waits.
只有基于这个对象的锁被释放;在线程等待期间,当前线程可能被同步的任何其余对象都将保持锁定。(翻译看不懂?直接看代码吧)
1 class T3 extends Thread { 2 Object obj; 3 4 T3(String name, Object obj) { 5 super(name); 6 this.obj = obj; 7 } 8 9 @Override 10 public void run() { 11 synchronized (this) { 12 System.out.println(Thread.currentThread().getName() + "获取this锁"); 13 synchronized (obj) { 14 System.out.println(Thread.currentThread().getName() + "获取obj锁"); 15 try { 16 this.wait(); 17 } catch (InterruptedException e) {} 18 } 19 } 20 } 21 } 22 23 public class OtherLockBeRetainedTest { 24 25 public static void main(String[] args) throws Exception { 26 Object obj = "x"; 27 T3 t1 = new T3("[线程 t1]", obj); 28 T3 t2 = new T3("[线程 t2]", obj); 29 t1.start(); 30 Thread.sleep(2000); 31 t2.start(); 32 } 33 } 34 35 /* 输出 (程序死锁) 36 [线程 t1]获取this锁 37 [线程 t1]获取obj锁 38 [线程 t2]获取this锁 39 */
顺着代码理一下程序执行过程:
4. 虚假唤醒(spurious wakeups)
(我这里只给出一种简单的虚假唤醒状况,更详细的内容,你们能够自行查阅资料,能够参看:《Java并发编程实战》14.2 “使用条件队列”,《Effective Java》第69条 “并发工具优先于wait和notify”)
在某些状况下,咱们须要某个条件成立,线程才能往下执行,而下面的示例中,未必按这个套路出牌:
1 class T4 extends Thread { 2 boolean condition; 3 4 T4(boolean condition) { 5 this.condition = condition; 6 } 7 8 @Override 9 public void run() { 10 synchronized (this) { 11 try { 12 if (!condition) { 13 this.wait(); 14 } 15 // 当 condition == true 时,执行下面的操做 16 System.out.println("condition : " + condition); 17 } catch (InterruptedException e) {} 18 } 19 } 20 } 21 22 public class SpuriousWakeupTest { 23 public static void main(String[] args) throws Exception { 24 T4 t = new T4(false); 25 t.start(); 26 Thread.sleep(1000); 27 synchronized (t) { 28 t.notify(); 29 } 30 } 31 } 32 33 /* 输出 34 condition : false 35 */
上面的例子,咱们的原意是:当condition为true时,[线程 t] 继续执行下面的操做,不然继续等待直到条件成立。固然,[线程 t] 第一次判断condition时,符合咱们的意图,进行了等待;后来被主线程notify唤醒,condition依旧为false,而 [线程 t] 却执行了后续的操做,显然不符合咱们的意图。虽然 [线程 t] 真的被唤醒了,可是在咱们的业务逻辑定义下,它不该该被唤醒执行操做,应该继续等待。对于这样的唤醒,咱们称为虚假唤醒(状况不止于此)。如何解决这个问题呢?其实只须要作一点小修改便可,即:把 if语句换成 while语句,以下:
1 @Override 2 public void run() { 3 synchronized (this) { 4 try { 5 while (!condition) { 6 this.wait(); 7 } 8 // 当 condition == true 时,执行下面的操做 9 System.out.println("condition : " + condition); 10 } catch (InterruptedException e) {} 11 } 12 }
1 @Deprecated(since="9") 2 protected void finalize() throws Throwable {}
finalize,终结方法,当GC认为对象再也不被引用时,会先调用finalize(),再回收该对象。子类能够重写finalize方法,来处理一些系统资源或者完成其余清理工做。(自Java 9 开始被弃用)
关于finalize,有如下4个点须要注意(摘自《Effective Java》第2版,第7条:避免使用终结方法):
在我看来,咱们能够把finalize()当作是对象被GC回收前的回调,来看个“对象复活”的例子。代码来自:http://www.javashuo.com/article/p-htdktnss-ht.html
1 public class FinalizeTest { 2 3 public static FinalizeTest SAVE_HOOK = null; 4 5 public static void main(String[] args) throws Exception { 6 SAVE_HOOK = new FinalizeTest(); 7 SAVE_HOOK = null; 8 System.gc(); 9 Thread.sleep(500); 10 if (null != SAVE_HOOK) { 11 System.out.println("Yes, I am still alive"); 12 } else { 13 System.out.println("No, I am dead"); 14 } 15 SAVE_HOOK = null; 16 System.gc(); 17 Thread.sleep(500); 18 if (null != SAVE_HOOK) { 19 System.out.println("Yes, I am still alive"); 20 } else { 21 System.out.println("No, I am dead"); 22 } 23 } 24 25 @Override 26 protected void finalize() throws Throwable { 27 super.finalize(); 28 System.out.println("execute method finalize()"); 29 SAVE_HOOK = this; 30 } 31 } 32 33 /* 输出 34 execute method finalize() 35 Yes, I am still alive 36 No, I am dead 37 */
分析(为了描述方便,把 new FinalizeTest() 生成的对象叫作 对象A):
总的来讲,终结方法(finalizer)一般是不可预测的,也是很危险的,通常状况下是没必要要的。使用终结方法会致使行为不稳定、下降性能,以及可移植性问题。
那么,若是类的对象中封装了资源(如文件或线程)确实须要终止,怎么作才能不用编写终结方法呢?
其实,咱们只须要提供一个显示的终止方法便可,比较典型的例子:InputStream、OutputStream和java.sql.Connection上的close方法。一般,显示的终止方法会与try-finally结构结合使用,以确保及时终止。代码结构以下:
1 FOO foo = new Foo(); 2 try { 3 // do something 4 } finally { 5 foo.terminate(); // 如flie.close(); 6 }
终结方法有什么好处呢?或者说,咱们何时该使用它?
由于对JVM和GC方面不太了解,因此在深刻理解和实践时比较费劲,Emmmmm....再接再砺吧!
转载请说明出处,have a good time! :D