目录
多态的概念
多态通俗来讲就是多种形态,具体来说,当不同对象完成某个相同的行为时,会产生出不同的状态。多态要实现必须满足以下条件:1.向上转型 2.子类和父类由同名的覆盖或重写方法 3.通过父类对象,调用父类和子类重写方法 4.满足以上三点只能说明会发生动态绑定。那什么是静态绑定什么是动态绑定呢?理解了上述内容,你才能理解什么是多态。其实多态就是一种思想,十分抽象。
向上转型
把子类对象给父类
有三种方式可以发生向上转型:直接赋值、方法传参、方法返回
直接赋值
先有一父类Animal,子类有Dog和Cat,按上面的说发,把子类对象给父类,我们可以这样写代码:
class Animal {
public String name;
public int age;
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(this.name+"正在吃饭!");
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Dog extends Animal {
public Dog(String name,int age) {
super(name,age);
}
public void wangWang() {
System.out.println(this.name+"汪汪汪!");
}
}
class Bird extends Animal {
public Bird(String name,int age) {
super(name, age);
}
public void fly() {
System.out.println(this.name+"正在飞!");
}
}
public class Test8_1 {
public static void main(String[] args) {
//向上转型
Dog dog = new Dog("小狗",2);
Animal animal = dog;
}
}
main方法中的两条语句合并起来就相当于:
//父类引用 引用了子类对象
Animal animal = new Dog("小狗",2);
方法传参
我们在Test8_1中写一个静态方法func1,参数是Animal类,实例化两个子类对象,将子类对象传入方法func1中,此时也发生了向上转型
public class Test8_1 {
public static void main(String[] args) {
Dog dog1 = new Dog("小狗1",2);
Bird bird1 = new Bird("小鸟1",1);
func1(dog1);
func1(bird1);
}
public static void func1(Animal animal) {
}
}
方法返回
将方法的返回值类型设为父类Animal,方法中返回子类对象,在main方法中用Animal类接受返回值。
public static void main(String[] args) {
Animal animal1 = func2();
Animal animal2 = func3();
}
public static Animal func2() {
return new Dog("小狗2",2);
}
public static Animal func3() {
return new Bird("小鸟2",1);
}
向上转型的优缺点
向上转型的优点是让代码实现更加灵活,缺陷是不能调用到子类特有的方法。
看一段代码,我们来理解上面这句话:
public static void main(String[] args) {
//父类引用 引用了子类对象
Animal animal = new Dog("小狗1",2);
animal.eat();
animal.wangWang;//报错
}
我们实例化了一个对象,此时animal可以调用父类Animal的eat方法,但是不能调用子类Dog的方法,也就是说此时 向上转型只能调用父类的方法,不能调用子类特有的方法。
方法重写
现在我们在Dog子类中也写一个和父类同名的eat方法,看看程序运行结果会不会有变化。
public void eat() {
System.out.println(this.name+"吃狗粮!");
}
输出结果:
为什么这是调用的又是子类的eat方法了呢?
大家有没有注意到,父类的eat和子类的eat方法的方法名、返回值类型、参数列表(个数,类型,顺序)都一样。
其实这就是方法重写或方法覆盖。方法的重写:1.方法名必须相同 2.参数列表必须相同(类型、个数、顺序) 3.返回值必须相同,可以不能但是两者返回值类型必须是父类和子类的关系(协变类型)。满足这三点之后,才构成了方法的重写。
不能发生重写的情况:1.被private修饰的方法不能进行重写。2.被static修饰的方法不能进行重写。3.被fianl修饰的方法(密封方法)不能进行重写。4.访问修饰限定符 private < 默认权限 < protected < public ,要求子类修饰限定符的权限要大于等于父类修饰限定符的权限。5.方法的返回值类型不同且不是父子类关系。6.构造方法不能进行重写。
加上@Override注解,可以帮助提醒你方法重写是否正确,如果方法重写不正确,@Override会提示。
动态绑定
接下来我们再来观察main方法中的代码:
public static void main(String[] args) {
//向上转型
Animal animal = new Dog("小狗1",2);
//通过父类引用 调用这个父类和子类重写的方法
animal.eat();
}
main方法中只做了两步:1.向上转型 2.通过父类引用,调用这个父类域子类重写的方法。程序的结果是:调用了子类的方法。这个过程我们就叫做:运行时绑定或者动态绑定。运行时绑定是多态的基础。
静态绑定
除了动态绑定,我们还有静态绑定或者编译时绑定,就是在编译时就能确定调用哪个方法了。比如:方法的重载。通过方法的参数等,在编译的时候就能确定她调用的是哪个方法。
我们在out目录下找到字节码文件所在的位置,在目录框中打开cmd,输入命令 javap -c 文件名
找到main函数
向下转型
将一个子类对象经过向上转型后当成父类方法使用,再无法调用子类的方法,但有时候需要调用子类特有的方法,此时将父类引用再还原为子类对象就称为向下转型。
public static void main(String[] args) {
Animal animal1 = new Bird("小鸟1",1);
//animal1.fly();此时不能调用子类特有的fly()方法
//向下转型 但是不安全
Bird bird1 = (Bird) animal1;
bird1.fly();
}
为什么不安全呢?我们再看一段代码
public static void main(String[] args) {
Animal animal2 = new Dog("小狗2",2);
Bird bird2 = (Bird) animal2;
bird2.fly();
}
此时这段代码并没有报红,我们运行以下看结果:
因此向下转型必须做检查,通过关键字instanceof把代码改进一下:
Animal animal2 = new Dog("小狗2",2);
if (animal2 instanceof Bird) {
Bird bird2 = (Bird) animal2;
bird2.fly();
}else {
System.out.println("不能飞!");
}
目前为止,我们只是理解了动态绑定,但是到底什么是多态?多态的思想是什么样子的?下面我们看一个例子:
class Shape {
public void draw() {
System.out.println("画图形!");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("矩形");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("圆");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("❀");
}
}
我们定义了一个父类Shape,父类中有一个draw方法,子类Rect、Circle、Flower都重写了父类中的draw方法,现在我们要画出:圆、矩形、❀。我们应该如何做呢?
public class Test8_2 {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Circle circle = new Circle();
Rect rect = new Rect();
Flower flower = new Flower();
drawMap(circle);
drawMap(rect);
drawMap(flower);
}
}
这是输出结果为:
下面我们来观察一下代码,理解到底什么是多态
使用多态的优缺点
1.能够降低代码的“圈复杂度”,避免使用大量的if-else。我们可以粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为“圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构。
如果在没有多态的情况下,我们要打印“圆 矩形 圆 矩形 ❀”我们应该如何打印呢?
public class Test8_2 {
public static void drawMaps() {
Circle circle = new Circle();
Rect rect = new Rect();
Flower flower = new Flower();
String[] strings = {"circle","rect","circle","rect","flower"};
for (String s:strings) {
if (s.equals("circle")) {
circle.draw();
} else if (s.equals("rect")) {
rect.draw();
} else if (s.equals("flower")) {
flower.draw();
}
}
}
public static void main(String[] args) {
drawMaps();
}
运行结果:但此时你会发现代码中有大量的if-else,那么我们用多态应该怎么做呢?
public class Test8_2 {
public static void drawMaps() {
Circle circle = new Circle();
Rect rect = new Rect();
Flower flower = new Flower();
Shape[] shapes = {circle,rect,circle,rect,flower};//发生了向下转型
for (Shape shape:shapes) {
shape.draw();
}
}
public static void main(String[] args) {
drawMaps();
}
这是代码就简洁许多了。
2.可扩展能力很强
如果我们还想花三角形,直接定义一个三角形的子类即可。
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("🔺");
}
}
在drawMaps方法中实例化三角形的对象就可以了
Circle circle = new Circle();
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
Shape[] shapes = {circle,rect,circle,rect,flower,triangle};//发生了向下转型
for (Shape shape:shapes) {
shape.draw();
}
多态的缺点:代码的运行效率降低。1.属性没有多态性。2.构造方法没有多态性
避免在构造方法中调用重写方法
现在有一个父类B,其中有一构造方法调用了func(),子类D中重写了func()
class B {
//构造方法
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func()"+num);
}
}
现在在main方法中实例化一个D类的对象
public class Test8_3 {
public static void main(String[] args) {
D d = new D();
}
}
当代码执行时,会调用D类默认的不带参数的构造方法,根据我们前面讲过的知识,会先执行父类B的构造方法,再执行子类D的构造方法,现在问题来了,父类B的构造方法中的func()是子类的还是父类的呢?我们运行代码看结果
现在我们知道,父类构造方法中的func()调用的是子类的func()方法,但是这里的num值为什么是0呢?因为父类调用子类的func()时,子类还没有完成构造,num并没有被初始化。这种代码不建议写,尤其是在父类的构造方法中忽然调用重写方法,可能会出现一些隐藏的但是又极难发现的问题。
这部分的语法难以理解,希望你可以多看几遍。