10_Object

文章目录

  • API
  • Object成员方法
    • getClass方法
      • 方法的声明
      • Class对象
      • 作用
    • toString方法
    • equals方法
      • 重写equals方法
        • 重写equals方法的注意事项
    • hashCode方法
      • 方法的声明
      • 方法的作用
      • 默认实现
      • 方法的重写
      • 如何重写
      • 为什么要同时重写hashCode和equals方法
    • finalize方法
      • 方法的声明
      • 测试finalize方法
    • clone方法
      • 方法的声明
      • 方法的作用
      • 使用clone方法的步骤
      • 浅克隆
      • 深克隆

API

方法说明
指一些已经预先定义好的,暴露给外界使用的方法或者工具

Object成员方法

隐式继承

getClass方法

方法的声明

public final native Class<?> getClass();

说明

  1. 首先它的访问权限修饰符是public,可以任意访问,没有访问权限问题。

  2. final修饰它,表示它无法被重写。

  3. native表示该方法是一个本地方法,指的是Java调用其它语言(主要是C/C++)的实现来完成功能的方法。本地方法不需要方法体,我们也不会考虑它的实现细节。(该方法的作用可以通过查阅API文档了解)

  4. Class是返回值类型,表示该方法需要返回一个Class对象

    上述表示泛型,关于泛型后面会详细讲。

作用通过一个本地方法的实现,去获取Class对象。

Class对象

具体来说,某个类的运行时类对象,就是该类的Class对象

某个类的Class对象当中,包含了该类的所有类型信息(比如类名是啥,有哪些方法、变量、构造器等

于是程序员在程序的运行时期,只需要获取该类的Class对象,就能够获取类型信息了。

作用

  1. getClass()方法只是获取Class对象,该方法不负责创建Class对象。
  2. 某个类的类加载在一次程序运行过程中,仅有一次。
    所以某个类的运行时类对象(Class对象)也必然是唯一的!!!
  3. 可以判断两个引用所指向的对象的类型是否相同

toString方法

源码

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

}

说明
getClass().getName():全类名
Integer.toHexString(hashCode()):十六进制地址值

作用
返回该对象的字符串表示。

重写toString的方法

public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }

注意事项

  1. toString()方法可以快速自动生成,仍然使用IDEA快捷键Alt + Insert完成。

  2. toString()方法,普遍来说就是为了完成打印成员变量取值的,不要在里面写一些奇怪的代码。

  3. 如果类中有(自定义)引用数据类型成员变量,也需要重写它的toString方法,不然就会打印地址值了。

equals方法

源码

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

说明
参数需要传入一个对象
默认实现:判断当前这个对象是否跟参数对象一样

只有当两个引用完全指向同一个对象时,方法才会返回true,否则都会返回false。

重写equals方法

equals 方法在非空对象引用上实现相等关系

  1. 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
  2. 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
  3. 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
  4. 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  5. 排他性:当比对的不是同种类型的对象或者是一个null时,默认返回false
    @Override
    public boolean equals(Object o) {
        // 自反性
        if (this == o) return true;
        // 排他性
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        // 比较成员变量的值
        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }
重写equals方法的注意事项
  1. 在实现排他性时,实际上有两种选择:

    1. 使用getClass方法比较。 这个比较是比较苛刻的,只有在完全是同一个类型时才会返回true

    2. 使用instanceof比较。

      引用 instanceof 类名
    

    表示判断引用所指向的对象的具体类型,是否是后面类名的对象或者子类对象,如果是就返回true,如果不 是就返回false。

    这个比较的条件就比较宽松了,可以允许传入子类对象。(当子类对象的父类成员和父类对象相同时,equals方法仍然返回true)

     @Override
     public boolean equals(Object o) {
         // 自反性
         if (this == o) return true;
         // 排他性
         //if (o == null || getClass() != o.getClass()) return false;
         if(o == null) return false;
    
         // 类型扩大,可以判断子类
         if(!(o instanceof Student)) return false;
    
         Student student = (Student) o;
         // 比较成员变量的值
         if (age != student.age) return false;
         return name != null ? name.equals(student.name) : student.name == null;
     }
    
  2. equals方法也是可以用快捷键自动生成的,使用快捷键alt + insert。而且可以选择在实现排它性时的方式。

  3. 浮点数比较特殊,它具有规格化和非规格化的区别,还有非数(NaN),无穷大,无穷小很多特殊的概念,正常情况下,如果仅仅比较数值,用==比较相等是够用的。但为了避免因浮点数特殊值,而出现的错误。实际开发中,从严谨角度出发,浮点数的比较仍然建议使用,对应包装类型的compare方法去比较浮点数的大小:

    1. Float.compare(float a,float b)
    2. Double.compare(double a,doublet b)

    这两个方法在,a < b时返回-1(负数),在a>b时,返回1(正数),只有在两个浮点数相等时,才会返回0

  4. 如果类中有引用数据类型成员变量,需要去调用它们的equals方法完成比较。这就意味着还需要重写这个类的equals方法。

  5. 财务金额上的运算是不推荐使用浮点数的,会出现精度问题。推荐使用BigDecimal这个类完成运算。
    eg:

    // 不会出现精度问题
    public static void main(String[] args) {
        BigDecimal b1 = new BigDecimal("1.0");
        BigDecimal b2 = new BigDecimal("0.9");

        BigDecimal sub = b1.subtract(b2);
        System.out.println(sub);

        BigDecimal addNumber = b1.add(b2);
        System.out.println(addNumber);
    }

