好久不见啊,兄弟们!!这不将近期末考试了吗,阿涛平日里课听的不多,所以最近都在疯狂补课,祖宗之法也可变,阿涛的学校终于不是二十周校历了!!希望从今往后我们的生活都能够回归正轨吧!
面向对象的三大特性:封装、继承、多态。封装和继承我们已经在之前的博客中进行了一定的说明,今天我们站好最后一班岗,讲一讲多态!
什么是多态?
我觉得初学多态可以从字面上来理解:面向不同的对象时,会有多种形态。
重写
之前我们讲重载的时候,提过两者之间的差别,我们来复习一下。
重载是在同一个类中(或者继承关系),对于方法我们需要不同的返回类型(重载对于返回类型不做要求,甚至于可以没有返回类型),参数列表(类型个数顺序):
public int add(int a,int b){
return a+b;
}
public int add(int a){
return a;
}
public double add(double a,int b){
return a+b;
}
方法名相同的方法之间,不同的仅仅是参数列表和返回类型,这些方法之间构成了重载,也就不需要我们程序猿额外进行大量无意义的记忆。
那么重写是什么呢?
public class Animal {
public String name;
public int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void eat(){
System.out.println(this.name+"eating");
}
}
public class Dog extends Animal{
public int height;
public void eat(){
System.out.println(this .name+"恰狗粮");
}
public Dog(String name,int age){
this.name=name;
this.age=age;
}
public Dog(){
}
}
本来我是想在一个.Java文件下面写的,但是后来发现最好还是一个类写成一个Java文件好一点。
我们来分析一下:Dog这个类继承了Anima这个类,那么Anim类中非静态的属性、方法都会被Dog继承,本来在Animal类中有eat方法,但是我们在Dog类中重写了这个方法,那么下面我们测试的时候调用的就是我们在Dog里面写的这个方法。
那么重写一般用于继承关系中,在子类中重写父类已有的方法,重写的方法函数名、返回类型、参数列表都要一样,不然怎么能叫做重写呢?
关于重写我还有几点想强调一下:
1.关于重写的两个方法,他们的返回类型必须相同或者退而求其次,必须是父子类关系,构成协变类型。
2.在子类中重写的方法,其访问权限必须大于父类方法的访问权限!
3.private、static、final修饰的方法不可以被重写:
private:被private修饰的只能在当前类中使用,出了父类在子类中怎么能够使用呢?
static:被static修饰的静态方法就就是类方法了,就不属于对象的了,而重写基本上就是为了多态服务的,多态是面向对象的三大特性,现在类方法甚至都不需要对象了,那我们不就是瞎忙活了嘛?
final:被final修饰的变量是常变量,被final修饰的引用不可以指向其他的引用,被final修饰的方法不可以被重写!
向上转型和动态绑定
想要发生多态,向上转型和动态绑定是基础。
一般来说,我们习惯于把父类放在上面,基础这个普遍认识,我们说的向上转型就是把一个子类对象赋值给一个父类引用,或者说,就是使用父类引用引用子类对象!
在第一行就发生了我们所说的向上转型,可是为什么第二行报错了呢?我们明明在Dog类中定义了height这个成员变量啊!animal也确实是指向我们new出来的Dog对象啊!
在这一点上我们可以这样理解:无论anima指向谁,它始终是一个Animal类型的引用,那么它就只可以使用Animal中的字段和方法!
此时此刻兄弟们或许还是没有意识到我这张图片的用意。如果是刚才我们说的animal这个引用只可以访问Animal类中的方法,那么为什么此时此刻我们打印出来的是Dog中的eat方法呢?
这就是我们多态实现的第二个基础:动态绑定。
也就是说在编译期间我们调用的确实是animal的eat方法,但是在运行期间,会发生动态绑定,绑定到我们的Dog对象的eat方法。
向下转型
有了向上转型相应的我们也就会有向下转型,通过类比思想,不难总结出向下转型就是通过子类引用引用父类对象:
这里报错的原因是因为类型不匹配,我们可以很轻易的把一个子类对象赋值给一个父类引用,因为小狗本身就是动物,但是我们怎么敢说每一个动物都是小狗呢?
通过这一系列的操作,这下子从效果上来说我们的dog引用此时就指向了一个Dog对象,那么我们就可以通过dog引用访问Dog对象中的字段和方法了。
但是这样子的向下转型是有大的风险的:
Cat cat=new Cat("gzj",20);
Animal animal=cat;
Dog dog=(Dog)animal;
兄弟们且来看这串代码,在编译器看来这是没有问题的,但是我们细细分析,最终的效果就是我们用了一个Dog类型的引用指向了一个Cat对象,这合理吗?这显然是不行的:
只要一运行,这个程序的遮羞布就会被薅下来,程序的丑陋就会一览无余,那我们就想啊,能否在向下转型的时候进行一定的检查呢?诶,我们还真有:
instanceof
Cat cat=new Cat("gzj",20);
Animal animal=cat;
if(animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.eat();
}else{
System.out.println("不匹配");
}
只有当animal指向Dog类型的时候我们才可以进行向下转型。
关于多态的知识点差不多就这么多了,最后呢,阿涛想给兄弟们看一下多态的一个小小的使用:
public static void shout(Animal animal){
animal.bark();
}
public static void main(String[] args) {
shout(new Dog());
shout(new Cat());
shout(new Wolf());
我们的Dog、Cat、Wolf三个类都继承了Animal类并且重写了其中的bark方法,接着通过传参我们就实现了向上转型:shout方法的参数是Anima类型的引用,而我们传的确实它的子类对象,接着我们通过父类引用调用子类中重写的方法,在运行时发生动态绑定,最终的效果就是我们的方法完全一样,只是因为传参的不同,就呈现出了不同的形态,这就是我们说的多态!
好了,那么关于多态的重点知识大概也就这么多了,自由的感觉真好,希望大家也要好好学习啊!
百年大道,你我共勉!!