目录
在代码的编写工程中,会遇到功能类似的函数,比如猫和狗都会叫,只是叫声不同。如果将其抽象出来宠物的概念,就引申出了多态的概念
一、多态
多态是同一个行为具有多个不同表现形式或形态的能力。 多态性是对象多种表现形式的体现。 比如说宠物这个对象可以有不同的表达方式,可以是猫,可以是狗,当我们跟服务员说:“我要一个宠物。”那么店员可能会给你一个猫也有可能是狗,这样我们就说宠物具有多态性。 例如以下代码:
public 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(name + "正在吃饭"); }}public class Cat extends Animal{ public Cat(String name, int age) { super(name, age); }
public void eat() { System.out.println(name + "正在吃鱼"); }
}public class Dog extends Animal{ public Dog(String name, int age) { super(name, age); }
public void eat() { System.out.println(name + "正在吃骨头"); }
}public class Test { public static void eat(Animal animal) { animal.eat(); }
public static void main(String[] args) { Cat cat = new Cat("咪咪", 2); Dog dog = new Dog("旺财", 3);
eat(cat); eat(dog); }}运行的结果是
咪咪正在吃鱼旺财正在吃骨头这时候我们发现,相同的函数eat(),对于不同的对象cat和dog,会对应不同的函数。
多态的使用必须满足下列三个条件:
1.必须要在继承体系下
2.子类要对父类的方法进行重写
3.通过父类的引用调用重写的方法
其中第二点对应的是方法的重写,第三点对应的是对象的向下转型,接下来会分成这两个部分进行阐述。
二、方法的重写
1.重写的概念
重写(override)是子类对父类非静态、非private、非final、非构造方法的实现过程的改写,要求名称,参数列表相同,改变的是函数的核心(需要注意的是,方法重写不要求返回值相同,但必须是父类返回值的派生类,比如Animal和Dog)。
好处是子类可以根据需要,定义特定于自己的行为,也就是说子类可以根据自己的需要实现父类的方法。
例如下面的方法就构成重写:
public class Animal { public String name; ...... public void eat() { System.out.println(name + "正在吃饭"); }}
public class Dog extends Animal{ ...... public void eat() { System.out.println(name + "正在吃骨头"); }
}public class TestDog{
public static void main(String args[]){ Animal a = new Animal(); // Animal 对象 Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法 }}结果是:
动物可以移动狗可以跑和走可以看到哪怕都是Animal类,a类会调用Animal类的move()方法,但是b类却会调用Dog类的move()方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java 虚拟机 (JVM) 指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为Animal类中存在move方法,然而运行时,运行的是特定对象的方法。
2.重写的原则
方法重写详细的原则如下图:

3.super关键词的使用
当子类需要调用父类的被重写方法的时候,可以用super关键词。
class Animal{
public void move(){ System.out.println("动物可以移动"); }}
class Dog extends Animal{
public void move(){ super.move(); // 应用super类的方法 System.out.println("狗可以跑和走"); }}
public class TestDog{
public static void main(String args[]){
Animal b = new Dog(); // b.move(); //执行 Dog类的方法
}}以上实例编译运行结果如下:
动物可以移动狗可以跑和走4.重写和重载
方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载 (Overloading)。 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写 (Overriding)。
一句话概括就是:重载是类的多态性的体现,重写是子类和父类的多态性的体现。 具体的书写方式也有区别。
| 区别点 | 重写 | 重载 |
|---|---|---|
| 参数类型 | 不能修改 | 必须修改 |
| 返回类型 | 不能修改(除非构成子父类关系) | 可以修改 |
| 访问限制符 | 不能作更严格的限制 | 可以修改 |
| 异常 | 可以减少或删除,一定不能抛出新的或者更广的异常 | 可以修改 |
三、向下转型和向上转型
1.向上转型
即创建一个子类对象,并当作是父类对象来使用。 更抽象点,就是把“更具体的类型”当成“更一般的类型”来用。 举个例来讲,你有一只狗,那我把他当作是一只宠物来使用与宠物相关的规定也是完全可行的。
一个简单的例子(直接赋值)
public interface Vegetarian{}public class Animal{}public class Deer extends Animal implements Vegetarian{}
Deer d = new Deer();
Animal a = d;Vegetarian v = d;Object o = d;Deer 既是 Animal,也是 Vegetarian,同时任何类最终也都是 Object。
多态性的实例解析如下: 一个 Deer IS-A(是一个) Animal 一个 Deer IS-A(是一个) Vegetarian 一个 Deer IS-A(是一个) Deer 一个 Deer IS-A(是一个)Object
向上转型的过程实际是一种“视角”的转变。
我们知道,访问一个对象的唯一方法就是通过引用型变量。
因此当我们声明Animal a = d;的时候,实际上a指代的同样是d的鹿,只是我们用一个更加一般和抽象的视角看待它。
这样我们就可以用Animal类的方法来处理对象了。
方法传参
除了直接复制,向上转型还可以以方法传参的形式进行,比如前面的:
public class Test { public static void eat(Animal animal) { animal.eat(); }
public static void main(String[] args) { ... eat(cat); eat(dog); }}因为猫猫狗狗都是动物,因此可以统一送进Animal的入口,调用方法。
这样就可以把猫猫狗狗都作为参数,调用eat()函数,实现代码的复用。
返回值
向上转型还可以应用在方法的返回值中,如下:
public class TestAnimal { //作返回值:返回任意子类对象 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 = buyAnimal("狗"); animal.eat();
animal = buyAnimal("猫"); animal.eat(); }}前面是作为参数,也就是设立了一个宽松的入口,而返回值则是设立了一个宽松的出口,猫猫狗狗可以带着自己的属性走出来。
但是这样出来的对象还是Animal,我们没办法调用子类的特殊方法,那么有没有办法可以解决呢?那就是向下转型。
2.向下转型
向下转型的形式是类型强转,比如:
public class TestAnimal { public static void main(String[] args) { Dog dog = new Dog("小七", 1);
// 编译失败,编译时编译器将animal当成Animal对象处理 // 而Animal类中没有bark方法,因此编译失败 Animal animal = dog; animal.bark();
// 类型强转成Dog类对象,因为animal本来就指代的是狗,因此强转并没有风险 dog = (Dog)animal; dog.bark(); }}但是我们要注意,当我们看待animal的时候,我们是不知道它具体是哪只动物的,因此向下转型其实是在向编译器打包票,拍拍胸脯说放心好了这是一只狗,所以编译器才允许调用bark()方法。
但是如果把animal强行转换成了一只猫,编译器也不会允许调用猫的方法的,所以说类型强转是有一定风险的。
下一章讲接口!!
部分信息可能已经过时











