java是一门面向对象的编程语言,所有的事物都可以抽象为对象。
Object类是所有类的超类,位于java.lang包下,可以看做是所有类的父类,因此所有类都具备它的实现方法,或自己重写,或直接使用。
1.注册本地函数
在Object中存在这样头一段代码
private static native void registerNatives();
static {
registerNatives();
}
在静态代码块中,调用了registerNatives()函数,也就是说在Object类加载过程中,便执行了registerNatives()函数。
这个函数的作用是向JVM(Java Virtual Machine,我们的java不是直接运行在操作系统之上,而是运行在java虚拟机上)注册Object的本地函数hashCode()、clone()、notify()、notifyAll()、wait()方法(不包含getClass())。
2.getClass()
public final native Class<?> getClass();
getClass()用于获取某对象运行时类对象,这是获取运行时类对象的方法之一,另一种方式是Class.forName(包名 + 类名)的方式。
同一类的实例都是有该类的类对象生成。创建新的对象可以通过new 的方式,通过Class对象,我们可以使用它的newInstance()函数调用其无参构造函数,完成新对象的创建;如果需要使用有参构造函数,需要获取Constructor对象(以后有机会在介绍)。
运行以下代码:
class MyTest{
static int count;
public MyTest(){
System.out.println("创建的第" + (++count) + "个对象实例...");
}
}
public class Main {
public static void main(String[] args) throws Exception {
// MyTest的对象实例
MyTest myTest = new MyTest();
// 获取运行时类对象
Class clazz1 = myTest.getClass();
Class clazz2 = Class.forName("com.guaniu.MyTest");
// 创建新的对象实例
MyTest instance1 = (MyTest) clazz1.newInstance();
MyTest instance2 = (MyTest) clazz2.newInstance();
System.out.println("clazz1 == clazz2:" + (clazz1 == clazz2));
System.out.println("instance1 == instance2:" + (instance1 == instance2));
}
}
得出结果:
3.hashCode()
public native int hashCode();
hashCode()方法返回的是32位的int类型值,在底层实现中是将对象地址的转换。这个方法主要是用于支持哈希表的数据结构,如HashCode。
hashCode需要满足的原则是:
a.在同一个java程序中对用同一个对象的多次调用,返回的数值必须是一致的,在不同的Java程序中不需要满足此规则;
b.如果通过equals方法判断两个对象相等,那么两个对象hashCode()必须返回相等的数值;
c.如果两个对象不相等,不需要要求它们的hashCode()方法返回相同的数值。
4.equals()
public boolean equals(Object obj) {
return (this == obj);
}
equals()方法用于判断两个对象是否相等,没有要求相同(==),虽然在Object的原有实现中是“==”的方式。
equals() 方法需要满足一下性质:
a.自反性(reflexive):对于任何非null引用x,x.equals(x)为true;
b.对称性(symmetric):对于任何非空引用x和y,如果x.equals(y)为true,y.equals(x)必须也为true;
c.传递性(transitive):对于任何非空引用x、y和z,如果x.equals(y)为true,y.equals(z)为true,那么x.equals(z)也为true;
d.一致性(consistent):对于任何非空引用x和y,对于多次调用x.equals(y)始终返回的是true和false,不会改变。
e.对于任意非空引用x,x.equals(null)返回false。
5.clone()
protected native Object clone() throws CloneNotSupportedException;
可以看到这个方法是使用protected关键字修饰的,这方法用于返回当前对象的复制。然而,通过对象调用这个方法需要实现Cloneable接口,然后重写这个方法(如果需要在其他包中调用,还需要将其声明为public方法),否则会抛出CloneNotSupportedException。
Cloneable接口是一个空接口,并没有包含clone()方法的声明!!!但是仅实现Cloneable接口,而不重写clone()方法是不能使用这个方法的。
public interface Cloneable {}
案例分析1:
class MyTest implements Cloneable{
int i1;
Integer i2;
String str;
Content content = new Content();
public MyTest(int i1, int i2, String str){
this.i1 = i1;
this.i2 = i2;
this.str = str;
}
@Override
protected MyTest clone() throws CloneNotSupportedException {
return (MyTest) super.clone();
}
@Override
public String toString() {
return "MyTest{" +
"i1=" + i1 +
", i2=" + i2 +
", str='" + str + '\'' +
", content=" + content +
'}';
}
}
class Content{
String value = "test";
@Override
public String toString() {
return "Content{" +
"value='" + value + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) throws Exception {
MyTest myTest = new MyTest(128, 128, "aaa"); // 因为Integer数值在[-128,127]之间时会从缓存中获取,因此我的测试用例选择大于127的值
MyTest cloneMyTest = myTest.clone();
System.out.println("myTest:" + myTest.toString());
System.out.println("cloneMyTest:" + cloneMyTest.toString());
System.out.println("myTest.i2 == cloneMyTest.i2:" + (myTest.i2 == cloneMyTest.i2));
System.out.println("myTest.str == cloneMyTest.str:" + (myTest.str == cloneMyTest.str));
System.out.println("myTest.content == cloneMyTest.content:" + (myTest.content == cloneMyTest.content));
cloneMyTest.i1 = 129;
cloneMyTest.i2 = 130;
cloneMyTest.str = "bbb";
cloneMyTest.content.value = "new content";
System.out.println("myTest:" + myTest.toString());
System.out.println("cloneMyTest:" + cloneMyTest.toString());
}
}
运行结果:
结论:a.克隆对象的基本类型的成员变量的修改不会影响到原来的对象;
b.克隆对象的包装类型的成员变量的修改不会影响到原来的对象;
c.克隆对象的String类型的成员变量的修改不会影响到原来的对象;
d.克隆对象的自定义类型【引用类型】的成员变量的修改会影响原来的对象;
解决办法时Content类同样需要实现Cloneable接口,在MyTest类的clone()方法中,在克隆对象返回之前,调用content的clone()方法,如下所示:
class MyTest implements Cloneable{
int i1;
Integer i2;
String str;
Content content = new Content();
public MyTest(int i1, int i2, String str){
this.i1 = i1;
this.i2 = i2;
this.str = str;
}
@Override
protected MyTest clone() throws CloneNotSupportedException {
MyTest myTest = (MyTest) super.clone();
myTest.content = this.content.clone();
return myTest;
}
@Override
public String toString() {
return "MyTest{" +
"i1=" + i1 +
", i2=" + i2 +
", str='" + str + '\'' +
", content=" + content +
'}';
}
}
class Content implements Cloneable{
String value = "test";
@Override
public String toString() {
return "Content{" +
"value='" + value + '\'' +
'}';
}
@Override
protected Content clone() throws CloneNotSupportedException {
return (Content) super.clone();
}
}
public class Main {
public static void main(String[] args) throws Exception {
MyTest myTest = new MyTest(128, 128, "aaa"); // 因为Integer数值在[-128,127]之间时会从缓存中获取,因此我的测试用例选择大于127的值
MyTest cloneMyTest = myTest.clone();
System.out.println("myTest:" + myTest.toString());
System.out.println("cloneMyTest:" + cloneMyTest.toString());
System.out.println("myTest.i2 == cloneMyTest.i2:" + (myTest.i2 == cloneMyTest.i2));
System.out.println("myTest.str == cloneMyTest.str:" + (myTest.str == cloneMyTest.str));
System.out.println("myTest.content == cloneMyTest.content:" + (myTest.content == cloneMyTest.content));
cloneMyTest.i1 = 129;
cloneMyTest.i2 = 130;
cloneMyTest.str = "bbb";
cloneMyTest.content.value = "new content";
System.out.println("myTest:" + myTest.toString());
System.out.println("cloneMyTest:" + cloneMyTest.toString());
}
}
代码运行结果:
为什么会产生这样的结果呢?
首先先介绍两个概念:
浅拷贝:对于拷贝对象内部的引用类型变量,只拷贝了原有对象引用成员变量的引用,两个引用实际指向的还是同一个对象。
深拷贝:对于拷贝对象内部的引用类型变量,对原有对象引用所指向的对象进行了拷贝,两个引用指向的不是同一个变量。
java中对于基本类型成员变量来说,拷贝的就是值;对于引用类型来说,默认的拷贝类型就是浅拷贝(不管是包装类、String、还是其他引用类型)。
在上面的例子中,对Integer和String类型拷贝是引用拷贝,也就是浅拷贝,对于赋新值得过程中,生成了新的对象,使他们指向了新生成的对象。
6.toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString()方法就是对象的字符串展示方法,默认是【类型 + @ + hash码十六进制表示】
7.notify()/notifyAll()
public final native void notify();
public final native void notifyAll();
这两个方法用于唤醒在当前对象锁等待的线程,notify()随机唤醒一条等待线程,notifyAll()唤醒所有等待线程,这些线程竞争对象的监视器锁权限,实际同一时刻只能有一条线程能够获得锁权限。
使用notify/notifyAll的线程当前应该通过synchronized持有这对象所,而被唤醒的线程则等待该线程退出同步方法或者同步代码块,才能获取到锁。
8.wait()
public final native void wait(long timeout) throws InterruptedException;
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 >= 500000 || (nanos != 0 && timeout == 0)) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
可以看到wait/notify方法都是使用final关键字修饰的,属于不能被重写的方法。
wait()用于使当前线程在其调用位置等待,它会释放锁的占用,中断该线程或者其他线程调用notify/notifyAll可使该线程脱离该状态。
wait有三种方法可供使用:
a.无参,一致等待直到其他线程使用notify/notifyAll;
b.一个参数timeout(毫秒),等待timeout的时间直到其他线程使用notify/notifyAll或者超时;
c.两个参数timeout(毫秒),nanos(纳秒),可以看到,对于纳秒的计算实际上是类似四舍五入的形式,等待round(timeout + nanos)的时间直到其他线程使用notify/notifyAll或者超时;
9.finalize()
protected void finalize() throws Throwable { }
当前对象回收前会被自动调用,个人不要使用!!!。
总结:
1、Object是Java中所有类的超类;
2、深拷贝与浅拷贝的认识和使用;
3、对象锁等待、与唤醒方法的认识。
wait()和notify()涉及到了多线程的内容,具体内容后面再细讲。