目录
一、学习多态之前需要的知识储备
二、重写
1.什么是重写
2.重写可以干嘛
3.怎么书写重写
4.重载与重写的区别
三、向上转型
1.什么是向上转型?
2.向上转型的语法
3.向上转型的使用场景
四、多态是什么
六、多态实现
七、多态的好处
八、多态的缺点
九、向下转型
一、学习多态之前需要的知识储备
学习多态之前,我们得知道多态需要的知识储备,有了这个基础,我们才能更好的学习多态。
多态的发生需要三个条件(三个“要”):
(一)要继承
(二)要重写
(三)要向上转型
在之前的博客谈及过继承,可以翻阅我之前的博客,这里就不赘述了。因此,我们先需要学习重写与向上转型。请看:
二、重写
1.什么是重写
重写是指:书写方法时返回值和形参都不能改变。即外壳不变,核心重写!简单来讲:增加多点业务。
例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改(因为可能还有老用户在使用)正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。
2.重写可以干嘛
重写就是为了服务多态,为多态做继承。也可以认为是不同人做不同事,多写出一个一模一样的方法,打个不恰当的比方就是看人下菜。
3.怎么书写重写
public class Animal {
String name;
int age;
//构造方法
public Animal(String name,int age){
this.name=name;
this.age=age;
}
//这个eat方法与下面的eat方法一模一样(除了里面的内容),形成重写
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name, age);
}
//eat方法重写,对于不同的对象(狗狗)用不同的方法
public void eat(){
System.out.println(this.name+"吃骨头");
}
}
class Cat extends Animal{
public Cat(String name,int age){
super(name, age);
}
//eat方法重写,对于不同的对象(猫猫)用不同的方法
public void eat(){
System.out.println(this.name+"吃鱼");
}
}
【方法重写的规则】
1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致。
2.被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
3.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected。
4.父类被static、private修饰的方法、构造方法都不能被重写。
5.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写。
4.重载与重写的区别
三、向上转型
1.什么是向上转型?
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
2.向上转型的语法
语法格式:父类类型 对象名 = new 子类类型()
Animal animal = new Dog("puppy",2);
animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
3.向上转型的使用场景
【使用场景】
1. 直接赋值
2. 方法传参
3. 方法返回
public class Test {
//方法传参的方法
public static void eat(Animal animal){
animal.eat();
}
//方法返回的方法
public static Animal buyAnimal(String var){
if("狗".equals(var) ){
return new Dog("狗狗",1);
}else if("猫" .equals(var)){
return new Cat("猫猫", 1);
}else{
return null;
}
}
public static void main(String[] args) {
//直接赋值
Animal animal=new Dog("puppy",2);
//方法传参
eat(new Dog("puppy",2));
//方法返回
Animal animal2 = buyAnimal("狗");
}
}
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
四、多态是什么
既然我们了解了重写以及向上转型,加上之前博客提到的继承,我们接下来来学习一下多态。
首先,多态是什么呢?
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
六、多态实现
public class Animal {
String name;
int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name, age);
}
public void eat(){
System.out.println(this.name+"吃骨头");
}
}
class Cat extends Animal{
public Cat(String name,int age){
super(name, age);
}
public void eat(){
System.out.println(this.name+"吃鱼");
}
}
public class Test {
public static void main(String[] args) {
Animal animal1=new Cat("花花",2);
animal1.eat();
Animal animal2=new Dog("puppy",2);
animal2.eat();
Animal animal3=new Animal("caddy",3);
animal3.eat();
}
}
//执行结果:
花花吃鱼
puppy吃骨头
caddy吃饭
对于不同的对象调用,产生的结果不一样。
多态的本质就是动态绑定。动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
七、多态的好处
假设我们有以下代码:
class Shape {
//属性....
public void draw() {
System.out.println("画图形!");
}
}
class Rect 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("❀");
}
}
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
比如,假设我们不基于多态,我们会写出下列代码:
public static void drawShapes() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
for (String shape : shapes) {
if (shape.equals("cycle")) {
cycle.draw();
} else if (shape.equals("rect")) {
rect.draw();
} else if (shape.equals("flower")) {
flower.draw();
}
}
}
如果我们基于多态,会写出以下代码:
public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(),
new Rect(), new Cycle(),
new Rect(), new Flower()
};//我们通过把子类的对象放进父类的数组里面,也算向上转型
for (Shape shape : shapes) {
shape.draw();
}
}
2. 可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低。
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
八、多态的缺点
我们需要避免在构造方法中调用重写的方法:
一段有坑的代码.。我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法。并且在 B 的构造方法中调用 func。
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
构造 D 对象的同时, 会调用 B 的构造方法。
B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func。
此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0。
所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
九、向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
// 向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
// 编译失败,编译时编译器将animal当成Animal对象处理
// 而Animal类中没有bark方法,因此编译失败
// animal.bark();
// 向上转型
// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
cat = (Cat)animal;
cat.mew();
// animal本来指向的就是狗,因此将animal还原为狗也是安全的
dog = (Dog)animal;
dog.bark();
}
}
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。代码如下:
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
// 向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
if(animal instanceof Cat){
cat = (Cat)animal;
cat.mew();
}
if(animal instanceof Dog){
dog = (Dog)animal;
dog.bark();
}
}
}