hashCode方法

映射不要求元素一一对应,允许出现多对一,但绝不允许一对多。

方法的声明

public native int hashCode();

很显然,它是一个本地方法,这个方法也没有任何参数,返回值是一个int类型整数。

方法的作用

  1. 返回该对象的哈希码值
  2. 支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

默认实现

实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。

方法的重写

  1. 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals比较时所用的信息没有被修改。

  2. 如果根据 equals(Object)方法,两个对象是相等的,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。

  3. 如果根据 equals(java.lang.Object)方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

  4. hashCode方法和equals方法是息息相关的,要重写必须一起重写

    1. 一起重写后,必须符合equals方法返回true,那么这两个对象的哈希值必须相同的规则。
    2. 这是因为equals是判断对象相等的,对象相等后,就相当于是上述映射定义中,集合A中的同一个元素,那么它们哈希映射的结果必然相同。这是映射定义强制要求的。
    3. 哈希冲突是允许存在(不允许也不可能)的,但是我们要清楚,设计出更好的哈希算法,尽量避免哈希冲突,可以提升哈希表性能。

如何重写

使用快捷键Alt + Insert

为什么要同时重写hashCode和equals方法

首先hashCode方法中规定:

  1. 两个对象相等,哈希值一定相等。
  2. 两个对象不相等,哈希值不要求完全不相等。

那么这里就很明显存在一个问题:

如何判断对象相等? ----> 答:依赖类中的equals方法!

finalize方法

方法的声明

protected void finalize() throws Throwable { }

说明

  • 首先注意访问权限是protected,这说明在不重写访问权限的情况下,仅能够在自身类中创建自身对象,才能调用这个方法。

  • 其次它的方法体是空的,功能没有实现,这种设计的目的是为了让子类自己去重写该方法的实现。这种做法比起抽象方法的强制子类实现,要更加灵活,而且可以用在普通类中。

  • 最后,它的方法声明中有一个throws Throwable, 这是方法抛出异常列表的声明。

  • Java当中的finalize方法就是"模仿"析构函数设计的,finalize方法也会在对象销毁时自动被调用,

测试finalize方法

public class Demo {
 public static void main(String[] args) {
   	// 匿名对象,理论上很快成为垃圾对象
     new Student();
     // 通知GC进行垃圾回收
     System.gc();
 }
}
class Student{
 @Override
 protected void finalize() throws Throwable {
     System.out.println("模拟释放资源");
 }
}

在Java中,所有的资源释放,都必须依赖程序员手动完成,所以以后大家做I/O操作,网络操作,数据库操作等需要占用系统资源的操作时,一定不要忘记在用完后,释放系统资源!

clone方法

方法的声明

clone方法的方法声明为:

protected native Object clone() throws CloneNotSupportedException;

这里需要注意的地方有:

  1. 首先注意访问权限,它的访问权限是protected。这意味着:

    1. 一般来说,只能在子类当中,创建子类自身对象才能够调用该方法(方法调用位置,肯定不是同包)。
    2. 让一个类自身克隆自身,一般都没有多大意义,所以建议在子类中重写方法访问权限。
  2. 它是一个本地native方法,没有方法体。(依赖本地方法实现创建对象,不同于new对象)

  3. 返回值类型是Object

    这里,也建议在子类方法中重写这个返回值类型。

  4. throws CloneNotSupportedException是方法抛出异常的声明,

方法的作用

Object类当中的clone方法默认实现,就是得到一个独立的,和原先对象成员一致的新对象。

使用clone方法的步骤

  1. 重写clone方法,修改权限
  2. 把返回值类型修改成自身类型
  3. 要求实现clone方法的类,实现cloneable接口
    cloneable是一个空接口,起到标记的作用
    像Cloneable这种没有声明定义任何成员的,一个空接口,它其实就起到一个标记的作用,称之为"标记接口"。

浅克隆

概念
如果类中有引用数据类型的成员变量,那么clone方法的使用就要格外注意了:

  1. Java当中,Object类的clone方法的默认实现是完全直接拷贝一份成员变量。
    1. 对于基本数据类型的成员变量来说,没有任何问题,直接拷贝值。
    2. 但对于引用数据类型而言,拷贝的是引用。这意味着克隆后的引用和原先的引用指向同一个对象。
  2. 这样的话,使用任何一个引用去修改对象的状态,都会互相影响,这样的两个对象就不是完全独立的了。

像以上Object类当中的clone方法的实现,直接拷贝一份成员变量,不管引用数据类型成员变量引用,所指向的对象。我们称之为"浅克隆"。

代码演示

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

10_Object_第1张图片

深克隆

概念
如果能够让引用数据类型成员变量之间也能相互独立,克隆后获取真正独立的两个对象。我们称之为"深度克隆"。

  1. 将引用指向的对象,也克隆一份。
  2. 然后让克隆后的引用指向它。

代码演示

@Override
protected User clone() throws CloneNotSupportedException{
	// 1. 深克隆在浅拷贝的基础上进行修改
	User cloneUser = (User)super.clone();
	// 2. 把Address这个引用再clone一份
	Address cloneAddress = this.address.clone();
	// 3.赋值给克隆出的User对象的address,就是将拷贝引用指向拷贝对象
	cloneUser.address = cloneAddress;
	return cloneUser;
}

10_Object_第2张图片

你可能感兴趣的:(JavaSE,java)