[Java]多态:从入门到入门
画师:竹取工坊
大佬们好!我是Mem0rin!现在正在准备自学转码。
如果我的文章对你有帮助的话,欢迎关注我的主页Mem0rin,欢迎互三,一起进步!
文章目录
在代码的编写工程中,会遇到功能类似的函数,比如猫和狗都会叫,只是叫声不同。如果将其抽象出来宠物的概念,就引申出了多态的概念
一、多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态性是对象多种表现形式的体现。
比如说宠物这个对象可以有不同的表达方式,可以是猫,可以是狗,当我们跟服务员说:“我要一个宠物。”那么店员可能会给你一个猫也有可能是狗,这样我们就说宠物具有多态性。
例如以下代码:
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强行转换成了一只猫,编译器也不会允许调用猫的方法的,所以说类型强转是有一定风险的。
下一章讲接口!!
更多推荐





所有评论(0)