Java中级——Object和Objects
Object和Objects
- Object和Objects是什么?
- Object源码
- Objects源码
- 多态
- Object.equals()方法和 ==
- Object.hashcode()方法
- Object.clone()方法
- Object.toString()方法
Object和Objects是什么?
Object是所有Java类的父类,一个类若没有明确指出父类,Object就默认为此类的父类
Objects是工具类,用于实现基本方法
Object源码
不同源码的Object可能不同,如安卓原源码里有其他方法,以下按照空格分为:Natives()、getClass()、hashCode()和equals()、clone()、toString()、notify()和wait()、finalize()
public class Object {private static native void registerNatives();static {registerNatives();}public final native Class> getClass();public native int hashCode();public boolean equals(Object obj) {return (this == obj);}protected Object clone() throws CloneNotSupportedException {if (!(this instanceof Cloneable)) {throw new CloneNotSupportedException("Class " + getClass().getName() + " doesn't implement Cloneable");}return internalClone();}private native Object internalClone();public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}public final native void notify();public final native void notifyAll();public final void wait() throws InterruptedException {wait(0);}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);}public final native void wait(long timeout) throws InterruptedException;protected void finalize() throws Throwable { }
}
Tips:
- Object中的Native方法都是非Java实现,故看不到源码
- 可能需要重写的有hashCode()和equals()、clone()、toString()、finalize()
Objects源码
以下按照空格分为:私有构造、equals()和deepEquals()、hashCode()和hash()、toString()、compare()、requireNonNull()、isNull()和nonNull()
public final class Objects {private Objects() {throw new AssertionError("No java.util.Objects instances for you!");}public static boolean equals(Object a, Object b) {return (a == b) || (a != null && a.equals(b));}public static boolean deepEquals(Object a, Object b) {if (a == b)return true;else if (a == null || b == null)return false;elsereturn Arrays.deepEquals0(a, b);}public static int hashCode(Object o) {return o != null ? o.hashCode() : 0;}public static int hash(Object... values) {return Arrays.hashCode(values);}public static String toString(Object o) {return String.valueOf(o);}public static String toString(Object o, String nullDefault) {return (o != null) ? o.toString() : nullDefault;}public static int compare(T a, T b, Comparator super T> c) {return (a == b) ? 0 : c.compare(a, b);}public static T requireNonNull(T obj) {if (obj == null)throw new NullPointerException();return obj;} public static T requireNonNull(T obj, String message) {if (obj == null)throw new NullPointerException(message);return obj;}public static T requireNonNull(T obj, Supplier messageSupplier) {if (obj == null)throw new NullPointerException(messageSupplier.get());return obj;}public static boolean isNull(Object obj) {return obj == null;}public static boolean nonNull(Object obj) {return obj != null;}
Tips:
- 会经常用到的方法为equals()和deepEquals()、hashCode()和hash()、toString()、compare()
- requireNonNull()、isNull()和nonNull()是类库设计者用到的,我们一般直接判空
多态
所有Object类型的变量可以引用除基本数据类型外的各种对象
class Person {}
但要对其操作还得转为对应的数据类型
Object o = new Person();
Person p = (Person) o;
Object类型变量还可引用所有数组
Object o = new Object();
Person[] people = new Person[10];
o = people;
o = new int[10];
Object.equals()方法和 ==
- == 比较地址是否相同
- Object.equals()未重写时等价于 ==(源码就是调用==)
- Object.equals()重写后比较规则由设计者给出
以String中的equals()为例,当两个字符串地址相等时,肯定是同一字符串,当地址不等时,若字符串中每个字符相等也认为是同一字符串,故比较规则由设计者给出(若设计者想在一半相等就返回true也是可以的)
public boolean equals(Object anObject) {if (this == anObject) {return true;}if (anObject instanceof String) {String anotherString = (String)anObject;int n = length();if (n == anotherString.length()) {int i = 0;while (n-- != 0) {if (charAt(i) != anotherString.charAt(i))return false;i++;}return true;}}return false;
}
故Object.equals()经常用来判断两个对象状态是否相等(是否有相同的域)
对于非空的x、y、z,重写Object.equals()应该保证:
- 自反性: x.equals(x) == true
- 对称性:x.equals(y) == true && y.equals(x) == true
- 传递性:x.equals(y) == true && y.equals(z) == true && x.equals(z) == true
- 一致性:当x、y未改变时,对于多次调用总是 x.equals(y) == true
- 非空性:x.equals(null) == false
在effective java中给出了编写Object.equals()的规则
- 使用 == 比较参数是否为这个对象的引用
- 使用 instanceof 比较参数是否为正确的类型
- 把参数转换成正确的类型
- 比较参数中的域与该对象中对应的域是否相等
标准写法如下,(obj == null)判断不是必须的,当obj=null,在instanceof判断时也会返回false
class Person {private String name;public Person(String name) {this.name = name;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}/*if (obj == null) {return false;}*/if (!(obj instanceof Person)) {return false;}Person other = (Person) obj;return Objects.equals(this.name, other.name);}
}
为什么是instanceof判断而不是getClass()判断?
- 父类的equals()同时适用于子类,就用instanceof,如AbstractMap中的equals()适用于HashMap、TreeMap等
- 用instanceof方便以后的扩展
- 若父类的equals()无法满足子类的比较,则子类需重写equals()
如下给出一个使用getClass()判断的反例
class Person {private String name;public Person(String name) {this.name = name;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (getClass() != obj.getClass()) {return false;}Person other = (Person) obj;return Objects.equals(this.name, other.name);}
}class Man extends Person {public Man(String name) {super(name);}
}
针对上面的继承结构,若想判断name="song"的人是否存在于List,如下代码输出true和false,显然后者的false并不符合实际要求
List list = new ArrayList<>();
list.add(new Person("song"));
System.out.println(list.contains(new Person("song")));
System.out.println(list.contains(new Man("song")));
此外继承和equals()的性质是相违背的,对于如下继承结构
class Person {private String name;public Person(String name) {this.name = name;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Person)) {return false;}Person other = (Person) obj;return Objects.equals(this.name, other.name);}
}class Man extends Person {private int age;public Man(String name, int age) {super(name);this.age = age;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Man)) {return false;}Man other = (Man) obj;return super.equals(obj) && this.age == other.age;}
}
如下代码输出true、false,表示Person可Man比较,而Man跟Person比较始终为false,明显违背对称性
System.out.println(new Person("song").equals(new Man("song", 18)));
System.out.println(new Man("song", 18).equals(new Person("song")));
通过修改Man的equals进行混合比较可满足上述对称性,如下
class Person {private String name;public Person(String name) {this.name = name;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Person)) {return false;}Person other = (Person) obj;return Objects.equals(this.name, other.name);}
}class Man extends Person {private int age;public Man(String name, int age) {super(name);this.age = age;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Person)) {return false;}if (!(obj instanceof Man)) {return obj.equals(this);}Man other = (Man) obj;return super.equals(obj) && this.age == other.age;}
}
但是修改后的代码不满足对称性,如下输出true、true、false,因为前两个比较只考虑了name属性,而最后的比较多了age属性,同时上面的代码可能会导致无限递归调用(两个子类比较时都调用对方的equals)
Man m1 = new Man("song", 18);
Person p1=new Person("song");
Man m2 = new Man("song", 19);System.out.println(m1.equals(p1));
System.out.println(p1.equals(m2));
System.out.println(m1.equals(m2));
故无法在继承时增加比较域又保持equals的性质,而解决办法是把继承改为组合,并在子类中提供一个返回父类的公有视图(修改后将不再允许父类和子类比较)
class Person {private String name;public Person(String name) {this.name = name;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Person)) {return false;}Person other = (Person) obj;return Objects.equals(this.name, other.name);}
}class Man {private Person person;private int age;public Man(String name, int age) {person = new Person(name);this.age = age;}public Person asPerson() {return person;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Man)) {return false;}Man other = (Man) obj;return Objects.equals(this.person, other.person) && this.age == other.age;}
}
Tips:
- 对于float和double,使用Float.compare()和Double.compare()比较
- 对于其他基本数据类型,使用 == 比较
- 对于引用数据类型,使用Objects.equals(a,b)比较
- 对于数组,使用Arrays.equals比较
- 比较顺序会影响equals()方法性能,应优先比较最有可能不相等的域或开销最低的域
Object.hashcode()方法
重写equals()方法就必须重写hashcode()方法,需要保证
- x.equals(y) == true,必有x.hashcode() == y.hashcode()
- x.hashcode() != y.hashcode(),必有x.equals(y) == false
- x.hashcode() == y.hashcode(),x.equals(y)不确定
先看不重写hashcode()会怎样,对于如下代码
class Person {private String name;public Person(String name) {this.name = name;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Person)) {return false;}Person other = (Person) obj;return Objects.equals(this.name, other.name);}
}
如下将一个实例存储到hashmap再取出,打印为null,原因是
- 未重写hashcode时,其值为地址,每次new出来的对象地址不一样
- 所以导致put()把对象存放在一个散列桶,而get()却在另一个散列桶内查找
HashMap map = new HashMap<>();
map.put(new Person("song"), 1);
System.out.println(map.get(new Person("song")));
effective java给出的编写规则为:将第一个关键域的hashCode()作为result,其乘以31再累加其他关键域的hashCode(),其他关键域的计算方法如下:
- 若为基本数据类型,则调用其装箱类型的hashCode()方法
- 若为引用数据类型,并且该数据在equals中递归调用equals,则在hashcode也递归调用hashcode
- 若为数组中的某个元素,则将其单独拿出来按照上面规则计算,若整个数组都为关键域则调用Arrays.hashCode()
class Person {private String name;private int age;@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Person)) {return false;}Person other = (Person) obj;return this.name.equals(other.name) && this.age == other.age;}@Overridepublic int hashCode() {int result = name.hashCode();result = 31 * result + Integer.hashCode(age);return result;}
}
若觉得上面太麻烦,可通过Objects.hash()生成hashcode,但其性能不如上面
class Person {private String name;private int age;@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Person)) {return false;}Person other = (Person) obj;return Objects.equals(this.name, other.name) && this.age == other.age;}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}
若一个类经常用于散列,可定义一个域将hashcode缓存起来(可选择创建时初始化或延迟初始化)
Object.clone()方法
赋值 = 对于基本数据类型和不可变对象类型(如String)是拷贝数据,对于可变对象则是拷贝引用
class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}
}
对于如下代码,b的改变不会影响a,但p2的改变会影响p1
int a = 1;
int b = a;
b = 2;Person p1 = new Person("tom", 1);
Person p2 = p1;
p2.setName("john");
要想获得一个与调用者一样的对象实例,则需要clone方法,如下p2的修改不会再影响p1
Person p1 = new Person("tom", 1);
Person p2 = (Person) p1.clone();
p2.setName("john");
上述代码是运行不了的,因为clone()是protect方法,想要调用须实现Cloneable接口并重写clone()
class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}protected Object clone() throws CloneNotSupportedException {return super.clone();}
}
但如果类包含可变对象域,super.clone()会让两个实例中对象域的引用相等,如Person新增Clothe域
class Person {private String name;private int age;private Clothe clothe;public Person(String name, int age, Clothe clothe) {this.name = name;this.age = age;this.clothe = clothe;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Clothe getClothe() {return clothe;}public void setClothe(Clothe clothe) {this.clothe = clothe;}protected Object clone() throws CloneNotSupportedException {return super.clone();}
}class Clothe {private String Coat;public Clothe(String coat) {Coat = coat;}public void setCoat(String coat) {Coat = coat;}
}
对于以下代码,p1、p2的Clothe域引用同一个对象,对p2的Clothe域修改会影响p1
Person p1 = new Person("tom", 1, new Clothe("裤子"));
Person p2 = (Person) p1.clone();
p2.getClothe().setCoat("裙子");
故还需要对类内部可变对象域进行拷贝,让Clothe也实现Cloneable重写clone()
class Clothe implements Cloneable{private String Coat;public Clothe(String coat) {Coat = coat;}public void setCoat(String coat) {Coat = coat;}protected Object clone() throws CloneNotSupportedException {return super.clone();}
}
然后在Person的clone()内部完成对可变实例域(Clothe)的拷贝,即递归拷贝
protected Object clone() throws CloneNotSupportedException {Person tempPerson= (Person) super.clone();tempPerson.clothe= (Clothe) clothe.clone();return tempPerson;
}
现在p1、p2的Person引用的对象不一样,其内部Clothe域引用的对象也不一样
Person p1 = new Person("tom", 1, new Clothe("裤子"));
Person p2 = (Person) p1.clone();
p2.getClothe().setCoat("裙子");
Tips:
- clone()不能用于final类型的可变对象域
- 复写的clone()方法可修改返回值(省去类型转换)、去掉异常并申明为public供外部调用
- 递归拷贝容易出错,更好的方式是提供拷贝构造器或拷贝工厂(里面调用addAll)
Object.toString()方法
默认打印类名@十六进制Hashcode,通常重写为对域的遍历打印(会自动生成)
class Person {private String name;private int age;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}
打印结果为
Person{name='null', age=0}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
