Java 是一门典型的面向对象语言,提供 extends
关键字使子类继承父类。
public class Student extends Person {...}
然而创立 Person
类时不必应用 extends
继承 Object
类。
public class Person extends Object {...}
因为,创立的类没有明确指明继承关系时,会在编译时主动继承 Object
类。能够应用 Object
类型的变量援用任何类型的实例:
Object sample = new Student(1001, "小咖", 20, "男");
也能够将任意的 Object
实例转换为须要的类型:
Person p = (Person) sample;
下面两个例子在编译器中是不会报错的。因而,Object
类是 Person
类的父类,每个类都是由 Object
扩大而来的。所以,相熟 Object
类中提供的服务是非常重要的。
Java 中的根本类型不是对象,但也提供了相应的包装类型,如 int
根本类型对应 Integer
包装类型。但应用根本类型创立的数组是扩大自 Object
类,实际上是援用。
Object sample = new int[10];
其实,所有的数组类型都是扩大了 Object
类。
下表为 Object
类的通用办法。
办法 | 形容 | 异样 |
---|---|---|
final native Class<?> getClass() |
返回对象的运行时类 | 无 |
native int hashCode() |
返回对象的散列码 | 无 |
boolean equals(Object obj) |
与其它对象是否相等 | 无 |
native Object clone() |
克隆并返回对象的正本 | CloneNotSupportedException |
String toString() |
返回对象的字符串示意 | 无 |
final native void notify() |
唤醒正在期待对象监听器上的一个线程 | 无 |
final native void notifyAll() |
唤醒正在期待对象监听器上的所有线程 | 无 |
final native void wait() |
导致以后线程期待,直到另一个线程调用此对象的 notify() 或notifyAll() |
InterruptedException |
final native void wait(long timeout) |
导致以后线程期待,直到另一个线程调用此对象的 notify() 或notifyAll() ,或者指定工夫已到 |
InterruptedException |
final void wait(long timeout, int nanos) |
导致以后线程期待,直到另一个线程调用此对象的 notify() 或notifyAll() ,或者指定工夫已到 |
InterruptedException |
void finalize() |
当 GC 确定不再有对该对象的援用时,由对象的 GC 调用此办法 | Throwable |
equals()
Object
类提供了 equals()
办法用于检测对象是否相等。其实现相等性要满足五个条件:
- 自反性。对于任何的非空援用都需满足
x.equals(x) == true
。 - 对称性。对于任何援用都满足
x.equals(y) == y.equals(x)
。 - 传递性。对于任何援用都满足
(x.equals(y) && y.equals(z)) == x.equals(z)
。 - 一致性。对于不产生任何变动的援用都满足
x.equals(y) == x.equals(y)
,屡次调用equals()
办法后果不变。 - 与
null
的比拟。x.equals(null)
返回false
。对任何不是null
的对象调用equals()
办法与null
比拟的后果都为false
。
援用类型最好应用 equals()
办法比拟;而根本类型应用 ==
比拟。
其中,子类中定义 equals()
办法时,需先比拟超类的 equals()
。如果检测失败,对象就不可能相等。如果超类中的域都相等,就须要比拟子类中的实例域。
上面能够从两个截然不同的状况看一下这个问题:
- 如果子类可能领有本人的相等概念,则对称性需要将强制采纳 getClass 进行检测。
- 如果由超类决定相等的概念,那么就能够应用 instanceof 进行检测,这样能够在不同子类的对象之间进行雷同的比拟。
对象中应用 equals()
办法比拟须要实现的步骤如下:
- 查看是否为同一个援用,如果是间接返回
true
。 - 检测传入的值是否为
null
,如果是间接返回false
。 - 检测是否属于同一个类型。如果不是间接返回
false
。 - 将
Object
对象类型转为要比拟的类型。 - 比拟每个要害域是否相等。
public boolean equals(Object o) {if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name)
&& Objects.equals(sex, person.sex)
&& (age != person.age);
}
如果子类从新定义 equals
,就要在其中蕴含 super.equals(other)
。
hasCode()
hashCode()
返回的是整数值,是无规律的散列码。hashCode()
定义在了 Object
类中,每个类都能够应用 hashCode()
办法调用本身散列码,其值为对象的存储地址。
Object sample = new Student(1001, "小咖", 20, "男");
System.out.println(sample); // [I@5b464ce8
如果你在创立的类中笼罩了 equals()
办法,就必须笼罩 hashCode()
办法。这是 hashCode()
的通用约定。上面是笼罩 hashCode()
办法的约定:
- 程序执行期间,对象的
equals()
办法中比拟的信息不变,同一个对象的hashCode()
办法的返回值也不变。两个程序的执行期间,hashCode()
办法返回的值能够不统一。 - 如果两个对象依据
equals(Object)
办法比拟相等,那hashCode()
办法的返回值也必须相等。 - 如果两个对象依据
equals(Object)
办法比拟不相等,那hashCode()
办法的返回值最好不相等。如果相等,会在应用Map
时造成散列码抵触。
总结就是两个对象相等,其散列码肯定雷同;然而散列码雷同的两个对象并不一定相等。因为计算散列码具备随机性,两个值不同的对象可能计算出雷同的散列码。
现实的散列函数是把汇合中不相等的实例平均地散布到所有可能的 int
之上。但十分艰难,只能实现绝对靠近这种现实的情景。
当计算散列码时,要将每个域都思考进去。能够将每个域都当成 R 进制的某一位,而后组成一个 R 进制的整数。
R 个别取奇数 31,偶数会呈现乘法溢出,信息会失落。因为与 2 相乘相当于向左移一位,最右边的位失落。并且一个数与 31 相乘能够转换成移位和减法:31*x == (x<<5)-x
,编译器会主动进行这个优化。
如下是一个如何简略的计算散列码的参考:
- 根本类型调用
Type.hashCode(value)
来生成。Type
类型为根本类型各自的包装类。 - 如果是援用类型,并且须要笼罩
equals()
办法,equals()
办法应用哪些域比拟,hashCode()
办法也会递归地调用这些域的散列码并计算。 - 如果是数组类型,那数组中的每个值都当做独自的域来解决。也能够应用
Arrays.hashCode()
办法计算。
不要试图从散列码计算中排除掉一个对象的要害域来进步性能。
上面重写 Student
类的 hashCode()
办法:
@Override
public int hashCode() {int result = super.hashCode();
result = 31 * result + sid;
return result;
}
hashCode()
办法返回的散列码也能够是正数,正当地组合实例域的散列码,以便可能让各个不同的对象产生的散列码更加平均。
toString()
默认的 toString()
办法是返回的是 java.lang.Object@511baa65
这种类型是的字符串。
Student sample = new Student(1001, "小咖", 20, "男");
System.out.println(sample); // java.lang.Object@511baa65
下面 System.out.println(sample)
会主动调用 sample.toString()
办法将值输入到控制台。然而,提供好的 toString()
实现能够获取对象状态的必要信息,也易于调试。因而倡议为每个自定义的类笼罩 toString()
办法。
上面重写 Student
类的 toString()
办法:
@Override
public String toString() {return getClass().getName()
+ "{ sid=" + sid
+ ", name=" + super.getName()
+ ", age=" + super.getAge()
+ ", sex=" + super.getSex()
+ "}";
}
如果父类 Person
也重写类 toString()
办法:
@Override
public String toString() {return getClass().getName() +
"{" + '\'' +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
并且子类的 toString()
也能够调用:
@Override
public String toString() {return super.toString() + "{sid =" + sid + "}";
}
// xxx.Student{'name=' 小咖 ', age=20, sex=' 男 '}{sid = 1001}
clone()
Object
类提供了 clone()
办法用于克隆实例,但因为是 protected
修饰符所润饰的办法,因而不会显示地笼罩 clone()
。实现 Cloneable
接口的类能够笼罩 clone()
办法提供克隆。如果不实现,会抛出 CloneNotSupportedException
异样。正确的写法如下所示:
public class Person implements Cloneable {
private String name;
private int age;
private String sex;
private String[] address;
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
... 省略 getter 与 setter...
@Override
protected Person clone() throws CloneNotSupportedException {return (Person) super.clone();}
}
Java 反对协变返回类型,也就是笼罩办法的返回类型能够是被笼罩办法的返回类型的子类。并且在 clone()
办法中调用 super.clone()
办法失去性能残缺的克隆对象。
当应用 Person
创建对象并调用 clone()
办法克隆。
Person s1 = new Person("小卡", 22, "男");
s1.setAddress(new String[] {"浙江省", "江苏省", "湖南省"});
Person s2 = sample.clone();
System.out.println(s1.hashCode()); // 873415566
System.out.println(s2.hashCode()); // 818403870
System.out.println(s1 == s2); // false
System.out.println(s1.getAddress() == s2.getAddress()); // true
从下面的代码中得出,调用 clone()
办法取得的对象是个新的对象,然而对象中的援用还是原来的援用,而不是新援用。这次的克隆被称为 浅拷贝。
应用 clone()
办法与通过结构器创建对象实际上是一样的,要确保不会挫伤到原始的对象,并确保正确地创立被克隆的对象中的约束条件。这次的克隆被称为 深拷贝。
因而,在 Person
类的外部,address
数组也要递归地调用 clone()
办法:
@Override
protected Person clone() throws CloneNotSupportedException {Person result = (Person) super.clone();
result.address = address.clone();
return result;
}
记住,Cloneable
与援用可变对象的 final
域的失常用法是不兼容的。因而,实现 clone()
办法禁止给 final
赋新值。
上述的拷贝形式比较复杂。能够在类中提供一个拷贝结构器或拷贝工厂来实现克隆的代替性能。
public Person(Person value) {...}
public static Person newInstance(Person value) {...}
finalize()
当 GC 确定不再有对该对象的援用时,GC 会调用对象的 finalize()
办法来革除回收。
protected void finalize() throws Throwable {}
因而,子类能够通过笼罩此办法解决一些额定的清理工作。然而,finalize()
办法何时被调用取决于 Java VM,而且不保障 finalize()
办法会被及时地执行。因而,不要依赖 finalize()
办法来更新重要的长久状态。
Java VM 会确保一个对象的 finalize()
办法只被调用一次,而且程序中不能间接调用 finalize()
办法。
finalize()
办法通常也不可预测,而且很危险,个别状况下,不必要笼罩 finalize()
办法。
wait 与 notify
Object
对象提供了 wait()
和 notify()
办法,这两个办法的应用是绝对的:
wait()
:线程进入期待状态。notify()
:唤醒期待该对象的线程。
应用 wait()
办法必须在同步区域外部调用,这个同步区域将对象锁定在调用 wait()
办法的对象上。上面是应用 wait()
办法的规范模式:
synchronized (obj) {while (<condition does not hold>) {obj.wait();
}
}
这时的 obj
对象所在线程会处于期待状态,须要应用 notify()
办法唤醒 obj
所在线程。
synchronized (obj) {obj.notify();
}
最好应用 notifyAll()
办法唤醒线程。因为总会产生正确的后果,保障会唤醒所有须要被唤醒的线程。尽管也会唤醒其余线程,但不影响程序的正确性,而且 notifyAll()
办法代替 notify()
办法能够防止来自不相干线程的意外或歹意的期待。
留神:必须应用 synchronized
,否则会报 IllegalMonitorStateException
异样。
总结
这里对 Object
类做了一个简略的理解,晓得 Object
类是所有类的基类,能够援用所有的援用类型,包含数组类型。且 Object
中提供的办法须要在适宜的场景下笼罩失去最佳的后果,最好始终都笼罩 toString()
办法。
更多内容请关注公众号「海人为记」