赞
踩
多态:通俗来说,就是多种形态。具体点来说,就是同一件事,不同的对象去完成时会表现出不同的状态。
比如见下面这张图:
同样是打印的行为,但是打印机不同(对象不同),所表现的结果也不相同。
多态的体现:在代码运行时,当传递不同类的对象的时候,会调用对应子类中重写的方法,而不是调用父类的
(看到这里还是没有明白多态是正常的,必须要通过代码才能够更清晰的了解)
向上转型:把子类的对象给到父类,或者说是 父类的引用指向子类的对象
这里Cat类是继承Animal类,我们可以看到,animal这个引用的类型明明是Animal类型,但是指向的却是Cat这个类创建的对象。
虽然我们平常都说“=”运算符,左右两边的数据类型必须是要相同的,但是这个继承比较特殊。
这个例子就是父类的引用指向了子类的对象,也就是向上转型。
Animal animal = new Dog("圆圆",19);
public static void func1(Animal animal){
}
public static void main(String[] args){
Dog dog = new Dog("圆圆",19);
func1(dog);
}
public static Animal func2(){
Dog dog = new Dog("圆圆",19);
return dog;
}
总之,总的来说就是父类的引用指向了子类的对象
重写也称为 覆盖
重写的注意事项:
- 子类中重写方法的访问权限不能够比父类中被重写方法的访问权限更低——重写方法的访问权限 >= 子类方法的访问权限
- 父类中被static、private、final修饰的方法、构造方法都不能被重写
- 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.
- 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
重写是 方法名 、 返回类型 、 参数列表 都必须一样;
重载是要求方法的 参数列表必须改变 ,另外两个可变、可不变。
在前面类和对象的时候,我们学习了可以在类中快速创建一个toString()方法,用来打印子类的数据。
链接: 类和对象
其实这个过程就运用了方法的重写。
原理如下:
首先, Object默认是所有类的父类
这里println的方法原型是下图:
然后再看valueOf的方法原型:
可以看到这里调用了toString()方法,只不过是父类Object的,因此若是我们在类里面重写了toString()方法,就会调用自己的。
下面是原理图合集:
动态绑定对应的是方法重写,也就是多态。
在上述代码之后,若是我们在main方法里面写这样的代码:
Animal animal = new Dog("圆圆",19);
animal.eat;
我们会发现animal这个父类的引用,调用的是子类的eat方法。 我们把这个过程就叫做动态绑定。
所谓动态绑定就是指在程序运行的时候,将eat这个方法绑定到了子类的eat方法,因此调用的就是子类的eat
我们再通过汇编代码来看看动态绑定
静态绑定对应的是方法重载。
见下面代码:
add(int a,int b)
add(int a,int b,int c)
add(int a,int b,int c,int d)
main(){
add(1,2);
add(1,2,5);
add(1,5,6,8);
}
编译器通过传入的参数个数和类型就能够找到对应的方法,这就是静态绑定。
前面我们学习到向上转型就是父类的引用指向了子类的对象,可是向上转型有一个缺点,就是通过向上转型创建的变量只能够调用子类和父类共有的方法(重写的方法),但是不能够调用子类特有的方法。
为了解决这个问题就有了向下转型。向下转型也就是将父类引用再还原为子类对象即可,即向下转换
因此向下转型的发生必须有向上转型,并且这个父类引用只能够还原为该引用所指向的对象,不能是另外一个类的对象此外向下转型必须要进行强制类型转换
见代码:
public class test {
public static void main(String[] args) {
Animal animal = new Dog("圆圆",1);
Dog dog = (Dog) animal; //向下转型
dog.bark();
}
}
但是若是Cat cat = (Cat)animal;
这个就不可以,因为还原错误,animal指的是Dog类,不能还原为Cat类。
public class test {
public static void main(String[] args) {
Animal animal = new Dog("圆圆",1);
//如果animal引用的对象是Cat对象的实例
if(animal instanceof Cat){
Cat cat = (Cat) animal;
cat.miaomiao();
}else{
System.out.println("error");
}
}
}
这样就可以更加安全
直接见代码:
class Shape{ public void draw(){ System.out.println("画一个图形"); } } class Rect extends Shape{ @Override public void draw() { System.out.println("矩形"); } } class Triangle extends Shape{ @Override public void draw() { System.out.println("△"); } } class Cycle extends Shape{ @Override public void draw() { System.out.println("○"); } } class Flower extends Shape{ @Override public void draw() { System.out.println("❀"); } } public class test { public static void drawMap(Shape shape){ shape.draw(); } public static void main(String[] args) { Shape[] shapes = {new Cycle(),new Flower(),new Cycle(),new Rect(),new Triangle()}; for (Shape shape:shapes) { drawMap(shape); } } }
关于这个应用有几个巧妙的地方要说明:
- Shape[] shapes = {new Cycle(),new Flower(),new Cycle(),new Rect(),new Triangle()};这个形式是可以的,创建了一个Shape数组
- 用foreach进行遍历
- 使用了多态,是根据函数参数来实现向上转型,在drawMap()这个方法里面实现了多态
什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如
果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.
如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10 .
public static void drawMap(Shape shape){
shape.draw();
}
见下面的代码:
class B { public B() { // do nothing 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); } } public class Test { public static void main(String[] args) { D d = new D(); } } // 执行结果 D.func() 0
1. 这里父类的构造方法中func()调用的是子类中重写的方法,也就是发生了动态绑定
2. 由于父类构造方法先执行,因此在调用子类的重写方法的时候,num还没有初始化,这也是为什么执行结果num=0,因为子类此时还有没完成初始化
结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。