第十六天

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

 

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/jikebin/p/12571146.html