关于Object类中的方法,根据其所涉及的知识点,分为如下4个部分:
2018-09-15
今天写:equals、hashCode、toString、clone、getClass,后面的方法等学到相关知识再作补充。
一. equals
public boolean equals(Object obj) { return (this == obj); }
equals用来判断两个对象是否”相等“,而对“相等”这个词的定义不同,得出的结果也不同:
要实现上面的需求,我们就需要重写equals方法,如下:
public class Point { int x; int y; Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Point) { Point p = (Point)obj; return (this.x == p.x && this.y == p.y); } return false; } }
测试:
public static void main(String[] args) { Point p1 = new Point(1, 1); Point p2 = new Point(1, 1); System.out.println("p1.equals(p2) : " + p1.equals(p2)); p2.x = 2; System.out.println("p1.equals(p2) : " + p1.equals(p2)); } /* 输出 p1.equals(p2) : true p1.equals(p2) : false */
另记:String类也重写了equals方法,实现了:只要两个字符串长度相等及字符串中对应位置的字符相等,则两字符串相等。可以参考String类中的equals方法。
注意:在重写equals方法时,方法定义要写为:public boolean equals(Object obj) {....} ,参数类型是:Object obj
二. hashCode
public native int hashCode();
hashCode,官方解释:返回对象的一个哈希码,为基于哈希表的数据结构提供便利,如:HashMap等。
Object类中的hashCode方法为本地方法,在重写hashCode方法时,需遵循以下规则:
关于第2条规则,我们继续Point类这个例子。首先,在未重写hashCode方法的情况下,我们测试两个对象的hashCode()输出值:
public static void main(String[] args) { Point p1 = new Point(9483, 89382); Point p2 = new Point(9483, 89382); System.out.println("p1.hashCode() : " + p1.hashCode()); System.out.println("p2.hashCode() : " + p2.hashCode()); } /* 输出: p1.hashCode() : 166239592 p2.hashCode() : 991505714 */
可以看到,在我们定义的equals方法下相等的两个对象,得到的hashCode是不同的,如此不一致会造成什么后果呢?我们知道 HashMap 在存储<Key, Value>时,如果Key1等于Key2,那么存储的键值对为:<Key1, Value2>,即:只会存储一个Key,使用的是最新的Value。而 HashMap 中在判断 Key1是否等于Key2时,使用的就是它们的hashCode。在未重写hashCode方法的情况下,看如下测试:
public static void main(String[] args) { Point p1 = new Point(9483, 89382); Point p2 = new Point(9483, 89382); HashMap<Point, Integer> map = new HashMap<Point, Integer>(); map.put(p1, p1.hashCode()); map.put(p2, p2.hashCode()); for (Map.Entry<Point, Integer> m : map.entrySet()) { System.err.println(m); } } /* 输出 Point@9e89d68=166239592 Point@3b192d32=991505714 */
根据我们对Point类相等的定义,p1与p2相等,而在 HashMap 中却存入了两个键值对,显然不符合我们的意图。(equals与hashCode的不一致,会造成使用时产生歧义,从而导致意想不到的错误。所以,我们在重写equals方法后,也要重写hashCode方法,使其意义一致)现在我们来重写hashCode方法,再进行如上测试:
@Override public int hashCode() { return (x & y) | (x ^ y); } /* 输出 Point@17d2f=97583 */
根据我们对hashCode方法的定义,对象的hashCode只与(x, y)相关,所以 p1.hashCode() == p2.hashCode() 为 true。这样一来,HashMap 中只会存入一个键值对,符合我们的预期。
三. toString
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
源码中直接返回:对象类型名@对象hashCode的十六进制,举个例子:
public static void main(String[] args) { Point p1 = new Point(9483, 89382); System.out.println(p1.toString()); } /* 输出 Point@17d2f */
很多情况下,我们都要重写toString()方法,就比如Point类,我们想知道的是点的横纵坐标(x, y),而不是 Point@17d2f 这串不知所云的字符。
@Override public String toString() { return "(" + x + ", " + y + ")"; } /* 输出 (9483, 89382) */
四. clone
protected native Object clone() throws CloneNotSupportedException;
从方法定义入手:
现在,我们对之前的Point类进行部分修改,为了节省空间,我只贴出修改部分的代码:
首先,定义Data类,用来记录一个点所包含的相关信息;
public class Data { int weight; String name; Data(int weight, String name) { this.weight = weight; this.name = name; } }
然后,Point类实现Cloneable接口,并且Point类中包含一个Data类型字段,如下:
public class Point implements Cloneable { int x; int y; Data data; Point(int x, int y, Data data) { this.x = x; this.y = y; this.data = data; }
... }
测试:
public static void main(String[] args) throws Exception { Data data = new Data(20, "A"); Point p1 = new Point(1, 2, data); Point p2 = (Point)p1.clone(); System.out.println("p1 == p2 : " + (p1 == p2)); System.out.println("p1.(x, y) = " + p1.toString() + ", p2.(x, y) = " + p2.toString()); System.out.println("p1.data == p2.data : " + (p1.data == p2.data)); } /* 输出 p1 == p2 : false p1.(x, y) = (1, 2), p2.(x, y) = (1, 2) p1.data == p2.data : true */
对于测试的输出,我们可以发现:
对于第3条,即Object类的clone方法是浅拷贝,理解如图:
在一些并发编程情景下,我们常常需要操作 不可变对象 来保证并发安全性。不可变对象,顾名思义就是你创建的对象不会改变,你可以理解为:
(更详细的内容,可以参考《Java并发编程实战》)
现在,假如我要在并发环境下使用p1.clone()出来的对象p2,并要求p2是不可变的。而事实上,其他线程可以通过 p1.data 来改变 p2.data 的状态,以破坏p2的不可变性。
要想使p2不可变,我们就需要对Point类进行深拷贝,即:对Piont类中的Data类型字段也创建一个新的对象,使得 p1.data != p2.data,如下:
public class Data { ... // 自定义的clone(),并非重写Object类中的clone() public Data clone() { return new Data(weight, name); } } public class Point implements Cloneable { ... @Override protected Object clone() throws CloneNotSupportedException { Point p = (Point)super.clone(); p.data = data.clone(); // 这里的data.clone()与Object类中的clone()无关 return p; } ... } /* 重复上面的测试,输出: p1 == p2 : false p1.(x, y) = (1, 2), p2.(x, y) = (1, 2) p1.data == p2.data : false */
思考:如果一个类中一直嵌套着包含引用类型字段,那么我们该怎么才能做到深拷贝呢?很明显,对于类中每一个引用类型对象都做深拷贝。(递归处理)
五. getClass
public final native Class<?> getClass();
getClass方法,返回对象的类对象,在反射中经常使用,例如:
Data类中有个私有方法 printInfo(),该方法在Point类中无法正常调用,但是我们可以通过反射机制来调用该方法。
public class Data { ... private void printInfo() { System.out.println("weight = " + weight); System.out.println("name : " + name); } } // 在Point类中 public static void main(String[] args) throws Exception { Data data = new Data(20, "A"); Class<?> clz = data.getClass(); Method m = clz.getDeclaredMethod("printInfo"); m.setAccessible(true); // 抑制Java的访问控制检查 m.invoke(data); } /* 输出 weight = 20 name : A */
这里只是简单提一下,更多关于反射的知识,会在后期总结。
原文:https://www.cnblogs.com/southday/p/9651092.html