Java.lang.Object 类

作者: 善倾 | 来源:发表于2018-09-21 15:45 被阅读0次

    Object 类是 Java 中所有对象的父类,它就是被设计进行扩展的。它有一些方法,用的非常广泛,很多时候子类还会根据实际需求重写这些方法。重写这些方法必须遵循通俗的约定,这种约定不是语法层次上的,而是一种程序编写的共识。如果不遵循这种共识,那么在和其他默认遵循这个共识的类搭配使用时肯定会出现问题。

    比如将对象作为元素插入 HashMap 集合中去时,它就要求,如果这个对象重写了 equals() 方法,就必须同时重写 hashCode() 方法,否则会破坏它不允许出现重复键的规定。

    int hashCode() 方法

    //hashCode()源码
    public native int hashCode();
    

    可见此方法是调用了本地方法实现的,它返回的是一个 int 类型的值。平时在直接输出 toString() 方法时输出的是这个 int 类型数据转成 16 进制表示后的字符串。基本上是不会有单独主动去重写 hashCode() 方法的情况,都是因为某个类有重写 equals() 方法的需求,所以就夹带着必须要重写它的 hashCode() 方法来保证,某个类的两个对象在逻辑上的 equals 的,那么它的 hashCode 值必须相等,这就是关于重写 equals() 方法所必须能要遵循的约定。

    假如不遵循此规定,那么在使用 HashMap 进行数据存储时,依据它先使用哈希值确定下标索引,然后再利用 equals() 方法进行判断是否相同,相同则直接进行替换的规则时,就会产生逻辑上相同的对象同时存在于集合中,这就破坏了 HashMap 的规则。这就是为什么重写了 equals() 方法就必须要重写 hashCode() 方法的原因,这是一个必须要遵守的编程规约。

    boolean equals(Obj obj) 方法

    //Object 类的源码
    public boolean equals(Object obj){
        return (this == obj);
    }
    

    引用变量存储的地址也是分类型的,如上,this 存放的是某个具体对象的地址,obj 存放的是 Object 类型的地址。但是仍然能够进行比较,这就涉及到多态比较的问题。

    任何变量使用 == 比较的是都是这个变量的内容,只不过基本数据类型的内容是值,引用数据类型的内容保存的是指向堆区对象的地址。

    但是在实际编码当中会存在判断两个对象存储的内容逻辑上相同(内存中地址是否相同无所谓)的需求,所以很多类都会重写其父类 Object 的 equals(Object obj) 方法,Integer 和 String 类就有这样的需求,从它们的源码中可以发现,它们重写了此方法,定义了

     //Integer.equals(Object obj)在 JDK 中的源码
     public boolean equals(Object obj) {
            if (obj instanceof Integer) {
                return value == ((Integer)obj).intValue();
            }
            return false;
        }
     //String.equals(Object anObject)在 JDK 中的源码
     public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }   
    

    toString() 方法

    Object 类提供这个方法是为了返回对象的字符串表示形式,让人能够快速的了解这个对象的用途。

    //Object 类的源码
    public String toString(){
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    

    默认的 toString() 方法输出的是,这个对象对应类的名字 + @ + 这个对象哈希值的16进制字符串组成的字符串。

    这里需要提到的一点是,System.out 对象的public void println(Object x)方法在接收一个对象引用时候,会默认调用这个对象的 toString() 方法,如果对象为 null ,则直接输出 null ,并不会造成空指针异常。

    //System.out对象的println(Object x)方法
    public void println(Object x) {
        String s = String.valueOf(x);//调用valueOf() 方法
        synchronized (this) {
            print(s);//输出字符串s
            newLine();
        }
    }
    //String类的valueOf(OBject obj)方法
    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }
    

    通过上面的 JDK 可以看到,System.out 对象的 println() 方法先调用 String 类的 valueOf() 方法得到返回值,而 valueOf() 方法就会判断 obj 是否为 null ,如果是则返回 null ,如果不是则再调用 obj 的 toString() 方法。

    java.lang.Integer

    Integer 类是 int 类型的包装类,Java 是完全面向对象的语言,所以给八个基本数据类型都有它对应的包装类。

    Integer n1 = 127以这种形式声明 Integer 类型变量时,存在一个自动装箱的概念,即 JDK 会自动的将127转换成 Integer 类型的对象,而不是 int 类型的数据。

    当这种形式声明的 Integer 类对象的数值范围在 [-128,127] 之间时,会在方法区的常量数据区开辟内存空间,再次声明同样数值的对象时,直接指向存在相同值的对象。JVM 为什么要这样设计呢?

    超过这个范围的数据和直接使用new Integer(120)则都是直接在堆区开辟对象内存空间,和引用数据类型对象没有任何不同。

    Integer 类重写了 Object.equals(Object obj) 方法,当两个对象类型相同,且值相同就判断为同等。看上面提供的 JDK 源代码就知道了啊!

    java.lang.String

    String 字符串类型,可以说用的是最广泛的了。创建对象有三种方式

    • 使用 new 关键字:String s = new String("keqi")
    • 直接赋值:String s = "keqi"
    • 使用字符串连接符 ‘+’ :String s = "keqi" + "is so cool";

    第一种方式使用的是String s1 = "keqi";String s2 = "keqi";,这会直接在常量数据区开辟对象内存空间。所以,采用 == 判断两个字符串同值的对象得到结果为 true。

    第二种方式使用的是String s3 = new String("keqi");String s4 = new String("keqi");,会先在方法区的常量数据区开辟对应的字符串对象,然后再在堆区开辟对象内存空间,以后使用的也是堆区的内存对象。所以,采用 == 判断两个字符串同值的对象得到结果为 false。因为尽管它在常量数据区是同一个内存对象,但是在堆区是两个不同的内存对象。

    至于 Stirng.equals(Object anObject) 方法判断的结果当然都是 true 啦

    最初接触哈希值的概念是在数据结构当中学习散列表的时候,哈希算法计算出来的哈希值是为了减少碰撞,快速定位,能够利用这种数据结构更快速的查找和存储对象。Java 集合框架中的 HashMap 、HashSet 的底层数据结构就是散列表。Object 类有一个名为 hashCode() 的抽象方法,每一个子类都必须实现这个方法,这是为了支持散列表存储而专门设置的。

    String 类实现了 hashCode() 方法,其哈希算法计算规则如下s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],也就是按照每个字符对应的 ascii 十进制的数据代入上述公式计算,最终得出的结果就是哈希值。明白了 String 类的哈希值计算方式,就明白了指向同一块内存空间的两个变量的哈希值肯定相同,但是哈希值相同,并不代表是同一个对象,可能只是因为它的内容刚好相同。

    Integer 类也实现了 hashCode() 方法,它是直接返回了存储的 value 值,作为哈希值。

    以下为证明

    Integer n1 = 127;Integer n2 = 127;
    System.out.println(n1 == n2);//输出为true
    Integer n3 = 128;Integer n4 = 128;
    System.out.println(n3 == n4);//输出为false
    Integer n5 = new Integer(120);
    Integer n6 = new Integer(120);
    System.out.println(n5 == n6);//输出为false
    //分隔符
    String s1 = "keqi";String s2 = "keqi";
    System.out.println(s1 == s2);//输出为true
    System.out.println(s1.equals(s2));//输出为true
    String s3 = new String("keqi");
    String s4 = new String("keqi");
    System.out.println(s3 == s4);//输出为false
    System.out.println(s3.equals(s4));//输出为true
    

    总结

    总结就是,如果是在方法区的常量数据区分配空间的对象,只要存在相同的变量,声明时会直接引用已经存在的变量,而不是新开辟一个对象空间。在堆区开辟内存空间的对象,每一个都有自己独立的内存空间。理解了这一点,什么 == 和 equals() 方法根本不是事。说白了还是要理解内存,理解了内存就理解了一切。

    相关文章

      网友评论

        本文标题:Java.lang.Object 类

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