深入Java多态性
封装隐藏了类的内部实现机制,从而可以在不影响使用者的前提下改变类的内部结构,同时保护了数据。
继承是为了重用父类代码,同时为实现多态性作准备,多态性则是方法的重写、重载与动态连接构成。
JAVA之所以引入多态的概念:
Java在类的继承问题上和C++不同,C++允许多继承,这确实给其带来的非常强大的功能,但是复杂的继承关系也给C++开发者带来了更大的麻烦,为了规避风险,JAVA只允许单继承,派生类与基类间有IS-A的关系(即“人”IS A “动物”)。这样做虽然保证了继承关系的简单明了,但是势必在功能上有很大的限制,所以,JAVA引入了多态性的概念以弥补这点的不足,此外,抽象类和接口也是解决单继承规定限制的重要手段。同时,多态也是面向对象编程的精髓所在。
要理解多态性,首先要知道什么是“向上转型upsacting
package com.gx.abstractdemo;
/*
* 对象转型
* 一个基类(父类)的引用类型变量可以“指向”其子类的对象
* 一个基类的引用不可以访问其子类对象的特有的成员(属性和方法)
* 可以使用变量instanceof类名来判断该引用型变量所“指向”的对象是否属于该或该类的子类
* 子类的对象可以当做基类的对象来使用称作向上转型
* (父类对象的引用或者叫基类对象的引用指向子类对象,这就是向上转型upsacting),反之为向下转型(downcasting)
*/
class Animal2{
public String name;
public Animal2(String name){
this.name=name;
}
}
class Cat extends Animal2{
public String eyeColor;
public Cat(String n,String c){
super(n);
this.eyeColor=c;
}
}
class Dog extends Animal2{
public String furColor;
public Dog(String n,String c){
super(n);
this.furColor=c;
}
}
public class AbstractAll {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Animal2 a=new Animal2("name");
Cat c=new Cat("catname","blue");
Dog d =new Dog("dogname","black");
System.out.println(String.format("a indtanceof Animal的结果是%s",a instanceof Animal2));
// a instanceof Animal这句话的意思是a是一只动物吗?
// a是Animal这个类里面的是一个实例对象,所以a当然是一只动物,其结果为true。
a=new Dog("bigyellow","yellow");
System.out.println(a.name);
// 这句话比较有意思了,a本身是Animal类的实例对象的引用,
// 但现在这个引用不指向Animal类的实例对象了,而是指向了Dog这个类的一个实例对象了,
// 这里也就是父类对象的引用指向了子类的一个实例对象。
Dog d1= (Dog)a;
System.out.println(d1.furColor); //yellow (子类对象的特有属性)
}
// 这里使用强制转换,把指向Animal类的引用对象a转型成指向Dog类对象的引用,
// 这样转型后的引用对象d1就可以直接访问Dog类对象里面的新增的成员了(特有属性)。
}
为了更好理解多态,简单定义了一个子类CAT,它继承了ANIMAL类,那么后者就是前者是父类,那么有:
CAT C = NEW CAT(); //实例化一个CAT的对象
ANIMAL A = NEW CAT(); //表示定义了一个ANIMAL类型的引用,指向新建的CAT类型的对象。由于CAT是继承自它的父类ANIMAL,所以ANIMAL类型的引用是可以指向CAT类型的对象的。
意义:子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大,属性较父类更独特,定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。同时,父类中的一个方法只有在在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用;对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。
典型的多态程序:
CLASS FATHER{
PUBLIC VOID FUNC1(){
FUNC2();
}
//这是父类中的FUNC2()方法,因为下面的子类中重写了该方法
//所以在父类类型的引用中调用时,这个方法将不再有效
//取而代之的是将调用子类中重写的FUNC2()方法
PUBLIC VOID FUNC2(){
SYSTEM.OUT.PRINTLN("AAA");
}
}
CLASS CHILD EXTENDS FATHER{
//FUNC1(INT I)是对FUNC1()方法的一个重载
//由于在父类中没有定义这个方法,所以它不能被父类类型的引用调用
//所以在下面的MAIN方法中CHILD.FUNC1(68)是不对的
PUBLIC VOID FUNC1(INT I){
SYSTEM.OUT.PRINTLN("BBB");
}
//FUNC2()重写了父类FATHER中的FUNC2()方法
//如果父类类型的引用中调用了FUNC2()方法,那么必然是子类中重写的这个方法
PUBLIC VOID FUNC2(){
SYSTEM.OUT.PRINTLN("CCC");
}
}
PUBLIC CLASS POLYMORPHISMTEST {
PUBLIC STATIC VOID MAIN(STRING[] ARGS) {
FATHER CHILD = NEW CHILD();
CHILD.FUNC1();//CCC
}
}
子类CHILD继承了父类FATHER,并重载了父类的FUNC1()方法,重写了父类的FUNC2()方法。重载后的FUNC1(INT I)和FUNC1()不再是同一个方法,由于父类中没有FUNC1(INT I),那么,父类类型的引用CHILD就不能调用FUNC1(INT I)方法。而子类重写了FUNC2()方法,那么父类类型的引用CHILD在调用该方法时将会调用子类中重写的FUNC2()。
总结:
一、使用父类类型的引用指向子类的对象;
二、该引用只能调用父类中定义的方法和变量;
三、如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;(动态连接、动态调用)
四、变量不能被重写(覆盖),”重写“的概念只针对方法,如果在子类中”重写“了父类中的变量,那么在编译时会报错。