Object
在 Java 中 Object
类是所有类的祖先类,Object
没有定义属性,一共有13个方法。其它所有的子类都会继承这些方法。
构造函数
registerNatives()
private static native void registerNatives();
static {
registerNatives();
}
复制代码
在 Java 中,用 native 关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用
而 registerNatives()
方法的主要作用则是将C/C++中的方法映射到 Java 中的 native
方法,实现方法命名的解耦。
构造函数 public Object()
@HotSpotIntrinsicCandidate
public Object() {}
复制代码
@HotSpotIntrinsicCandidate注解,该注解是特定于Java虚拟机的注解。通过该注解表示的方法可能( 但不保证 )通过HotSpot VM自己来写汇编或IR编译器来实现该方法以提供性能。 它表示注释的方法可能(但不能保证)由HotSpot虚拟机内在化。如果HotSpot VM用手写汇编和/或手写编译器IR(编译器本身)替换注释的方法以提高性能,则方法是内在的。 也就是说虽然外面看到的在JDK9中weakCompareAndSet和compareAndSet底层依旧是调用了一样的代码,但是不排除HotSpot VM会手动来实现weakCompareAndSet真正含义的功能的可能性
一般创建对象的时候直接使用 new className(Args)
来创建一个新的对象。而在类的定义过程中,对于未定义构造函数的类,那么它就会默认继承Object
的无参构造函数,如果定了一个或多个构造函数,那么就需要把无参构造函数方法也写上。
方法
public final native Class<?> getClass()
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
复制代码
getClass
返回运行时当前对象的类对象。在 Java 中,类是对具有一组相同特征或行为的实例的抽象进行描述。而类对象则是对类的特征和行为进行描述(即类的名称,属性,方法...)。也就是说通过获取到类对象,则可以获取到该类的所有属性,方法等。
public native int hashCode()
@HotSpotIntrinsicCandidate
public native int hashCode();
复制代码
hashCode
返回当前对象的哈希码。hashCode
遵守以下三个约定
- 在 Java 程序运行期间,对同一个对象多次调用
hashCode
,那么它们的返回值需要是一致的。(前提:没有对对象进行修改) - 如果两个对象相等(调用
equals()
方法),那么这两个对象的hashCode
也是一样 - 两个对象调用
hashCode
方法返回的哈希码相等,这两个对象不一定相等
也即是说,调用equals
方法返回值相等,那么调用hashCode
方法返回值也一定相等。所以,在重写euqlas
方法之后,一定要重写hashCode
方法。
那么判断对象是否先等可以直接用equals
来判断,为什么还需要hashCode
方法呢?
其实hashCode
方法的一个主要作用是为了增强哈希表的性能。比如:我们知道Set
集合不能存在相同的两个对象,那么该怎么判断两个对象是否相同呢?如果没有hashCode
,那么就需要进行遍历来逐一判断。那么有hashCode
,我们就可以计算出即将要加入集合的对象的hashCode
,然后查看集合中对应的位置上是否有对象即可。
public boolean equals(Object obj)
public boolean equals(Object obj) {
return (this == obj);
}
复制代码
equals()
用于判断两个对象是否相等。根据 Object
的实现,可以看到判断的依据是看两个对象的引用地址是否相等。
而一般我们会用另外一种方式来判断是否相等。即==
,==
表示的是两个变量值是否相等(基础类型的值在内存地址中存储的是值)
那么我们想要判断是否相等:
- 如果是基础类型,就可以直接用
==
来判断 - 如果是引用类型,那么就需要通过
equals
方法来判断(在实际业务中,一般会重写equals
方法)
需要注意的一点是String
也是引用类型,我们判断String
的时候是直接使用的equals
方法,而按照默认的equals
实现,创建两个具有相同值的String
对象,那么equals
返回的应该是false
,
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
复制代码
public String toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
复制代码
toString()
返回该对象的字符串表示。在使用 System.out.printLn(obj)
的时候,其内部也是调用的toString
方法。可以按需重写toString
方法。
protected native Object clone()
protected native Object clone() throws CloneNotSupportedException;
复制代码
clone()
方法返回的是当前对象的引用,指向的是新clone
出来的对象,此对象和原对象占用不同的堆空间。
clone
方法的正确调用需要实现 cloneable
接口,如果没有实现该接口,那么子类调用父类的 clone
方法则会抛出CloneNotSupportedException
异常
Cloneable接口仅仅是一个表示接口,接口本身不包含任何方法,用来指示Object.clone()可以合法的被子类引用所调用。
1. 使用
先看一段代码
public class CloneTest {
public static void main(String[] args) {
Object o1 = new Object();
try {
Object clone = o1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
复制代码
执行这段段代码会抛出The method clone() from the type Object is not visible
异常。原因是clone
方法是被 protected
修饰的,也就是说被protected
修饰的属性和方法,在同一包下或在不同包下的子类可以访问。显然,CloneTest
和Object
不在同一包下,不过按照字面意思,CloneTest
会默认继承Object
,所以即使在不同的包下,应该也是可以访问的才对。那么问题就出现在「在不同包下的子类可以访问」这句话上:
不同包中的子类可以访问: 是指当两个类不在同一个包中的时候,继承自父类的子类内部且主调(调用者)为子类的引用时才能访问父类用protected修饰的成员(属性/方法)。 在子类内部,主调为父类的引用时并不能访问此protected修饰的成员。(super关键字除外)
也就是说在子类中想要调用父类的protected
方法,可以
- 在子类中重写父类的方法
- 在子类中通过
super.methodName()
来调用父类方法
2. 浅拷贝&深拷贝
浅拷贝: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址。 深拷贝: 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
对于浅拷贝来说,如果含有引用类型,那么修改其中一个对象的引用值,那么会影响到另外一个对象。按层级来说,浅拷贝只拷贝了第一层。对于默认的clone
实现是浅拷贝。如果想要实现深拷贝,可以
- 对对象进行序列化
- 重写
clone
方法
//序列化实现深拷贝
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
public class Person implements Serializable{
private static final long serialVersionUID = 2631590509760908280L;
}
public class CloneTest {
public static void main(String[] args) {
Person person = new Person();
Person person1 = CloneUtils.clone(person);
}
}
参考:https://blog.csdn.net/chenssy/article/details/12952063
复制代码
protected void finalize()
protected void finalize() throws Throwable {}
复制代码
finalize()
方法主要与 Java 垃圾回收机制有关,JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。所以此方法并不是由我们主动去调用的。
wait()/notify/notifyAll
可先看java 多线程尝鲜。后续会专门讲多线程相关源码。