继承、多态、组合(Java系列5)
目录
前言:
1.继承
1.1继承的概念
1.2继承的语法
1.3父类成员访问
1.4super关键字
1.5super和this
1.6继承关系的执行顺序
1.7继承方式
1.8final关键字
2.继承与组合
3.多态
3.1多态的概念
3.2多态实现的条件
4.重写
4.1重写的概念
4.2方法重写的规则
4.3重写和重载的区别
5.静态绑定
6.动态绑定
7.向上转型
8.向下转型
结束语:
前言:
这次小编与大家分享一下Java中的继承与多态,谈一下何为继承,怎么继承,何为多态,为什么会存在多态,以及什么是组合。
1.继承
1.1继承的概念
继承机制:是面向对象程序设计使代码可以重复使用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了有简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码的复用。同时继承最大的意义就是对代码可以进行复用。
1.2继承的语法
在java中如果要表示继承关系,需要借助extends关键字,如下所示:
修饰符 class 子类 extends 父类 {
}
注意:
- 子类会将父类中的成员变量或者成员方法继承到子类中。
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要做继承了。
1.3父类成员访问
在子类方法中或者通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译器报错。
- 如果访问的成员变量与父类中成员变量与父类中成员变量同名,则优先访问自己的。
- 成员变量访问时遵循就近原则,自己有优先自己的,如果没有则向父类中寻找。
成员方法名字相同时:
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法中传递的参数选择合适的方法访问,如果没有则报错。
1.4super关键字
作用:在子类方法中访问父类的成员。
代码如下所示:
public class Animal {
public String name;
public String sex;
public int age;
public void Animal(){
}
public void eat() {
System.out.println(name + "正在吃!");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.age = 10;
dog.sex = "雌性";
dog.colour = "black";
dog.name = "可乐";
dog.eat();
}
}
class Dog extends Animal {
public String colour;
public void eat() {
super.eat();//调用的是父类的eat;
System.out.println(name + "正在吃狗粮!");
}
}
结果如下所示:
注意:
- 只能在静态方法中使用。
- 在子类访问父类的成员变量和方法。
- super其实只是一个关键字,最大的作用其实就是在你写代码的时候或者是在你的阅读代码的时候能够更加方便,体现出了更好的可读性。
1.5super和this
相同点:
- 都是java中的关键字。
- 只能在类的非静态方法中使用,用来访问非静态成员和字段。
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。
不同点:
- this是当前对象的引用,当前对象即调用实例的方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用。
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。
- 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现。
- 构造方法中一定会存在super(…)的调用,用户没有写编译器也会调用,用户没有写编译器也会增加,但是this(…)用户不写则就是没有。
1.6继承关系的执行顺序
执行顺序:
- 父类静态代码块优先于子类静态代码块执行,且是最早执行的。
- 父类实例代码块和父类构造方法紧接着执行。
- 子类的实例化代码块和子类构造方法紧接着执行。
- 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行。
1.7继承方式
单继承:
代码如下所示:
public class A {
}
class B extends A{
}
多层继承:
代码如下:
public class A {
}
class B extends A{
}
class C extends B{
}
不同类继承同一个类:
代码如下所示:
public class A {
}
class B extends A{
}
class C extends A{
}
多继承(不支持):
注意:java中不支持多继承。
1.8final关键字
final关键字可以用来修饰变量,成员方法以及类。
1.修饰变量或字段,表示常量(即不能修改)
如下所示:
如果修改就会报错。
2.修饰类:表示次类不能被继承,如下所示。
3.修饰方法:表示该方法不能被重写。
如果不被final修饰之前是可以进行重写的,代码如下所示:
public class Animal {
public String name;
public int age;
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class cat extends Animal{
@Override
public void eat() {
System.out.println(name + "正在吃猫粮!");
}
}
但是在被final修饰之后就不可以在进行重写了,代码如下所示:
下面我们在看一个有关于final修饰的的一个例子:
代码如下所示:
public class Test1 {
public static void main(String[] args) {
final int[] array = {1,2,3,4};
// array[0] = 19;①
// array = new int[];②
}
}
大家现在思考一下上面代码中的①和②哪一个表达是正确的。
答案是①正确②错误,为什么呢?下面小编给大家画图进行分析一下。
2.继承与组合
组合并不是面向对象的三大特性之一,但是它和继承相似,组合也是表达类之间的关系,它也可以达到代码的复用,组合表示的是一种组成包含关系,类似于英语中的has-a的关系,下面我们来具体举一个例子让大家深切的感受一下什么是组合。
比如汽车中的一些零部件轮胎、发动机、方向盘、车载系统等等都是组成汽车的部件。
代码如下所示:
class Tire{
//轮胎
}
class Engine{
//发动机
}
class VehicleSystem{
//车载系统类
}
public class Car {
//这样就可以重复使用其中的一些属性了。
private Tire tire;
private Engine engine;
private VehicleSystem vehicleSystem;
}
3.多态
3.1多态的概念
通俗的来讲就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。我们也可以这样理解:当父类引用的对象不一样的时候表现出的行为是不一样的。
3.2多态实现的条件
在java中要实现多态,必须要满足如下几个条件,缺一不可:
- 必须在继承体系下实现。
- 子类必须要对父类中方法进行重写。
- 通过父类的引用调用重写方法。
下面我们先来具体了解一下什么是重写和向上转型。
4.重写
4.1重写的概念
重写也称为覆盖,重写是子类对父类的非静态、非private修饰、非final修饰、非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。重写的好处在于子类可以根据需要,定义特定于自己的行为,也就是说子类能够根据需要实现父类的方法。
4.2方法重写的规则
- 子类在重写父类的方法时,一般必须与父类方法原型一致:返回值类型、方法名(参数列表)、方法名称。
- 被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
- 访问权限不能比父类中被重写的方法的访问权限更低,例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected。
- 父类被static、final、private修饰的方法都不能被重写。
- 重写的方法,可以使用@Override注解来显示指定,有了这个注解能帮我们进行一些合法性校验,例如不小心将方法名字拼写错了,那么此时编译器就会发现父类中没有这个方法,就会编译报错,提示无法构成重写。
当方法名字不一样时,@Override就会起到一定的监视作用来提醒程序员编写可能出错了。
4.3重写和重载的区别
区别点 | 重写 | 重载 |
参数列表 | 一定不能修改 | 必须修改 |
返回值类型 | 一定不能修【除可以非构成父子关系】 | 可以修改 |
访问限定符 | 一定不能做更加严格的限制(可以降低限制) | 可以修改 |
5.静态绑定
静态绑定也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用哪个方法,典型代表函数重载。就是在编译的时候我们就已经知道调用哪个方法了。
6.动态绑定
动态绑定也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用哪个类的方法。
动态绑定的条件:
- 向上转型。
- 重写。
- 通过父类引用调用这个父类和子类重写的。
7.向上转型
实际就是创建一个子类对象,将其当成父类对象来使用。通俗点讲就是把子类给父类。
语法格式:父类类型 对象名 = new子类类型();
有三种方式进行向上转型:
- 直接赋值
- 方法传参
- 作为返回值
具体实现如下面的代码所示:
class Base{
public int age;
public String name;
public Base(int age, String name) {
this.age = age;
this.name = name;
}
}
class Subclasses extends Base{
public int age;
public String name;
public Subclasses(int age, String name) {
super(age, name);
this.age = age;
this.name = name;
}
}
public class Test3 {
public static void fun1(Base base) {
//2.方法传参
}
public static Base fun2(){
//3.作为返回值:可以返回任意子类的对象。
return new Subclasses(13,"旺财");
}
public static void main(String[] args) {
//1.直接赋值
Base subclasses = new Subclasses(12,"小李");
//2.方法传参
fun1(subclasses);
//3.作为返回值
}
}
注意:当发生向上转型之后,此时通过父类的引用只能访问父类自己的成员,不能访问子类的成员,即父类的访问范围变小,子类的访问返回变大。
向上转型的优点:让代码实现更简单灵活。
向上转型的缺点:不能调用到子类特有的方法。
8.向下转型
将一个子类对象经过向上转型之后当成父类方法使用,在无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时父类引用在还原为子类对象即可,即向下转型。
代码如下所示:
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
//被final修饰
System.out.println(name + "正在吃饭!");
}
// public final void eat(){
// //被final修饰
// System.out.println(name + "正在吃饭!");
// }
}
class cat extends Animal{
public cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
//会报错
System.out.println(name + "正在吃猫粮!");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name + "正在吃狗粮!");
}
}
class Test {
public static void main(String[] args) {
Dog dog =(Dog) new Animal("可乐",12);//向下转型,但是需要强转
}
}
向下转型用的比较少,而且不安全,万一转型失败,运行时就会抛出异常,java中为了提高向下转型的安全性,引入instanceof,如果该表达式为true,则可以安全转换。
代码如下所示:
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
//被final修饰
System.out.println(name + "正在吃饭!");
}
// public final void eat(){
// //被final修饰
// System.out.println(name + "正在吃饭!");
// }
}
class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
//会报错
System.out.println(name + "正在吃猫粮!");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name + "正在吃狗粮!");
}
}
class Test {
public static void main(String[] args) {
Dog dog = new Dog("可乐",12);
//Dog dog =(Dog) new Animal("可乐",12);//向下转型,但是需要强转
Cat cat = new Cat("咪咪",12);
Animal animal = cat;
Animal animal1 = dog;
//instanceof就是判断animal是不是引用了Cat这个对象。
if(animal instanceof Cat) {
cat = (Cat) animal;
cat.eat();
}
//instanceof就是判断animal是不是引用了Dog这个对象。
if(animal instanceof Dog) {
dog = (Dog) animal;
dog.eat();
}
}
}
结果如下所示:
结束语:
这次和大家分享了继承、多态、组合希望通过这次分享能够加深大家对java中面向对象的了解,希望对大家有所帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)