目录
引例 - -认识多态??
向上转型
? ??向上转型发生场景
? ??方法覆写(override)
? ??方法覆写的三个要求:
向下转型
向下转型的风险!
instanceof关键字
引例
面向对象编程有三大特征:封装、继承、多态。现在我们对多态展开详细的了解。
- ?在现实中,如老师,一位具体的老师他既是老师,也是人,既表现出了多种形态
- ?在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();
}
?
?
|