按照顺序一个一个地把我目前学习到的知识总结一下:
- 封装:
这个我认为是这三个概念里最好理解的一部分了。为了使得类中的属性不能随意被篡改,我们需要将类中的属性与其对应的操作方法封装在一起。具体表现为:数据设置为private,然后数据存取的方法设置为public,在存取方法(get&set)中对其进行条件判别,满足一定条件的才能存取数据,一定程度上保护了数据的安全。
除此之外,封装还能够隐藏实现细节,比如我们平常对电视机的操作就是典型的封装案例。我们不需要知道内部是如何实现的,只需要知道我们按音量键能够调节电视机音量的大小(我认为这就是对电视机这个对象的属性的操作)。当然,封装应该不限于对属性操作方法的细节隐藏,再比如我们常听到的接口,也是封装的一种,但是这里我暂且把封装理解为对属性操作的一个封装。
如果设计的类中含有构造器的话,有需要的话可以将构造器中的属性设置语句用setXxx方法来代替。
2.继承:
继承也是比较好理解的一个概念。其提出是为了提高代码的复用性、扩展性。可以把子类和父类的关系想象成数据结构中的树,其根节点就是Object类,从根节点开始从上到下分布。(这对后面多态中的向上转型和向下转型的理解会有帮助。)
继承不能滥用,子类和父类之间必须要满足is-a的逻辑关系。子类可以继承父类中的所有属性和方法,包括用private修饰的。但是可以继承不代表可以访问,是否能够访问还是要遵循访问修饰符的限定。Java中是单继承机制,即子类最多只能继承一个父类(类似于树中每个节点只有一个父节点)。
在继承父类的方法这句话中,有一个方法比较特殊,那就是构造器方法。每个类都有构造器,如果你没有定义,那默认是无参构造器,如果你有定义,则会将无参构造器覆盖,此时想要使用,就必须自己再定义一遍。
我把构造器理解为对象初始化中的最后一个步骤:在一个对象被创建的时候,首先是在JVM的方法区中加载这个对象所属类的相关信息,加载完成之后,在执行new语句时,会在堆内开辟一片空间,加载属性,这里的属性初始化分为三步,第一步是设定属性的默认值,即Int类型设为0,Boolean类型设为false,引用类型设为null等;第二步是看类定义当中是否有对属性进行赋值,如果有的话,则加载到堆中去;第三步是看是否有有参构造器,如果有的话,则将构造器里的内容赋值到属性上去。至此,可以认为完成了对对象的一个初始化。
让我们回到继承关系中去,子类如果要完成初始化,因为它是继承了父类的属性和方法,所以在子类初始化之前,必须要完成父类的初始化。可以理解为初始化的顺序是从根节点一个一个往下走的。个人理解,就是为了在子类初始化之前完成父类的初始化,Java规定子类必须继承且调用父类的构造器,默认会继承父类的无参构造器(前提是父类中有无参构造器),而其他的有参构造器需要我们手动继承,继承构造器的语句super(...)要放在子类构造器中的第一行(和类中定义多个构造器,使用this关键字在一个构造器中调用另一个是一样的,当然,在一个构造器中,this和super只能二选一)。
在继承构造器的这点上也体现出Java设计中的“分工明确”,即父类的属性由父类初始化,子类的属性由子类初始化。
这里还需要提一下子类的方法重写:我们知道,子类会继承父类中的所有属性和方法,如果我们需要在子类中重写这个方法(比如想添加一些功能的话),我们就需要定义一个和父类中的方法名称、参数都完全一样的方法(返回类型和访问修饰符可以不一样,但需要遵循一定的规则,这里的规则我们放在多态那部分一起讲,先留个坑。)
我们还需要注意区分方法重载和方法重写,重载是在本类的范围内,唯一的要求就是方法名必须一样,然后参数不能完全一样,对于返回类型和修饰符没有任何要求。这里要求参数不能完全一样,就是为了在我们调用这个方法的时候,能够通过参数的不同,去对应到我们到底是调用了哪个方法。重写是在父类和子类之间,方法名和参数必须完全一样,返回类型和修饰符可以不用一样,但要满足一定规则。
最后,让我们分析一下继承的本质:
我们知道,一个对象在定义的时候是会现在方法区中加载类信息(如果之前有加载过相同的类就可以不用加载),所以当我们定义一个子类对象的时候,其实是会先加载其所有父类信息,然后才会加在子类的信息。在堆中加载对象的属性也是同理,按照树结构从上到下下载对应的属性信息,这个每个类的属性是占有独立空间的,即重名属性是不冲突的。最后,才会加载子类的属性。当我们调用子类属性的时候,顺序就是相反的,从下而上,即先去子类的属性空间里找,找不到的话就去上一级的父类里找,直到找到(找不到就报错)。调用子类的方法也是同理(前提是子类引用指向子类对象)。
总结一下:就是初始化、加载、构造器都是按照父类->子类的顺序,而在子类中操作属性、调用方法,顺序则是相反的,即使按照子类->父类的顺序来查找,直至找到并且可以被访问的属性或者方法为止,如果往上找的过程中遇见了不能访问的属性或者方法,则会返回报错,而不会继续向上找噢~