Java Object类

一、简介

Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,Object是所有类的父类,也就是说任何一个类的定义的时候如果没有明确的继承一个父类的话,那么它就是Object的子类;
比如下面两种情况其实是一样的:

class World{ }
class Worldextends Object { }

因为它是所有类的父类,所以它可以通过向上转型去实例化任何子类,比如:

class Cat(){}
class Dog(){}
public class Test{
     public static void main(String args[]) {
         Object cat = new Cat();          
         Object dog = new Dog();   
     }
}

下面来看一下Object类中都有哪些方法:
在这里插入图片描述

二、方法介绍

1、hashCode()

hash值:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

情景:考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)。

大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值。实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。

重写hashCode()方法的基本规则:

  • 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
  • 当两个对象通过equals()方法比较返回true时,则两个对象的hashCode()方法返回相等的值。
  • 对象用作equals()方法比较标准的Field,都应该用来计算hashCode值。

2、equals(Object o)

Object中equals方法的实现仅仅是比较了两个对象的地址。
源码如下:

public boolean equals(Object var1) {
        return this == var1;
    }

我们很大部分时间都是进行两个对象的比较,而不是判断地址是否相同,所以很多类对equals进行重写,比如String类重写用来判断两个对象的值是否相等。但是对于某些类来说只需要判断地址就行了,所以不用重写该类,比如:

  • Thread,由于每个线程对象天生就是独一无二的,重点表达是实体而不是值,不需要比较
  • java.util.regex.Pattern,正则表达式的类型也没有比较实例是否相同的必要
  • 父类复写了equals方法,并且是子类所需要的,如AbstractSet,AbstractList,AbstractMap,其子类毋需复写。
  • private或package private修饰的类,其方法不会被调用

equals方法的复写需要满足以下通用约定:

  • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true,就是自己和自己比较必须相等。
  • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回
    true,就是x若等于y,那么y也应该等于x。
  • 传递递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回
    true,那么 x.equals(z) 应返回 true。
  • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上
    equals 比较中所用的信息没有被修改。
  • 非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。

如无必要不要复写equals 方法,如果复写了此方法一定要记得复写hashCode方法,因为两个对象相等,它们的hashCode也要相等,下面是equals方法的常用步骤:

@Override
    public boolean equals(Object o) {
        //判断引用是否相等
        if (o == this) {
            return true;
        }
        //判断参数类型是否正确 如果o为null也会返回false

        //这里判断的是class类型,也有可能是接口类型,这样就允许实现这个接口的类之间进行比较

        //AbstractSet,AbstractList,AbstracMap的equals方法这一步都是比较的接口

        if (!(o instanceof PhoneNumber)) {
            return false;
        }
        //类型转换
        // AbstractSet的类型转换  Collection<?> c = (Collection<?>) o;

        PhoneNumber pNum = (PhoneNumber) o;

       // 判断重要字段的相等,如果使用的是接口,调用接口的方法获取字段

       // 对于基本类型 如果不是float或double 直接使用==比较

       // float使用Float.compare(float, float), 原因参考testFloat方法

        //double使用Double.compare(double, double) 同上

        //Float.equals和Double的equals都设计autobox,影响性能

        //引用类型继续调用其equals方法

       // 上述方法也同样适用于数组元素,如果要比较整个数值,使用Arrays.equals对应的方法

        //对象的某些字段能为Null,为了避免NPE,使用Objects.equals(Object, Object)

        return this.linNum == pNum.linNum &&
                this.areaCode == pNum.areaCode &&
                this.prefix == pNum.prefix;
    }

3、clone()

快速创建一个已有对象的副本,属于保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

clone方法首先会判对象是否实现了Cloneable接口,若无则抛出CloneNotSupportedException, 最后会调用internalClone. intervalClone是一个native方法,一般来说native方法的执行效率高于非native方法。

复制对象和复制引用区别

Person p = new Person(23, "zhang"); 
Person p1 = p; 
System.out.println(p); 
System.out.println(p1); 

输出结果:
com.pansoft.zhangjg.testclone.Person@2f9ee1ac
com.pansoft.zhangjg.testclone.Person@2f9ee1ac

可已看出,打印的地址值是相同的,既然地址都是相同的,那么肯定是同一个对象。p和p1只是引用而已,他们都指向了一个相同的对象Person(23, “zhang”) 。 可以把这种现象叫做引用的复制。

Person p = new Person(23, "zhang"); 
Person p1 = (Person) p.clone(); 
System.out.println(p); 
System.out.println(p1); 

输出结果:
com.pansoft.zhangjg.testclone.Person@2f9ee1ac
com.pansoft.zhangjg.testclone.Person@67f1fba0

从打印结果可以看出,两个对象的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量。

深拷贝和浅拷贝区别

public class Person implements Cloneable{ 
    private int age ; 
    private String name; 
    public Person(int age, String name) { 
        this.age = age; 
        this.name = name; 
    } 

    public Person() {} 
  
    public int getAge() { 
        return age; 
    } 

    public String getName() { 
        return name; 
    } 

    @Override 
    protected Object clone() throws CloneNotSupportedException { 
        return (Person)super.clone(); 
    }

}

Person中有两个成员变量,分别是name和age, name是String类型, age是int类型 

由于age是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式: 直接将源对象中的name的引用值拷贝给新对象的name字段, 或者是根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。

通过下面代码进行验证。如果两个Person对象的name的地址值相同, 说明两个对象的name都指向同一个String对象, 也就是浅拷贝, 而如果两个对象的name的地址值不同, 那么就说明指向不同的String对象, 也就是在拷贝Person对象的时候, 同时拷贝了name引用的String对象, 也就是深拷贝。

4、toString()

toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。虽然不及equals和hashCode方法必要,但良好的类描述将能提供充分和友好的信息,AbstractCollection的toString为其子类统一提供集合信息的描述。

public String toString() {
        return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
    }

由源码可看出Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。

5、wait()

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

  • 其他线程调用了该对象的notify方法。
  • 其他线程调用了该对象的notifyAll方法。
  • 其他线程调用了interrupt中断该线程。
  • 时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

6、notify()

该方法唤醒在该对象上等待的某个线程。

7、notifyAll()

该方法唤醒在该对象上等待的所有线程。

8、finalize()

垃圾回收器准备释放内存的时候,会先调用finalize()。

  1. 对象不一定会被回收。
  2. 垃圾回收不是析构函数。
  3. 垃圾回收只与内存有关。
  4. 垃圾回收和finalize()都是靠不住的,只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值