美文网首页
java源码解析-object

java源码解析-object

作者: 狗肉是只猫 | 来源:发表于2020-04-09 17:33 被阅读0次

    引言:儿子,儿啊,我是你亲爸爸

    Object方法分类

    1. getClass

         public final native Class<?> getClass();
    

    作用:
    用于获取对象的类类型

    解读:

    1. native修饰,标识此方法底层由c实现
    2. 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。

    解读:

    1. native修饰,标识此方法底层由c实现
    2. 该方法未使用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。

    解读:

    1. 该方法未使用final修饰,子类可重写该方法,自己实现判等逻辑。
    2. 如果子类未重写此方法,比较两个对象是否相等,依据是否指向同一块内存来判断。

    使用示例:

    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;
    

    作用:
    用于对象的拷贝,基于原对象,复制出一个新对象

    解读:

    1. 该方法未使用final修饰,子类可重写该方法,自己实现复制对象属性的内容。
    2. 调用该方法可能会抛出CloneNotSupportedException,使用该方法的对象的类必须实Cloneable接口
    3. native方法,底层由c实现
    4. 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());
        }
    

    作用:
    需要将对象打印出来时,将对象转换成字符串时会调用该方法

    解读:

    1. 该方法public修饰,所有子类都会继承该方法,不重写的情况下,对象的输出遵循此合适
    2. 默认数据格式 对象的类类型的名称 + @ + 该对象的散列值的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(垃圾回收器)决定回收一个不被其他对象引用的对象时调用,子类可重写该方法来释放资源

    解读:

    1. protected修饰,非同包的子类的对象想调用此方法,必须重写该方法
    2. 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方法只会被执行一次。

    相关文章

      网友评论

          本文标题:java源码解析-object

          本文链接:https://www.haomeiwen.com/subject/qcxhmhtx.html