zoukankan      html  css  js  c++  java
  • 第十六天

    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()方法。

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    【leetcode】Binary Search Tree Iterator
    【leetcode】Palindrome Partitioning II
    【leetcode】Best Time to Buy and Sell Stock III
    【leetcode】Best Time to Buy and Sell Stock II
    【leetcode】Longest Consecutive Sequence
    【leetcode】Factorial Trailing Zeroes
    【leetcode】Simplify Path
    【leetcode】Generate Parentheses
    【leetcode】Combination Sum II
    【leetcode】Combination Sum
  • 原文地址:https://www.cnblogs.com/jikebin/p/12571146.html
Copyright © 2011-2022 走看看