第十六天
Posted jikebin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十六天相关的知识,希望对你有一定的参考价值。
Object类是一个特殊的类,是所有类的父类,如果定义一个类没有用extends明确指出继承于某个类,那么它默认继承Object类。
类Object是类层次结构的根类
所有对象,包括数组在内,都实现了这个类中的方法
Object类没有属性,只有方法,而且我们可以从源码中看到大多数方法都是native方法:
对native关键字做一个总结:
· native关键字是Java与C++/C联合开发的时候用的,java自己开发不用!
· 使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了dll,由java去调用。这个函数的实现是在DLL中,JDK的源代码中并不包含,所以看不到被这个关键字修饰的函数的源代码。对于不同的平台它们是不同的。这也是java的底层机制,实际上java就是在不同平台上调用不同的native方法实现对操作系统的访问。
· 简言之,native是用做java和其他语言(如C)进行协作时用的,也就是native后的函数的实现不是用java写的。
· native的意思就是通知操作系统,这个函数你必须给我实现,因为我要用。所以native关键字的函数就是操作系统实现的,java只能调用。
· java是跨平台的语言,既然是跨平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了
Object的主要常用方法做个了解:
1、public final nativeClass<?> getClass()方法
该方法也是native方法,返回的是该Object对象的类对象/运行时类对象。
类对象: 在java中,类是对具有一组相同特征或者行为的实例的抽象并进行描述,对象是该类所描述的特征或者行为的具体实例。作为概念层次的类,其本身也具有某些共同的属性,如都具有类型、类加载器、包、父类、属性、方法等。java中有专门定义一个类,Class,该类用于描述其他类所具有的这些特征。因此,从该角度来看,类本身也都是属于Class类的对象。 该部分涉及到反射的知识。
2、 public native int hashCode()方法
hashCode返回的值:一般是通过将该对象的内存地址转换成一个整数来实现。该方法不是java实现的,因此使用了native关键字。
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的规定,这样的话,,就无法达到我们预期想要的效果。
用例子来说明:
对于s1和s2的结果,我们并不惊讶,因为相同的内容的s1和s2获取相同内的value这个很正常,因为String类重写了equals方法和hashCode方法,使其比较的是内容和获取的是内容的哈希码。
但是对于k1和k2的结果就不太尽人意了,k1获取到的值是2,k2获取到的是null,这是为什么呢?想必大家已经发现了,Key只重写了equals方法并没有重写hashCode方法,这样的话,equals比较的确实是内容,而hashCode方法呢?没重写,那就肯定调用超类Object的hashCode方法,这样返回的不就是地址了吗?k1与k2属于两个不同的对象,返回的地址肯定不一样,所以现在我们知道调用map2.get(k2)为什么返回null了吧!
解决也很简单,重写hashCode方法,返回equals方法判断相等的关键属性的hashCode值:
记住结论:给一个类重写equals方法时一定要重写hashCode方法
3、protected native Object clone( )
clone()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成
clone英文翻译为”克隆”,其目的是创建并返回此对象的一个副本。
Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。
例子说明:
例子很简单,在main()方法中,new一个Oject对象后,想直接调用此对象的clone方法克隆一个对象,但是你会发现用不了!
why?回到Object类中clone()方法的定义,可以看到其被声明为protected,protected修饰的属性或方法表示:在同一个包内或者不同包的子类可以访问。显然,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)在java中clone()与new都能创建对象。
(2)clone()不会调用构造方法;new会调用构造方法。
(3)clone()能快速创建一个已有对象的副本,即创建对象并且将已有对象中所有属性值克隆;new只能在JVM中申请一个空的内存区域,对象的属性值要通过构造方法赋值。
注意:
(1)使用clone()类必须实现java.lang.Cloneable接口并重写Object类的clone()方法,如果没有实现Cloneable()接口将会抛出CloneNotSupportedException异常。(此类实现java.lang.Cloneable接口,指示Object.clone()方法可以合法的对该类实例进行按字段复制。)
(2)默认的Object.clone()方法是浅拷贝,创建好对象的副本然后通过“赋值”拷贝内容,如果类包含引用类型变量,那么原始对象和克隆对象的引用将指向相同的引用内容。
面试题:什么是浅拷贝?什么是深拷贝?
“浅拷贝”:默认的Object.clone()方法,对于引用类型成员变量拷贝只是拷贝“值”即地址,没有在堆中开辟新的内存空间。
“深拷贝”:重写clone()方法,拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝,会在堆中开辟新的内存空间。
4、public String toString()方法
当使用System.out.println(Object obj);时,返回的就是该obj对象的toString方法,实际上System.out.println()内部是通过toString实现的
getClass()返回对象的类对象,getClassName()以String形式返回类对象的名称(含包名)。Integer.toHexString(hashCode())则是以对象的哈希码为实参,以16进制无符号整数形式返回此哈希码的字符串表示形式。
如u1对象的哈希码是638,则对应的16进制为27e,调用toString()方法返回的结果为:com.corn.User@27e。包名.类名@哈希码
因此:toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果不可能相同,除非重写了toString()方法。
Object类是一个特殊的类,是所有类的父类,如果定义一个类没有用extends明确指出继承于某个类,那么它默认继承Object类。
类Object是类层次结构的根类
所有对象,包括数组在内,都实现了这个类中的方法
Object类没有属性,只有方法,而且我们可以从源码中看到大多数方法都是native方法:
对native关键字做一个总结:
· native关键字是Java与C++/C联合开发的时候用的,java自己开发不用!
· 使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了dll,由java去调用。这个函数的实现是在DLL中,JDK的源代码中并不包含,所以看不到被这个关键字修饰的函数的源代码。对于不同的平台它们是不同的。这也是java的底层机制,实际上java就是在不同平台上调用不同的native方法实现对操作系统的访问。
· 简言之,native是用做java和其他语言(如C)进行协作时用的,也就是native后的函数的实现不是用java写的。
· native的意思就是通知操作系统,这个函数你必须给我实现,因为我要用。所以native关键字的函数就是操作系统实现的,java只能调用。
· java是跨平台的语言,既然是跨平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了
Object的主要常用方法做个了解:
1、public final nativeClass<?> getClass()方法
该方法也是native方法,返回的是该Object对象的类对象/运行时类对象。
类对象: 在java中,类是对具有一组相同特征或者行为的实例的抽象并进行描述,对象是该类所描述的特征或者行为的具体实例。作为概念层次的类,其本身也具有某些共同的属性,如都具有类型、类加载器、包、父类、属性、方法等。java中有专门定义一个类,Class,该类用于描述其他类所具有的这些特征。因此,从该角度来看,类本身也都是属于Class类的对象。 该部分涉及到反射的知识。
2、 public native int hashCode()方法
hashCode返回的值:一般是通过将该对象的内存地址转换成一个整数来实现。该方法不是java实现的,因此使用了native关键字。
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的规定,这样的话,,就无法达到我们预期想要的效果。
用例子来说明:
对于s1和s2的结果,我们并不惊讶,因为相同的内容的s1和s2获取相同内的value这个很正常,因为String类重写了equals方法和hashCode方法,使其比较的是内容和获取的是内容的哈希码。
但是对于k1和k2的结果就不太尽人意了,k1获取到的值是2,k2获取到的是null,这是为什么呢?想必大家已经发现了,Key只重写了equals方法并没有重写hashCode方法,这样的话,equals比较的确实是内容,而hashCode方法呢?没重写,那就肯定调用超类Object的hashCode方法,这样返回的不就是地址了吗?k1与k2属于两个不同的对象,返回的地址肯定不一样,所以现在我们知道调用map2.get(k2)为什么返回null了吧!
解决也很简单,重写hashCode方法,返回equals方法判断相等的关键属性的hashCode值:
记住结论:给一个类重写equals方法时一定要重写hashCode方法
3、protected native Object clone( )
clone()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成
clone英文翻译为”克隆”,其目的是创建并返回此对象的一个副本。
Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。
例子说明:
例子很简单,在main()方法中,new一个Oject对象后,想直接调用此对象的clone方法克隆一个对象,但是你会发现用不了!
why?回到Object类中clone()方法的定义,可以看到其被声明为protected,protected修饰的属性或方法表示:在同一个包内或者不同包的子类可以访问。显然,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)在java中clone()与new都能创建对象。
(2)clone()不会调用构造方法;new会调用构造方法。
(3)clone()能快速创建一个已有对象的副本,即创建对象并且将已有对象中所有属性值克隆;new只能在JVM中申请一个空的内存区域,对象的属性值要通过构造方法赋值。
注意:
(1)使用clone()类必须实现java.lang.Cloneable接口并重写Object类的clone()方法,如果没有实现Cloneable()接口将会抛出CloneNotSupportedException异常。(此类实现java.lang.Cloneable接口,指示Object.clone()方法可以合法的对该类实例进行按字段复制。)
(2)默认的Object.clone()方法是浅拷贝,创建好对象的副本然后通过“赋值”拷贝内容,如果类包含引用类型变量,那么原始对象和克隆对象的引用将指向相同的引用内容。
面试题:什么是浅拷贝?什么是深拷贝?
“浅拷贝”:默认的Object.clone()方法,对于引用类型成员变量拷贝只是拷贝“值”即地址,没有在堆中开辟新的内存空间。
“深拷贝”:重写clone()方法,拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝,会在堆中开辟新的内存空间。
4、public String toString()方法
当使用System.out.println(Object obj);时,返回的就是该obj对象的toString方法,实际上System.out.println()内部是通过toString实现的
getClass()返回对象的类对象,getClassName()以String形式返回类对象的名称(含包名)。Integer.toHexString(hashCode())则是以对象的哈希码为实参,以16进制无符号整数形式返回此哈希码的字符串表示形式。
如u1对象的哈希码是638,则对应的16进制为27e,调用toString()方法返回的结果为:com.corn.User@27e。包名.类名@哈希码
因此:toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果不可能相同,除非重写了toString()方法。
以上是关于第十六天的主要内容,如果未能解决你的问题,请参考以下文章