Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,可是世界上这么多错综复杂的事物,有着各自的特性也有各自的共同点,我们该如何用类去简单有效去描述他们呢?
例如一只猫和一只狗,我们把他们各自都定义一个类:
通过上面的描述,我们可以看出来他们都有相同的共性和各自的特点,其中有很多共性都是重复的,为了实现代码高效化,那么我们怎样把共性进行抽取呢?面相对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
如上面提到的猫和狗,他们的共性都是动物,所以可以生成一个动物类,来抽取他们的共性,其中猫和狗称为:***子类,也可以是派生类。***其中动物称为:父类,基类,或者超类。
在Java中如果要表示类之间的继承关系,需要借助extends关键字
如我们上文提到的猫和狗类,抽取他们的共性生成动物类:
然后使用关键字extends来继承共性:
这样我们就实现了代码的复用
然后对他们进行赋值调用:
注意:
继承也可以看作是" is a "的关系,猫"是一个"动物。
在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?
1.4.1 子类中访问父类的成员变量
在子类方法中 或者 通过子类对象访问成员时:
1.4.2 子类中访问父类的成员方法
在这上面两个方法构成了重载,重载只能在一个类里面或者构成继承关系,所以符合重载要求。
说明:
如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?
由于一些特殊原因,在父类和子类中可能存在相同成员,如果要在子类方法中访问父类同名成员时,该如何操作?直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。
这样就可以直接获取到父类的成员数据,在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。
注意:
后文还会详细介绍super
子类对象构造时,需要先调用父类构造方法,然后执行子类的构造方法。
在父类中构造一个方法:
此处我们用super进行对父类的赋值,然后再初始化子类成员变量,必须注意super放在第一行,或者可以直接在构造方法里传参:在实例化的时候就传值
相同点:
不同点:
当我们在子类和父类当中都写了各自的静态代码块和实例代码块,以及构造方法的时候,这时候的执行顺序和我们之前讲的有点小差异,执行顺序应该是:静态代码块–>实例代码块–>构造方法,在这个地方我们引进了继承,所以我们的执行顺序应该是:父类和子类的静态代码块–>父类的实例代码块和构造方法–>子类的实例代码块和构造方法,且注意这里的静态代码块还是只执行一次。
总结:
在类和对象中我们提到过protected的作用域,它可以被同一包中的同一类,同一包中的不同类,不同包中的子类给访问,可以通过super关键字访问,切记不可以再main函数中用super,否则报错,而且被protected修饰的那个类必须是被public修饰的,否则没有权限访问这个包。
父类中private成员变量在子类中不能直接访问,但是也继承到子类中了
那么我们该如何区别使用这些关键字呢?要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public,所以我们必须明确写代码的时候认真思考, 该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用).
通过我们对继承的学习,我们还可以深挖继承的各种形式,继承又分为:单继承,多层继承,不同类继承同一个类,多继承。
单继承就是我们前面写的B继承A,一个类继承一个类。多层继承就是B继承A,C又继承B。不同类继承同一个类是B继承A,C也继承A。多继承是C继承A和B。
但是在Java里是不支持多继承的:我们写的类是现实事物的抽象,而我们真正在公司中所遇到的项目往往业务比较复杂,,可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示,,所以我们真实项目中所写的类也会有很多.,类之间的关系也会更加复杂,但是即使如此,,我们并不希望类之间的继承层次太复杂.,一般我们不希望出现超过三层的继承关系.,如果继承层次太多,就需要考虑对代码进行重构了,如果想从语法上进行限制继承, 就可以使用 final 关键字。
final关键可以用来修饰变量、成员方法以及类。
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a(is part of)的关系,比如:学校
这里的小学又把学校的各种属性和方法继承下来了
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
比如汽车:有的汽车动力来源是汽油,有的汽车动力是柴油。
比如动物:猫和狗都吃粮食,猫吃猫粮,狗吃狗粮。
总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。
2.2.1 向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用(父类引用子类对象)。
语法格式:父类类型 对象名 = new 子类类型();
animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
在子类中可以访问父类的方法,而在父类中不可以访问子类的方法,只能访问父类自己特有的成员,因为父类没有这个方法
使用场景:
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
2.2.2 向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。如向上转型就是所有的狗是动物,向下转型就是所有的动物都是狗,明显不正确。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。(了解即可)
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。子类的访问修饰限定符一定要大于等于父类的访问修饰限定符的范围。
此处拓展一下:协变类型:比如返回值构成父子类关系,父类重写方法返回值是父类类名,子类重写方法返回值是子类类名。
规则:
当我们在子类和父类当中都写了一个一摸一样的eat方法(重写)的时候,这时候主函数引用eat方法时会输出子类的结果,但是实际上编译的时候这里还是父类的eat,但是在运行的时候,变成了子类自己的方法了,运行时绑定,我们称为动态绑定,是多态的基础。
那我们如何证明在编译的时候是父类的方法呢?打开我们的字节码文件,然后查看主函数:
可以看出:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
重写的设计原则:对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
前提:
思想:同一个方法,因为这个调用方法的引用,引用的对象不同,所执行的行为也是不一样的
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
优点:
缺点:代码的运行效率降低
我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func
我们可以知道以下状况:
结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成),可能会出现一些隐藏的但是又极难发现的问题。
继承和多态就完全写完了,你收获到了吗?