目录
引例
面向对象编程有三大特征:封装、继承、多态。现在我们对多态展开详细的了解。
- 在现实中,如老师,一位具体的老师他既是老师,也是人,既表现出了多种形态
- 在Java语言中,多态就是一个引用可以表现出多种状态例如:
我们定义两个类:
public class Animal {
String name;
public Animal(){}
public Animal(String name){
this.name=name;
}
public void eat(String food) {
System.out.println(name+"正在吃"+food);
}
}
public class Dog extends Animal{
String name;
public Dog(String name){
super(name);//调用父类的有参构造
}
public void eat(String food) {
System.out.println(name+"正在吃"+food);
}
public static void main(String[] args) {
Animal animal = new Animal("动物");
Dog dog = new Dog("小狗");
animal.eat("食物");
dog.eat("骨头");
}
运行结果:
可以看到两个对象调用的都是eat方法,运行结果却不一样,这就是多态的表现。
向上转型
- 语法:父类 引用名称 = new 子类实例();那么向上转型怎么使用呢,承接上面的例子。我们做如下转换,将dog引用前面的类换成父类Animal,运行结果和上面一样
Dog继承Animal之间是一种天然的 is a 关系 :dog ia an animal, 所以可以自然而然的向上转型。
有了向上转型之后,我们将代码做如下转变:
public class Test {
public static void fun(Animal animal,String food){
animal.eat(food);
}
public static void main(String[] args) {
Animal animal = new Animal("动物");
Animal dog = new Dog("小狗");
fun(animal,"食物");
fun(dog,"骨头");
}
}
运行结果:
上述代码也中的Animal dog = new Dog("小狗");也可以写成Dog dog = new Dog("小狗");
变成Dog类对象后,依然可以使用fun方法,那么,fun方法里是Animal对象,为什么Dog对象可以使用呢?这就是向上转型的原因。只要对象是Animal和它的子类,都可以传入fun方法。
也就是说,不论Animal有多少个子类,fun方法只需要写一次,将参数定义为Animal就可以接受所有它的子类对象。
- 另个我们知道Dog类和Animal类都有完全相同的eat方法,当主方法如下写时:
Animal animal = new Animal("动物"); Animal dog = new Dog("小狗"); animal.eat("食物"); dog.eat("骨头");
dog向上转型成了Animal类的引用,那么它调用的是哪个类的对象呢?
其实,当发生向上转型时,若调用的方法子类和父类中都有,我们要看是通过哪个类new的对象,该对象就调用该类的方法。
向上转型发生场景
- 产生对象时:Animal dog = new Dog();
- 方法参数传递:既上面的例子,产生的是Dog类的对象,但用fun方法接收。在传递Dog对象时发生了向上转型。
- 方法返回值:如下代码,我们new的是Dog类的实例,test方法返回的却是Animal对象,这就是在返回的时候发生了向上转型。
public static Animal test(){ return new Dog("狗狗"); } public static void main(String[] args) { Animal a = test();
综上我们可得:向上转型是为了使参数统一化,父类引用可以接收子类所有对象,同时向上转型是一个非常实用也很重要的概念,读者一定要理解掌握。
方法覆写(override)
定义:发生在有继承关系的类之间,子类定义了和父类完全相同的方法。这种方法就称为方法覆写。
回到我们最开始的例子中:
public static void main(String[] args) {
Animal animal = new Animal("动物");
Dog dog = new Dog("小狗");
animal.eat("食物");
dog.eat("骨头");
}
eat()就称为方法覆写。
在IDEA中,子类中方法覆写的旁边有一个这样的标,表示发生了方法覆写,在父类中是一个向下的标。点击一下就可以找到父类/子类的这个方法。
方法覆写的三个要求:
1.只能重写成员方法,不能重写静态方法
//在父类写一个test方法 public static void test(){ System.out.println("Animal's test"); }//在子类写一个test方法
public static void test(){ System.out.println("Dog's test"); }//在主方法中测试
Animal animal = new Animal("动物"); Animal dog = new Dog("小狗"); animal.test(); dog.test();
输出结果为:
可见没有调用Dog类中的test方法,而且在IDEA中没有出现方法覆写的图标,所有这不是方法覆写。
2.子类中方法的权限修饰符权限要大于或等于父类的 (权限修饰符:private < default < protected < public)
另外,方法覆写时不能出现private权限。当方法是包访问权限(default)时,若父类和子类不在同一个包中,就无法方法覆写。
3.方法覆写的返回值是基本类型时必须相同,否则至少是向上转型的返回值
若父类方法返回值类型是int,而子类是double,编译就会报错 。那么向上转型的返回值是怎样的的呢?
//父类 public Animal func(){ return new Animal("动物"); }//子类
public Dog func(){ return new Dog("狗狗·"); }
这样就是方法返回值是向上转型的使用,func()一个方法覆写。
向下转型
需要使用子类独有的方法时使用向下转型,而且向下转型之前一定要先发生向上转型。由于父类不一定是子类的类型,所有向下转型必须要强制类型转换。
什么叫父类不一定是子类的类型呢?上面我们提到dog ia an animal,但是animal is a dog吗,显然不一定。
Animal animal = new Dog();
Dog dog = (dog) animal ;//向下转型
这里可以发生向下转型的原因是已经发生了向上转型,这个时候就可以使用dog引用去访问Dog类中独有的方法了。
!!要注意,向下转型是有风险的!!
1.如果发生强制转换的两个类之间没有关系,那么一定会出错,例如:
Cat cat = new Cat() ; //猫类
Dog dog = (dog) cat ;//编译错误
上述是强制转换出的错误,两个类之间没有联系,所以编译就会出错
2.现在我们在Dog中写一个方法
public void yell(){ System.out.println("汪汪"); }
yell方法是Dog类独有的,Animal对象要调用就要使用向下转型
Animal animal = new Animal("动物"); Dog dog1 = (Dog) animal; dog1.func();
我们发现编译没有出错,而运行时却出错了,ClassCastException是类型转换异常,大多发生在向下转型时父类引用没有和子类建立联系时。细读代码我们发现animal就是Animal类的对象,没有发生向上转型,所以出错。
instanceof关键字
如何规避向下转型出现错误呢,Java中使用instanceof关键字,它返回一个布尔值,表示一个引用能否指向一个类的实例
if(animal instanceof Dog){ Dog dog1 = (Dog) animal; dog1.yell(); }