您好,登录后才能下订单哦!
本篇文章给大家分享的是有关Java中对象的本质是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
在学习编程语言中,除非是最底层的 0101001 这些东西,一定离不开类型(type)。什么是类型?类型就是“抽象的是什么”。
“抽象的是什么”这句话,其实很容易理解,我们学习基础数据类型时必会提到每种数据类型都占据多少多少个字节,而且他们都是有范围限制的,比如 byte 数据类型是8位、有符号的,以二进制补码表示的整数——这其实就是通过给二进制数字人为的赋予定义来获得一个固定的表示内容来达到的,这个过程就是抽象:本来只是毫无意义的 0101001 这种东西,通过抽象,它能够变成任意我们希望的东西。
Java 中八大基本数据类型就是建立在多层抽象上的,而面向对象,大家都说“万物皆对象”,这句话的意思应该是“万物皆可被抽象后描述,而不仅仅限于一些基本的数据类型”,所以在 Java 中定义一个 class 时,我们给 class 赋予类型属性和行为方法,通过这两者抽象后形成的类(class)就是类型(type)的意思,他们几乎可以等同看待。type 所抽象的东西还比较基本,所以说是基本数据类型。而 class 则全部都是对于外界事物的描述,对象则是某个类中的个体,每个个体都拥有相同的特征,所以他们属于同一类(class)。所以类只是个概念,而对象才是一个个活蹦乱跳的可以使用操作的对象。
创建一个类,就是创建新的数据类型。
举个例子畅想一下。int 是 Java 本身自带的基本数据类型,它的描述是:32位、有符号的以二进制补码表示的整数,我们可以获得一堆的整数,它的类型是确定的,行为(运算)也被限定,带有正负等。Java 中也定义了一个 Integer 的 class,这个 Integer 跟 int 有什么关联吗?当然,它被设计来对应于基本数据类型 int,并且定义了很多操作方法。所以,如果忽略 int 类型的存在的话,Integer 类完全就是一种数据类型,而且还是一种“升级版”的。
封装性很容易理解,就是把一堆东西用大括号包起来。封装性作为面向对象的三大特性之一,深层意义绝不仅限于这个浅显的概念。
上面说到类与类型,类型就是“抽象的是什么”。类呢?就是“对万物的抽象后封装其描述”。所以我在上面总结说:创建一个类,就是创建新的数据类型。
封装了什么描述呢?每个类中的成员变量都是对这个类抽象后的描述。
比方说,我要创建一个类:人,那么我觉得人应该要有名字、性别。如果实际情况不需要知道或用到其他的属性,我就不会创建其他的成员变量来描述“人”这个类,而只有两个成员变量。抽象就是:我只描述我想要的。
抽象了一个类(型)之后,在 Java 里就是用花括号包起来,就封装成了一个真正的类(型概念)。
封装性是面向对象的最基本的特性。
同上面一样,继承的概念我也不想多说,只是说一些我认为起到点睛效果的点。
在 Java 里的继承不是真正意义上的或者纯概念上的继承,它是通过让派生类获得基类的一个子对象来获得看起来是继承的样子(效果)的。这个子对象等同于我们手动 new 一个对象,这就是为什么我们在写每一个派生类的任意构造函数时都需要确保能够调用到基类的任一个构造函数且只能调用一个构造函数的原因。
——这句话有点拗口,但是不难理解,这也是考察Java基础时常考到的知识点——给出几个类你,他们之间有继承关系,问你编译运行后的输出结果是什么,通常结果都是发生编译时异常,因为代码中往往会通过很隐秘的方法让派生类最终并不能调用到基类的构造函数,这样的结果就是派生类没办法生成并获取基类的子对象,那么继承所必需的代码就不完整,自然就在编译的时候就发生异常了。
理解这一点之后发现,所谓的继承其实可以通过手动的方式完成几乎相同(并不完全相同)的效果,你肯定猜到了,那就是直接 new 另一个类的对象,让它成为自己的类的成员变量——你一定经常这样做。这样的结果就是,你获得那个类的全部访问权限允许的属性和方法。
常常有人出这样的题目来吓唬人,就是有继承关系的两个类,基类有个私有变量a(String型),派生类能不能使用基类的a变量?答案肯定是不能的,系统提示没有访问权限。如果是真的概念上的继承的话,派生类应该获得基类的所有元素才对啊,为什么说没有权限呢?原因也是上述的:这仅仅是通过new一个类的对象来访问而已,自然是不能直接操作对象的声明为私有的任何东西的。
真正的继承,当然就是说所有东西都是属于派生类才对的。
假设这时候子类也有一个私有变量a(String型)。能不能访问到呢?这两个变量是什么关系呢?这时候变成可以访问了,因为他们都是分别属于两个类的成员变量,互相独立,他们之间没有任何的关系,这时候其实就是访问自己的私有变量,当然没有问题了。
所以说手动的、直接 new 一个对象,也是一种“继承”,这种方式反而更加灵活方便,它有个专门的名词叫做“组合”。组合与继承之间纠缠着的爱恨情仇大抵如上。同时,在写代码的时候,通常是优先使用组合而不是继承。
那么什么时候使用继承呢?继承虽然相比组合来说比较笨重不灵活(比如不能多继承可以多组合等),但是继承的魅力还是不小的,比如多态等。所以当你觉得派生类有向上(基类)转型的必要时,使用继承。
多态依赖于继承,组合是没办法完成多态的,这就是优缺点——有得必有失,看你的取舍。
多态的概念也不多说,因为这文章并不是知识普及用的,是思想提升用的,如果连相关知识点都没有掌握的话,估计你也不会看到这里了。
多态有个类型转换的问题:向上转型和向下转型。向上转型是永远不会出错的,同时意味着派生类丢失基类并没有的部分信息。而向下转型原则上是不允许的或者是不建议的,随意的、直接的向下转型,在编译器就会报错。如果确实有需要,则需要强制转型:所以在 Java 里所有的向下转型都必须显式声明,当显式声明时,就意味着你已经了解这种风险并愿意承担其带来的问题等。
这里有个问题:为什么向上转型是安全的,向下转型则是危险的?如果你把它当作一个知识点去学习并记住了,那么你为什么不会好奇其背后的缘由呢?这里就想回答这个背后的原因。
上面说继承的时候说过,Java 中的继承并不是我们概念上所理解的真正的继承,如果子类继承了父类,那么当你 new 了一个子类对象时,其实在 Java 的底层会同时帮你 new 一个父类的对象作为子类的子对象,这时候在Java里想要向上转型时,通过这个父类的子对象很容易就知道了这个继承关系,转型自然是安全的。
而如果 new 了的是父对象,再向下转型成子对象时,这样在编译期就会发生异常——Java 里是不允许这样的随意向下转型的行为的。所以你需要在父对象的前面显式声明说:我要强制转型。编译器才能通过编译。
但是,在上面这种情况,即使你在转换的时候已经在前面的小括号里声明了类型来强制转型,让自己的代码在编译时能够通过了,运行时还是会出现异常的:ClassCastException,因为这时候JVM发现这个父对象根本没有任何强转类型(子类)的信息。为了避免这种运行时异常通常需要去确认一下是不是同一个类型:instanceof 判断通过后再进行转换,就能确保代码不会出现异常。
有一种向下转型不会出现运行时异常 ClassCastException 的情况,那就是一开始 new 的是子类的对象,然后赋值给一个父类的引用,然后再将这个父类引用的对象强制转型为子类对象,这时候的强转是成功的——这背后的原因也很容易理解:new 创建的所有对象都是存放在堆内存区的,而引用则存放在栈内存区,它保存的只是堆内存中那个对象的开始地址而已。因为一开始 new 的就是子类的对象,所以这个对象是不仅拥有父类的子对象,而且拥有自身的对象的。这时候它不管是向上转型还是向下强制转型,都是不会有问题的。
下面是一个相似的例子:
第25行代码会在运行时抛出ClassCastException,java.lang.Integer cannot be cast to java.lang.Double。这两个类都继承自Number抽象类,所以他们的向上转型都不会有任何问题并且不需要强转,但是向上转型后再向下转型时,抛出的异常依然能识别到创建对象时的类,强转是失败的。
这就是为什么非要做类型判断后才能进行强转,所以注释掉的28到33行代码才是正确的做法。
同时,在Java里有个 Class 类,JVM 每加载一个类,Class 类都会新建一个对象,用于记录它的类型信息,Java 的反射机制也是基于它。
以上就是Java中对象的本质是什么,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。