Object Class 结构
toString()
- toString源码, 默认返回格式:对象的 class 名称 + @ + hashCode 的十六进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
class RunoobTest {
public static void main(String[] args) {
// toString() with Object
Object obj1 = new Object();
System.out.println(obj1.toString());
Object obj2 = new Object();
System.out.println(obj2.toString());
Object obj3 = new Object();
System.out.println(obj3.toString());
}
}
以上程序执行结果为:
java.lang.Object@d716361
java.lang.Object@6ff3c5b5
java.lang.Object@3764951d
- 根据实际需求重写, 一般print类中所有属性的值
clone()
- clone()方法是用于浅克隆或者深克隆一个java对象的
- 但是要注意一个误区:clone方法是Object类的,并不是Cloneable接口的,Cloneable只是一个标记接口,标记接口是用于标记实现该接口的类具有某种该接口标记的功能,常见的标记接口有三个:Serializable、Cloneable、RandomAccess,没有实现Cloneable接口,那么调用clone方法就会爆出CloneNotSupportedException异常。
- 要使得一个java类可以实现克隆,那么该类就需要实现Cloneable接口,并且重写基类Object的clone方法,其实Object的clone方法就已经默认有克隆的能力了,并且实现的是浅克隆,那为什么一定得重写clone方法呢?
protected native Object clone() throws CloneNotSupportedException;
- 方法被native关键字修饰,并且该方法没有方法体,没有方法体的方法我们认为是抽象方法,可是Object类并不是一个抽象类,而是一个具体类,那为什么还能容纳抽象方法呢,其实就是native关键字在起作用,被native关键字修饰的方法属于本地方法,表示Java的作用范围已经无法达到,底层会去调用C语言或者C++的库。
- 该方法是被protected修饰,这就表明我们在子类中不重写此方法,就在子类外无法访问,因为这个protected权限是仅仅能在Object所在的包和子类能访问的,这也验证了子类重写父类方法权限修饰符可以变大但不能变小的说法。
- 所以我们要使得一个类具有clone的能力,可以先实现Cloneable接口,再重写clone方法,方法内部去调用了父类的clone方法,其实是为了扩大访问权限,也可以把protected改为public,以后再继承就不用重写了。当然这只是浅克隆的clone函数,深克隆就需要修改clone的方法体了。
public class Mytest implements Cloneable{
/**
* 重写clone方法,调用父类默认的clone方法,实现的是浅克隆
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
————————————————
版权声明:本文为CSDN博主「can_chen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/can_chen/article/details/106816513
finalize()
- Object finalize() 方法用于实例被垃圾回收器回收的时触发的操作。
- 当 GC (垃圾回收器) 确定不存在对该对象的有更多引用时,对象的垃圾回收器就会调用这个方法。
import java.util.*;
class RunoobTest extends GregorianCalendar {
public static void main(String[] args) {
try {
// 创建 RunoobTest 对象
RunoobTest cal = new RunoobTest();
// 输出当前时间
System.out.println("" + cal.getTime());
// finalize cal
System.out.println("Finalizing...");
cal.finalize();
System.out.println("Finalized.");
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
输出结果:
Sun Oct 11 11:36:46 CST 2020
Finalizing...
Finalized.
getClass()
- Object getClass() 方法用于获取对象的运行时对象的类, 可以常在反射中用到
import java.util.ArrayList;
class RunoobTest {
public static void main(String[] args) {
// getClass() with Object
Object obj1 = new Object();
System.out.println("obj1 的类为: " + obj1.getClass());
// getClass() with String
String obj2 = new String();
System.out.println("obj2 的类为: " + obj2.getClass());
// getClass() with ArrayList
ArrayList<Integer> obj3 = new ArrayList<>();
System.out.println("obj3 的类为: " + obj3.getClass());
// 创建 RunoobTest 类的对象
RunoobTest obj4 = new RunoobTest();
System.out.println("obj3 的类为: " + obj4.getClass());
}
}
以上程序执行结果为:
obj1 的类为: class java.lang.Object
obj2 的类为: class java.lang.String
obj3 的类为: class java.util.ArrayList
obj4 的类为: class RunoobTest
- 在获得类型类之后,你就可以调用其中的一些方法获得类型的信息了,主要的方法有:
getName():String:获得该类型的全称名称。
getSuperClass():Class:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。
getInterfaces():Class[]:获得该类型实现的所有接口。
isArray():boolean:判断该类型是否是数组。
isEnum():boolean:判断该类型是否是枚举类型。
isInterface():boolean:判断该类型是否是接口。
isPrimitive():boolean:判断该类型是否是基本类型,即是否是int,boolean,double等等。
isAssignableFrom(Class cls):boolean:判断这个类型是否是类型cls的父(祖先)类或父(祖先)接口。
getComponentType():Class:如果该类型是一个数组,那么返回该数组的组件类型。
equals(Object obj)
- equals方法是 判断两个对象是否相等
- equals源码
public boolean equals(Object obj) {
return (this == obj);
}
- 在Object类中的equals方法当中, 用“ ==”判断的是两个java对象的内存地址是否相等
- 我们应该判断两个java对象的内容是否相等。所以需要子类重写equals。
- 基本数据类型,判断相等用“==”
-
引用数据类型,判断相等必须重写equals()方法,不然比较的是内存地址
hashCode()
什么是hashcode
public native int hashCode(); //native 关键字修饰的方法,用 C/C++ 语言实现
- java根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为hashcode
hashcode的主要作用
- 对于hashset, hashmap, hashtable等方法需要判断数据是否一样, 可以使用 equals() 方法进行逐个比较, 但是这种方如果数据量特别特别大,采用 equals() 方法进行逐个对比的效率肯定很低. 最好的解决方案就是哈希表.
关于重写equals必须重写hashcode方法
- 每个覆盖了equals方法的类中,必须覆盖hashCode。如果不这么做,就违背了hashCode的通用约定,也就是上面注释中所说的。进而导致该类无法结合所以与散列的集合一起正常运作,这里指的是HashMap、HashSet、HashTable、ConcurrentHashMap。 — Effective Java 第三版
具体Example
- 我们知道HashMap中的key是不能重复的,如果重复添加,后添加的会覆盖前面的内容。那么我们看看HashMap是如何来确定key的唯一性的。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 查看代码发现,它是通过计算Map key的hashCode值来确定在链表中的存储位置的。那么这样就可以推测出,如果我们重写了equals但是没重写hashCode,那么可能存在元素重复的矛盾情况
public class Employee {
private String name;
private Integer age;
public Employee(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(age, employee.age);
}
public static void main(String[] args) {
Employee employee1 = new Employee("冰峰", 20);
Employee employee2 = new Employee("冰峰", 22);
Employee employee3 = new Employee("冰峰", 20);
HashMap<Employee, Object> map = new HashMap<>();
map.put(employee1, "1");
map.put(employee2, "1");
map.put(employee3, "1");
System.out.println("equals:" + employee1.equals(employee3));
System.out.println("hashCode:" + (employee1.hashCode() == employee3.hashCode()));
System.out.println(JSONObject.toJSONString(map));
}
- 按正常情况来推测,map中值存在两个元素,employee2和employee3, 因为employee1和employee3一样(equal被重写了) 会被覆盖
- 实际执行结果
- 出现这种问题的原因就是因为没有重写hashCode,导致map在计算key的hash值的时候,绝对值相同的对象计算除了不一致的hash值。
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
- 加入重写hashcode, 根据name和age产生hash值
- 运行结果
关于利用hashcode和equals比较两个object
- 如果hahcode不一样则一定不一样
- 如果两个object的hashcode一致, 则说明有可能一样(因为存在hash冲突)
wait() and notify() — to be continued
Object wait() 方法让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
当前线程必须是此对象的监视器所有者,否则还是会发生 IllegalMonitorStateException 异常。
如果当前线程在等待之前或在等待时被任何线程中断,则会抛出 InterruptedException 异常。
notifyAll()
wait(long timeout)
wait(long timeout, int nanos)