• 第十六天


    Object类是一个特殊的类,是所有类的父类,如果定义一个类没有用extends明确指出继承于某个类,那么它默认继承Object类。

    Object是类层次结构的根类

    所有对象,包括数组在内,都实现了这个类中的方法

     

    Object类没有属性,只有方法,而且我们可以从源码中看到大多数方法都是native方法:

    native关键字做一个总结:

    ·        native关键字是JavaC++/C联合开发的时候用的,java自己开发不用!

    ·        使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了dll,由java去调用。这个函数的实现是在DLL中,JDK的源代码中并不包含,所以看不到被这个关键字修饰的函数的源代码。对于不同的平台它们是不同的。这也是java的底层机制,实际上java就是在不同平台上调用不同的native方法实现对操作系统的访问。

    ·        简言之,native是用做java和其他语言(如C)进行协作时用的,也就是native后的函数的实现不是用java写的。

    ·        native的意思就是通知操作系统,这个函数你必须给我实现,因为我要用。所以native关键字的函数就是操作系统实现的,java只能调用。

    ·        java是跨平台的语言,既然是跨平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了

     

    Object的主要常用方法做个了解:

    1public final nativeClass<?> getClass()方法

    该方法也是native方法,返回的是该Object对象的类对象/运行时类对象。

    类对象: 在java中,类是对具有一组相同特征或者行为的实例的抽象并进行描述,对象是该类所描述的特征或者行为的具体实例。作为概念层次的类,其本身也具有某些共同的属性,如都具有类型、类加载器、包、父类、属性、方法等。java中有专门定义一个类,Class,该类用于描述其他类所具有的这些特征。因此,从该角度来看,类本身也都是属于Class类的对象。 该部分涉及到反射的知识。

     

    2 public native int hashCode()方法

    hashCode返回的值:一般是通过将该对象的内存地址转换成一个整数来实现。该方法不是java实现的,因此使用了native关键字。

     

    public class Demo {
       public static void main(String[] args) {
        Demo demo = new Demo();//new 一个对象
        System.out.println(demo.hashCode());
       }//hashCode根据内存地址运算的来的哈希码值代表 内存地址
    }//运行结果:705927765

     

    3 public boolean equals(Object obj)方法

    有关equals()方法与==运算符的区别,前面的课程中有过讲解,但是在这里,还要进一步的讨论一个问题:重写equals方法时一定要重写hashCode方法,为什么?

    我们知道在Object类中,hashCode方法是通过Object对象的地址计算出来的,因为Object对象只与自身相等,所以同一个对象的地址总是相等的,计算取得的哈希码也必然相等,对于不同的对象,由于地址不同,所获取的哈希码自然也不会相等。因此到这里我们就明白了,如果一个类重写了equals方法,但没有重写hashCode方法,将会直接违反重写equals方法必须要有对称性原则:对于任何非null的引用值,当x.equals(y)返回true时,y.equals(x)一定返回true的规定,这样的话,,就无法达到我们预期想要的效果。

    用例子来说明:

    对于s1s2的结果,我们并不惊讶,因为相同的内容的s1s2获取相同内的value这个很正常,因为String类重写了equals方法和hashCode方法,使其比较的是内容和获取的是内容的哈希码。

    package coursetest;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Demo {      
       public static void main(String[] args) {//程序入口
        String s1 = new String("key");//当s1 new了一个String对象时,相当了对"key"做了一份拷贝,新建了一个内存空间,(对象始终存在于堆中)。
        String s2 = new String("key");
        //定义一个map集合:暂时理解这个map集合,就是一个容器,它里边保存的值:key=value
        //<>表示泛型,限制,集合里保存的key必须是什么类型,集合里的值必须是什么类型
        Map<String, Value> map1 = new HashMap<>();//创建了一个集合对象,数据容器
        Value value = new Value(12);
        map1.put(s1,value);
        System.out.println(s1.equals(s2));//s1和s2重写了equals方法,只看值,值相等返回true
        System.out.println(map1.get(s1));
        //在map集合里面,判断传进来的key,和我集合中保存key是不是一样的,就不是用equals,而是用hashcode
        System.out.println(map1.get(s2));
        Map<Key, Value> map2 = new HashMap<>();
        Value value2 = new Value(32);
        Key key1 = new Key("11");
        Key key2 = new Key("11");
        System.out.println(key1.equals(key2));
        
        map2.put(key1, value2);
        
        System.out.println(map2.get(key1));
        System.out.println(map2.get(key2));//拿不出来,传入的key2对象,map集合里
       }
       
       
       
       //k重写了equals方法,但没有重写hashCode方法
      static class Key{
          private String k;
          public Key(String k) {
            this.k = k;
        }
        @Override
        public boolean equals(Object obj) {
            // TODO Auto-generated method stub
            if(obj instanceof Key) {
                Key oKey = (Key)obj;
                return oKey.k.equals(this.k);
            }
            return false;
        }
        //下面是重写 k的hashcode方法 这样就可以取出key2对象
    //    @Override
    //    public int hashCode() {
    //        // TODO Auto-generated method stub
    //        return this.k.hashCode();
    //    }
      }
      static class Value{
          private int v;
          public Value(int v) {
              this.v = v;
          }
        @Override
        public String toString() {
            return "Value [v=" + v + "]";
        }            
      }
    } 

    但是对于k1k2的结果就不太尽人意了,k1获取到的值是2k2获取到的是null,这是为什么呢?想必大家已经发现了,Key只重写了equals方法并没有重写hashCode方法,这样的话,equals比较的确实是内容,hashCode方法呢?没重写,那就肯定调用超类ObjecthashCode方法,这样返回的不就是地址了吗?k1k2属于两个不同的对象,返回的地址肯定不一样,所以现在我们知道调用map2.get(k2)为什么返回null了吧!

    解决也很简单,重写hashCode方法,返回equals方法判断相等的关键属性的hashCode值:

     

    记住结论:给一个类重写equals方法时一定要重写hashCode方法

     

    3protected native Object clone( )

    clone()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成

    clone英文翻译为克隆,其目的是创建并返回此对象的一个副本。

    Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。

    public class Demo implements Cloneable {  //clone()的正确调用是需要实现cloneable接口    
       public static void main(String[] args) {//程序入口
        //使用一下clone方法
    //       Object obj = new Object();
    //       obj.//子类中能去调用父类中的由protected修饰的方法或属性,有一个前提:用子类引用
           Demo demo = new Demo();
          try {
            Demo demo2 =  (Demo)demo.clone();//demo克隆一份赋值给demo2
            
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
      }
    }

     

    例子说明:

    例子很简单,在main()方法中,new一个Oject对象后,想直接调用此对象的clone方法克隆一个对象,但是你会发现用不了!

     

    why?回到Object类中clone()方法的定义,可以看到其被声明为protectedprotected修饰的属性或方法表示:在同一个包内或者不同包的子类可以访问。显然,Object类与ObjectTest类在不同的包中,但是ObjectTest继承自Object,是Object类的子类,于是,现在却出现子类中通过Object引用不能访问protected方法,原因在于对不同包中的子类可以访问没有正确理解。

     

    不同包中的子类可以访问是指当两个类不在同一个包中的时候,继承自父类的子类内部且主调(调用者)为子类的引用时才能访问父类用protected修饰的成员(属性/方法)。 在子类内部,主调为父类的引用时并不能访问此protected修饰的成员

    是的,因为此时的主调已经是子类的引用了。

    上述代码在运行过程中会抛出”java.lang.CloneNotSupportedException”,表明clone()方法并未正确执行完毕,问题的原因在与Java中的语法规定:

    clone()的正确调用是需要实现Cloneable接口,如果没有实现Cloneable接口,并且子类直接调用Object类的clone()方法,则会抛出CloneNotSupportedException异常。

    Cloneable接口仅是一个标记接口,接口本身不包含任何方法,用来指示Object.clone()可以合法的被子类引用所调用。

    于是,上述代码改成如下形式,即可正确指定clone()方法以实现克隆。

    总结:

    1. Obejct类的clone()方法实现的是浅拷贝

    2. 在子类内部,主调为父类的引用时并不能访问此protected修饰的成员。调用者为子类的引用时才能访问父类中用protected修饰的成员

    3. 想要在子类中调用父类的clone()方法,子类需要实现Cloneable接口,该接口用来指示Object.clone()可以合法的被子类引用的标记

    4.简单谈谈什么时候需要用到clone方法呢,开发飞机大战的游戏,发射出来的子弹,每个子弹就是一个对象,这个时候就可以用clone方法复制子弹对象,它是一种浅拷贝,可以大量节约内存的开销。

    5.clone()new的区别:

    1)在javaclone()new都能创建对象。

    2clone()不会调用构造方法;new会调用构造方法。

    3clone()能快速创建一个已有对象的副本,即创建对象并且将已有对象中所有属性值克隆;new只能在JVM中申请一个空的内存区域,对象的属性值要通过构造方法赋值。

    注意:

    1)使用clone()类必须实现java.lang.Cloneable接口并重写Object类的clone()方法,如果没有实现Cloneable()接口将会抛出CloneNotSupportedException异常。(此类实现java.lang.Cloneable接口,指示Object.clone()方法可以合法的对该类实例进行按字段复制。)

    2)默认的Object.clone()方法是浅拷贝,创建好对象的副本然后通过赋值拷贝内容,如果类包含引用类型变量,那么原始对象和克隆对象的引用将指向相同的引用内容。

    面试题:什么是浅拷贝?什么是深拷贝?

    浅拷贝:默认的Object.clone()方法,对于引用类型成员变量拷贝只是拷贝即地址,没有在堆中开辟新的内存空间。

    深拷贝:重写clone()方法,拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝,会在堆中开辟新的内存空间。

     

    4public String toString()方法

    当使用System.out.println(Object obj);时,返回的就是该obj对象的toString方法,实际上System.out.println()内部是通过toString实现的

     

    public class Demo implements Cloneable { // clone()的正确调用是需要实现cloneable接口
        public static void main(String[] args) {// 程序入口
    
            Demo demo = new Demo();
            // 打印对象内存地址
            System.out.println(demo.toString());// toString可以省略 默认为toString    
        }
        // 覆盖toString方法后就会打印字符串内容
        @Override
        public String toString() {
            return "Demo []";
        }
    }

     

    getClass()返回对象的类对象,getClassName()String形式返回类对象的名称(含包名)。Integer.toHexString(hashCode())则是以对象的哈希码为实参,以16进制无符号整数形式返回此哈希码的字符串表示形式。

    u1对象的哈希码是638,则对应的16进制为27e,调用toString()方法返回的结果为:com.corn.User@27e。包名.类名@哈希码

    因此:toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果不可能相同,除非重写了toString()方法。

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    五、oracle基本建表语句
    二十九、oracle 触发器
    二十七、oracle 异常
    二十八、oracle 视图
    maven项目搭建
    springmvc java对象无法返回json格式问题
    springmvc+mybatis+oracle+druid搭建项目
    Trilateration三边测量定位算法
    滴滴开源:DoraemonKit来了,程序员的开发工具箱
    VUE中index.html什么时候加载的mainjs呢?
  • 原文地址:https://www.cnblogs.com/jikebin/p/12571146.html
Copyright © 2020-2023  润新知