Bruce Eckel 前辈写的《Java编程思想》把问题探讨得很是深刻,很是建议同行都学习一下。学习 Java 语言时,老师告诉你 What,本身练习知道 How ,Bruce Eckel 告诉你Why 。java
以前已经把后面的几章更新完了,之因此如今才写第一章,彻底是为了更加容易描述。这章须要不少其余章节说到的概念。编程
第3章 操做符安全
第5章 初始化与清理编程语言
第7章 复用类函数
第8章 多态学习
在学习编程语言中,除非是最底层的 0101001 这些东西,必定离不开类型(type)。什么是类型?类型就是“抽象的是什么”。spa
“抽象的是什么”这句话,其实很容易理解,咱们学习基础数据类型时必会提到每种数据类型都占据多少多少个字节,并且他们都是有范围限制的,好比 byte 数据类型是8位、有符号的,以二进制补码表示的整数——这其实就是经过给二进制数字人为的赋予定义来得到一个固定的表示内容来达到的,这个过程就是抽象:原本只是毫无心义的 0101001 这种东西,经过抽象,它可以变成任意咱们但愿的东西。.net
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 的反射机制也是基于它。
本文讲述的主要是如下几点:
- 语言创建在抽象之上,基本数据类型是语言根据须要抽象出来的经常使用类型(type),
- 对象则是更高层次的抽象,任何人均可以根据须要,本身抽象外界后封装成一种新的数据类型(class)。
- 在 Java 里,最终仍是经过子类所拥有的祖先子对象来得到继承、类型转换、多态等能力的。
- 向上转型安全和向下转型不安全的背后缘由,都是基于Java内部的继承机制才存在的问题。
若是理解了上述几个问题,就会对 Java 中的不少看成死记硬背的“知识点”恍然大悟了,之后这些在你眼里就不再是须要记忆的、知其然不知其因此然的、知识点了,而是一个个逻辑清晰的、一切皆有原因的语言准则了。
BTW:有兴趣的朋友可加群(302659873)交流技术。