引言:儿子,儿啊,我是你亲爸爸
1. getClass
public final native Class<?> getClass();
作用:
用于获取对象的类类型
解读:
- native修饰,标识此方法底层由c实现
- final关键字修饰,子类不可重写该方法,保证对象类类型的唯一性,避免重写改方法后,出现不同类的对象,获取到相同的类类型
使用示例:
public class ClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
SimpleClass bObj = new SimpleClass();
// 调用对象的getClass()方法来获取类类型
Class bClass = bObj.getClass();
// 输出:package com.mypro.classdemo-->SimpleClass
System.out.println(bClass.getPackage() + "-->" + bClass.getSimpleName());
// 另外两种获取类类型的方式,等价于obj.getClass()
Class bClass1 = SimpleClass.class;
Class bClass2 = Class.forName("com.mypro.classdemo.SimpleClass");
// 输出 true
System.out.println(bClass == bClass1);
// 输出 true
System.out.println(bClass1 == bClass2);
// 输出 true
System.out.println(bClass == bClass2);
}
}
class SimpleClass {
}
2. hashCode
public native int hashCode();
作用:
获取对象的散列值,散列值是所有需要判断散列值集合的处理条件,常见的依赖散列值的集合有HashSet、HashMap。
解读:
- native修饰,标识此方法底层由c实现
- 该方法未使用final修饰,子类可重写该方法,自己实现散列值的计算方式
使用示例:
public class HashDemo {
// 存放到map的固定值
private static final Object PRESENT = new Object();
public static void main(String[] args) {
Person person1 = new Person("110119");
Person person2 = new Person("110120");
Person person3 = new Person("110119");
// true
System.out.println(person1.hashCode() == person2.hashCode());
// false
System.out.println(person1.hashCode() == person3.hashCode());
// 存放hashSet
HashSet<Person> personSet = new HashSet<>();
personSet.add(person1);
personSet.add(person2);
personSet.add(person3);
// 只重写hashCode,未重写equals,虽然person1和person3的散列值相同,但都会被保留
System.out.println(personSet.size());
}
}
class Person{
private String idCard;
public Person(String idCard) {
this.idCard = idCard;
}
public String getIdCard() {
return idCard;
}
// 重写hashCode方法
@Override
public int hashCode() {
return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
.append(idCard)
.toHashCode();
}
}
此处散列算法使用了apache commons工具包中的方法,hash算法的具体实现,可阅读commons工具包源码。
HashSet与HashMap之间的关联
查看HashSet的源码片段
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable{
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public int size() {
return map.size();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
从源码中可以看出,HashSet内部维护了一个HashMap,将此map的键作为HashSet管理的主要对象,从而实现一种线性结构。
HashMap的内部实现逻辑,后续会单独开文章分析。可从互联网中寻找相关信息。
3. equals
public boolean equals(Object obj) {
return (this == obj);
}
作用:
获取对象的散列值,散列值是所有需要判断散列值集合的处理条件,常见的依赖散列值的集合有HashSet、HashMap。
解读:
- 该方法未使用final修饰,子类可重写该方法,自己实现判等逻辑。
- 如果子类未重写此方法,比较两个对象是否相等,依据是否指向同一块内存来判断。
使用示例:
public class EqualsDemo {
public static void main(String[] args) {
Person person1 = new Person("110119");
Person person2 = new Person("110120");
Person person3 = new Person("110119");
// false
System.out.println(person1.equals(person2));
// true
System.out.println(person1.equals(person3));
// false
System.out.println(person1 == person3);
}
}
class Person {
private String idCard;
public Person(String idCard) {
this.idCard = idCard;
}
public String getIdCard() {
return idCard;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return new EqualsBuilder()
.append(idCard, person.idCard)
.isEquals();
}
}
重写Person类的equals方法后,即可根据身份证号这个唯一标识来判断对象是否相等。
当equals成立时,==不一定成立,但当==成立时,equals一定成立。
equals与hashCode最佳实践
equals方法需要满足:
- 自反性 对于非null引用值x,x.equals(x) 必须为true
- 对称性 对于非null引用值x、y,当且仅当x.equals(y) 返回true,y.equals(x) 必须返回true
- 传递性 对于非null引用值x、y、z,如果x.equals(y)返回true,并且y.equals(z)返回true,x.equals(z) 必须返回true
- 一致性 对于非null引用值x、y,如果x.equals(y)返回true,那么在未修改值的情况下,多次调用equals都应该返回true
equals和hashCode之间的关系:
- 覆盖equals方法时,必须覆盖hashCode方法
- equals相等的情况下,,hashCode方法必须返回相同的整数
- 即使equals不相等,hashCode也有可能返回相同的值,这种情况叫做hash冲突
- 良好的散列算法,能够降低hash冲突发生的频率,提高HashMap的查询性能。
最佳实践:
public class HashAndEqualsDemo {
// 存放到map的固定值
private static final Object PRESENT = new Object();
public static void main(String[] args) {
Person person1 = new Person("110119");
Person person2 = new Person("110120");
Person person3 = new Person("110119");
// true
System.out.println(person1.hashCode() == person2.hashCode());
// false
System.out.println(person1.hashCode() == person3.hashCode());
// false
System.out.println(person1.equals(person2));
// true
System.out.println(person1.equals(person3));
// false
System.out.println(person1 == person3);
// 存放hashSet
HashSet<Person> personSet = new HashSet<>();
personSet.add(person1);
personSet.add(person2);
personSet.add(person3);
// 重写hashCode、equals方法后,person1.equals(person3),按照添加顺序,只会保留person3
System.out.println(personSet.size());
}
}
class Person{
private String idCard;
public Person(String idCard) {
this.idCard = idCard;
}
public String getIdCard() {
return idCard;
}
// 重写hashCode方法
@Override
public int hashCode() {
return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
.append(idCard)
.toHashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return new EqualsBuilder()
.append(idCard, person.idCard)
.isEquals();
}
}
4. clone
protected native Object clone() throws CloneNotSupportedException;
作用:
用于对象的拷贝,基于原对象,复制出一个新对象
解读:
- 该方法未使用final修饰,子类可重写该方法,自己实现复制对象属性的内容。
- 调用该方法可能会抛出CloneNotSupportedException,使用该方法的对象的类必须实Cloneable接口
- native方法,底层由c实现
- protected修饰,非同包的子类的对象想调用此方法,必须重写该方法
深克隆和浅克隆:
浅克隆会把值类型复制一份到新对象,引用类型只复制引用地址,复制后的对象与原对象的引用类型是指向同一块内存的,因此两个对象中的引用类型有任一变动,都会影响到对方。
深克隆会把值类型、引用类型都复制一份,原对象与克隆对象不会相互影响。
使用示例:
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
// 浅克隆示例
Person person = new Person("zhangsan",new Address("china beijing"));
Person personClone = person.clone();
personClone.setName("li si");
// 更改引用对象的值
personClone.getAddress().setLocation("china shanghai");
// 输出:Person{name='zhangsan', address=Address{location='china shanghai'}},原对象受到了影响
System.out.println(person);
// 输出: Person{name='li si', address=Address{location='china shanghai'}}
System.out.println(personClone);
// 深克隆示例
Animal animal = new Animal(5,new Address("japan"));
Animal animalClone = animal.clone();
animalClone.setAge(8);
// 更改引用对象的值
animalClone.getAddress().setLocation("china");
// 输出:Animal{age=5, location=Address{location='japan'}},原对象不受影响
System.out.println(animal);
// 输出:Animal{age=8, location=Address{location='china'}}
System.out.println(animalClone);
}
}
class Person implements Cloneable{
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
/**
* 浅克隆
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
}
class Animal implements Cloneable{
private int age;
private Address address;
public Animal(int age, Address address) {
this.age = age;
this.address = address;
}
/**
* 深克隆
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Animal clone() throws CloneNotSupportedException {
Animal animal = (Animal) super.clone();
animal.setAddress(this.address.clone());
return animal;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Animal{" +
"age=" + age +
", location=" + address +
'}';
}
}
class Address implements Cloneable{
private String location;
public Address(String location) {
this.location = location;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
@Override
public String toString() {
return "Address{" +
"location='" + location + '\'' +
'}';
}
}
实现深克隆的几种方式:
- 所有对象都实现克隆方法
- 通过构造方法实现深克隆
- 使用JDK自带的字节流实现深克隆
- 使用第三方工具实现深克隆,比如ApacheCommonsLang
- 使用JSON工具类实现序列化、反序列化
5. toString
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
作用:
需要将对象打印出来时,将对象转换成字符串时会调用该方法
解读:
- 该方法public修饰,所有子类都会继承该方法,不重写的情况下,对象的输出遵循此合适
- 默认数据格式 对象的类类型的名称 + @ + 该对象的散列值的16进制
使用示例:
public class ToStringDemo {
public static void main(String[] args) {
Person person = new Person("zhangsan");
Animal animal = new Animal("dog");
// 输出:person{name='zhangsan'} ,重写toString,使用我们自定义的输出方式
System.out.println(person);
// 输出:com.mypro.tostring.Animal@6a6824be,未重写toString,遵循继承方法
System.out.println(animal);
}
}
class Person{
private String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
'}';
}
}
class Animal{
private String type;
public Animal(String type) {
this.type = type;
}
}
最佳实践:
Object中的toString()方法的输出是语义不明朗的,我们自定义的需要格式化输出的类,都要重写toString()方法
6. notify、 notifyAll、wait
public final native void notify();
public final native void notifyAll();
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 > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
作用:
- wait 方法释放锁,并使当前线程进入阻塞状态
- notify 唤醒单个监听该对象阻塞状态的线程,如果有多个,则随机唤醒一个
- notifyAll 唤醒所有监听该对象阻塞状态的线程
解读:
- 三个方法都使用 native + final 关键字修饰,不可重写,且底层由c实现
- wait方法共由3个, wait() 在不被interrupt的情况下会一直等到notify,wait(long timeout) 则最多等待notify timeout毫秒后,抛出异常,wait(long timeout, int nanos) 相比与wait(long timeout)增加了纳秒单位,更精准
- 三个方法都需要在synchronized 修饰的代码块中调用
使用示例:
public class WaitNotifyDemo {
public static final Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread1();
Thread thread2 = new Thread2();
thread1.start();
// 线程1 启动后,休眠300ms,避免线程2先被调度,notify被先调用,wait就会一直阻塞住
Thread.sleep(300);
thread2.start();
}
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println("Thread 1 start");
try {
// wait 会释放锁,并自身进入阻塞状态,此时线程2会拿到锁,并执行
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread 1 get lock");
}
super.run();
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object) {
try {
// sleep 不会释放锁
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 休眠完成后,此时调用notify,Thread1从阻塞状态重新变成活跃状态,开始重新竞争锁资源
object.notify();
// notify 不会让出锁资源,thread2输出后,thread1才会输出
System.out.println("thread 2 execute notify");
}
}
}
}
7. finalize
protected void finalize() throws Throwable
作用:
GC(垃圾回收器)决定回收一个不被其他对象引用的对象时调用,子类可重写该方法来释放资源
解读:
- protected修饰,非同包的子类的对象想调用此方法,必须重写该方法
- finalizer方法的调用时机由jvm来决定,进行垃圾回收时,会调用该方法
说明:
- 任何对象的 finalize 方法只会被 JVM 调用一次
- finalize()方法引发的任何异常都会导致该对象的终止被暂停,否则被忽略
- 一般不需要重写该方法,java的垃圾回收机制一般都能很好的回收对象
示例:
public class GcDemo {
public static GcDemo FINALIZE_OBJ = null;
public void printHello() {
System.out.println("hello");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize execute");
// 重新赋值,则不会被回收,同一个对象只会执行一次
GcDemo.FINALIZE_OBJ = this;
}
public static void main(String[] args) throws InterruptedException {
FINALIZE_OBJ = new GcDemo();
FINALIZE_OBJ = null;
// 会触发 finalize()方法,FINALIZE_OBJ会被重新赋值
System.gc();
// 休眠等待finalize()执行完成,优先级较低
Thread.sleep(1000);
if (FINALIZE_OBJ != null) {
FINALIZE_OBJ.printHello();
} else {
System.out.println("FINALIZE_OBJ is null");
}
FINALIZE_OBJ = null;
// finalize()方法只会被执行1次,FINALIZE_OBJ不会被重新赋值
System.gc();
Thread.sleep(1000);
if (FINALIZE_OBJ != null) {
FINALIZE_OBJ.printHello();
} else {
System.out.println("FINALIZE_OBJ is null");
}
}
}
第一次会执行hello方法,因为gc时会调用finalize方法,第二次会打印出FINALIZE_OBJ is null,同一个对象的finalize方法只会被执行一次。