浅谈多态——继上一章

浅谈继承和多态

上篇文章已谈到封装,接下来就继续来浅谈下继承和多态吧。

当我们对一个类进行实例化对象后,我们实例化对象就可以表达现实中的实体。

但是我们仔细想一想,世界东西繁多复杂,每个东西都是都不可能是独善其身的,总归和其他东西有些关联的。

请看以下代码:

就会发现,两个类中就有一个共同的属性和方法。

那能否将这些共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。

讲到这里,那什么是继承?

继承的概念:

是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性 的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

例如上面的狗和猫的那样的类,我们将其共性抽取,创建一个新的类(例如:Animal)。

让我们的猫类和子类继承这个Animal类,然后我们就将这个猫类和狗类称为:子类/派生类。Animal类称为:父类。

子类可以复用父类的成员,而子类只需要关心自己的新增成员或方法。

从刚刚的讲述中得出,继承最大的作用就是:实现代码复用,还有就是来实现多态(后序讲)

讲完概念,就来介绍下语法吧:

当然class前也可以加关键字。

注意:

  1. 子类会将父类中的成员变量或者成员方法继承到子类中了。
  2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承。

到这里,又有一个问题啦。那我继承父类的方法了,那我子类怎么访问父类的方法呢?

那么请看下接下来的代码吧。

看完代码,我们细想,子类和父类中是不是共同存在一个相同类型和相同名字的变量?

那这样的话,那就又有问题了,那么一旦访问,先访问谁的呢?要是不同名字,也是这样的访问吗?

那好,我们分两种情况回答:

◎ 子类和父类的成员变量名字相同时:

1.通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。

2.通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错。

◎ 子类和父类的成员变量名字不相同时:

成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时 再到父类中找,如果父类中也没有则报错。

那么这个问题解决完了后,我又手痒了,我还想在子类方法中一步访问父类成员于方法,该如何做呢?

那么就要介绍这个super关键字了。

那么就看看它是怎么用的吧:

所以:在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。

注意事项:

  1. 只能在非静态方法中使用 2. 在子类方法中,访问父类的成员变量和方法。

那前面的类和继承讲到构造方法。

那么这里有没有呢?

那是当然的,那么这个在子类中就叫子类的构造方法:

在这里需要说明的是:

在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执 行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。

然后我们再来看这下这个代码:

在这个图片中,我在父类加入了构造方法,而且在子类的的构造方法中添加了super关键字。

所以这里要说明以下内容:

1.若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构 造方法

  1. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。
  2. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
  3. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现

那么问题又来了,这里讲了super和this关键字,那么它们有什么区别呢?

相同点:

  1. 都是Java中的关键字
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。

不同点:

  1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成 员的引用
  2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。
  3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现。
  4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。

既然区别讲完了,那就讲下这个初始化吧,之前也同样讲了初始化,之前的初始化是这样的:

  1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
  2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行。

而这里的涉及到的初始化就有些不一样了,直接上结论:

  1. 父类静态代码块优先于子类静态代码块执行,且是最早执行 2、
  2. 父类实例代码块和父类构造方法紧接着执行 3、
  3. 子类的实例代码块和子类构造方法紧接着再执行 4、
  4. 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行。

那么接下来讲讲类的继承方式吧。

单继承:

多层继承:

不同类继承同一个类:

多继承(java中不支持):

那如此看来,继承方式虽不多样,但我们依然可以写出各种各样的,其实我们不愿类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层 次太多, 就需要考虑对代码进行重构了.

那么我们就可以用到final关键字。

语法很简单,直接在方法的修辞词前面加或者成员变量前加,两者分别表示为:

修饰类:表示此类不能被继承,. 修饰变量或字段,表示常量(即不能修改)。

那么讲完这个就讲下组合吧,其实它与继承还是有点类似的。

但这个组合他又不想继承那样的语法,那么它具体就是这样写的:

// 发动机类
class Engine {
    private int horsepower;

    public Engine(int horsepower) {
        this.horsepower = horsepower;
    }
    // 启动发动机的方法
    public void start() {
        System.out.println("Starting engine with " + horsepower + " horsepower...");
    }
}
// 汽车类 - 使用组合包含发动机
class Car {
    private Engine engine;

    // 构造函数接收一个发动机对象
    public Car(Engine engine) {
        this.engine = engine;
    }
    // 通过组合对象的方法来操作汽车
    public void startEngine() {
        engine.start();
    }
   }
// 主程序
public class Main {
    public static void main(String[] args) {
        // 创建一个发动机对象
        Engine myEngine = new Engine(200);
        // 创建一个汽车对象,并组合(包含)上述发动机对象
        Car myCar = new Car(myEngine);
        // 通过汽车对象调用发动机的方法
        myCar.startEngine();
    }
}

注意:组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。

讲完这个,那么就讲讲目前最重要的一点了,就是多态。

那么什么是多态呢?

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

那么怎样实现多态呢?

  1. 必须在继承体系下。
  2. 子类必须要对父类中方法进行重写。
  3. 通过父类的引用调用重写的方法。

代码实现如下:

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

这里遇到重写:

那么就讲讲什么是重写吧:

重写:也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定 于自己的行为。 也就是说子类能够根据需要实现父类的方法。

其规则如下:

1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致

2.被重写的方法返回值类型可以不同,但是必须是具有父子关系的

3.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方 法就不能声明为 protected

4.父类被static、private修饰的方法、构造方法都不能被重写。

5.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写。

当然,这里的重写设计原则参考如下:

对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容, 并且添加或者改动新的内容。

其实这里也设计到了动态绑定,它是一种技术手段来的。

其定义为:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体 调用那个类的方法。

还有一个向上转型,这也是能体现多态的特性之一:

那什么是向上转型呢?

实际就是创建一个子类对象,将其当成父类对象来使用。

语法格式:父类类型 对象名 = new 子类类型()

代码如下:

Animal animal = new Cat("元宝",2);

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。

向上转型的优点:让代码实现更简单灵活。 向上转型的缺陷:不能调用到子类特有的方法。

那么有了向上转,当然也有向下转型。

那什么是向上转型呢:

向下转型(Downcasting)是指将父类类型的引用转换为子类类型,以便访问子类特有的属性和方法

代码范例入下:

class Animal {

void walk() {

System.out.println("Animal is walking");

}

}

class Dog extends Animal {

@Override

void walk() {

System.out.println("Dog is walking");

}

void bark() { // 子类特有的方法

System.out.println("Dog barks");

}

}

public class Main {

public static void main(String[] args) {

Animal myAnimal = new Dog(); // 向上转型

myAnimal.walk(); // 输出 "Dog is walking"

// 向下转型,检查并调用子类特有的方法

if (myAnimal instanceof Dog) {

Dog myDog = (Dog) myAnimal; // 强制向下转型

myDog.bark(); // 输出 "Dog barks"

} else {

System.out.println("The animal is not a dog.");

}

}

}

至此,已完。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值