Java三大特性:封装、继承、多态,相信大家都有所了解,今天我们主要从JVM的角度来学习一下多态。
进入正题之前,先带着大家来快速的温故一下什么是封装、继承、多态。
1,封装:把项目中用的比较多的、可复用的功能封装成一个Java类,或者封装成一个方法,然后只要用到该功能,直接调用即可;
2,继承:通过extends关键字,继承父类,以便重用父类的方法和属性;
3,多态:通俗点就是,同一种行为(Java方法),在不同的对象上(Java类的实例),具有不同的表现形式;
OK,言归正传。首先新建3个类,Fish、Jiyu、Huotou,分别代表鱼类、鲫鱼、火头。鲫鱼和火头都继承自鱼类。
public class Fish {
// 鱼类eat方法
public void eat(){
System.out.println("我是鱼类...");
}
}
public class Jiyu extends Fish {
//鲫鱼eat方法
public void eat(){
System.out.println("我是鲫鱼,我吃水草...");
}
}
public class Huotou extends Fish {
// 火头eat方法
public void eat(){
System.out.println("我是火头,我吃小鱼虾...");
}
}
Java语言实现多态有三个必要条件:继承(extends)、重写(override)、向上转型。当然,实现多态,也可以通过
接口的方式。
说到多态,我们需要先明白一个概念,就是向上转型。这个相信大家都清楚,为了帮助大家进入状态,先提几个问题,
大家试着思考一下,文章的最后,会附上向上转型的讲解,不太清楚的童鞋可以看一下:
1,向上转型有什么好处?
2,向上转型能访问子类特有的方法吗?
接下来写一个主方法,来进行测试。
/**
*
* @author yangcq
* @version 20160721
* @category Java三大特性之:多态
*
*/
public class DuotaiLearning {
//无参数构造方法
public DuotaiLearning(){
}
public static void main(String[] args) {
/**
* Java 方法解析
*
* Class文件的编译过程中不包含传统编译中的连接步骤,Java中方法调用在编译以后的Class文件中存储的
* 都是方法的符号引用,而不是方法在实际运行时内存布局中的入口地址。这个特性给Java带来了更强大的
* 动态扩展能力,使得在类运行期间才能确定某些目标方法的直接引用,我们把这个特性称之为动态连接。
* 还有一部分方法的引用在类加载阶段或第一次使用时转化为直接引用,我们把这个特性称之为静态解析。
*
* 静态解析成立的前提是:方法在程序真正执行前就有一个可确定的版本,并且这个方法的调用版本在运行期
* 是不可改变的。
*
* 在Java中,符合“编译器可知,运行期不可变”的方法主要有2种:静态方法和私有方法。前者用static关键字
* 修饰,后者在外部不可被访问,这2种方法都不可能通过继承或者别的方式重写出其他版本,因此他们在类
* 加载阶段进行解析。
*
* Java虚拟机提供了4条方法调用字节指令:
* 1,invokestatic
* 2,invokespecial
* 3,invokevirtual
* 4,invokeinterface
*
*
*/
DuotaiLearning duotaiLearning = new DuotaiLearning();
Fish jiyu_1 = new Jiyu();
Fish huotou_1 = new Huotou();
Fish fish_1 = new Fish();
duotaiLearning.eat(fish_1); // 打印:我是鱼类...
duotaiLearning.eat(jiyu_1); // 打印:我是鱼类...
duotaiLearning.eat(huotou_1); // 打印:我是鱼类...
/**
* 案例分析:
* Fish jiyu_1 = new Jiyu(); 我们把Fish称为变量的静态类型,把Jiyu称为变量的实际类型。
* 静态类型和实际类型,在程序中都可能变化,区别是静态类型的最终版本在编译期间是可知的,
* 而实际类型的变化,只有到了程序运行期间(Runtime)才可知。
*
* 上面的代码中,在调用eat方法时,方法的调用者都为duotaiLearning的情况下,使用哪个重载版本,
* 取决于传入参数的数量和数据类型。编译器(不是虚拟机)在重载时,通过参数的静态类型来决定
* 重载哪个版本,因为这三个eat方法传入的参数的静态类型都是Fish,所以最终都是调用了
* eat(Fish fish)这个方法,打印结果都是 :我是鱼类...
*
*/
Jiyu jiyu_2 = new Jiyu();
Huotou huotou_2 = new Huotou();
Fish fish_2 = new Fish();
duotaiLearning.eat(fish_2); // 打印:我是鱼类...
duotaiLearning.eat(jiyu_2); // 打印:我是鲫鱼,我吃水草...
duotaiLearning.eat(huotou_2); // 打印:我是火头,我吃小鱼虾...
/**
* 案例分析:
* 通过上面的分析,上面这三个方法传入的参数的静态类型分别是:Jiyu、Huotou、Fish
* 所以最终分别调用了三个不同的方法。
*
* 这就是Java中的静态分派。
* 因为静态类型是在编译期间可知的,所以静态类型重载哪个版本,在编译期间就已经确定了。
*
*/
Jiyu jiyu_3 = new Jiyu();
Huotou huotou_3 = new Huotou();
Fish fish_3 = new Fish();
fish_3.eat(fish_3); // 打印:我是鱼类...
jiyu_3.eat(jiyu_3); // 打印:我是鲫鱼,我吃水草...
huotou_3.eat(huotou_3); // 打印:我是火头,我吃小鱼虾...
/**
* 与静态分派相对应的,就是Java的动态分派,向上转型后调用子类覆写的方法,就是一个典型
* 的场景。
*/
Jiyu jiyu_4 = new Jiyu();
Huotou huotou_4 = new Huotou();
Fish fish_4 = new Fish();
jiyu_4.eat(fish_4); // 打印:我是鱼类...
jiyu_4.eat(jiyu_4); // 打印:我是鲫鱼,我吃水草...
jiyu_4.eat(huotou_4); // 打印:我是鱼类...
Fish jiyu_5 = new Jiyu();
Fish huotou_5 = new Huotou();
Fish fish_5 = new Fish();
fish_5.eat(fish_5); // 打印:我是鱼类...
jiyu_5.eat(jiyu_5); // 打印:我是鱼类...
huotou_5.eat(huotou_5); // 打印:我是鱼类...
}
public void eat(Fish fish){
System.out.println("我是鱼类...");
}
public void eat(Jiyu jiyu){
System.out.println("我是鲫鱼,我吃水草...");
}
public void eat(Huotou huotou){
System.out.println("我是火头,我吃小鱼虾...");
}
}
扩展阅读:向上转型
实例中,定义了三个类:
XSZX_Fish
XSZX_Jiyu extends XSZX_Fish
XSZX_Huotou extends XSZX_Fish
/**
*
* @author yangcq
* @category 这里为了说明向上转型,定义2个方法
* @method:eat 这个方法被子类重写了
* @method:run 这个方法没有被子类重写
*
*/
public class XSZX_Fish {
// 鱼类eat方法
public void eat(){
System.out.println("我是鱼类...");
}
public void run(){
System.out.println("我是鱼类,我用鱼鳍游泳...");
}
}
/**
*
* @author yangcq
* @category 这里为了说明向上转型的优缺点,在子类XSZX_Jiyu中定义2个方法
* @method:eat 吃饭方法
* @method:sleep1 睡觉方法(public)
* @method:sleep2 睡觉方法(private)
*
*/
public class XSZX_Jiyu extends XSZX_Fish {
// 鲫鱼eat方法
public void eat(){
System.out.println("我是鲫鱼,我吃水草...");
}
// 鲫鱼睡觉方法(子类独有的方法,父类没有定义这个方法)
public void sleep1(){
System.out.println("我是鲫鱼,我睡觉时睁着眼睛...");
}
// 鲫鱼睡觉方法(子类独有的方法,父类没有定义这个方法)
private void sleep2(){
System.out.println("我是鲫鱼,我睡觉时睁着眼睛...");
}
}
/**
*
* @author yangcq
* @category 这里为了说明向上转型的优缺点,在子类XSZX_Huotou中定义2个方法
* @method:eat 吃饭方法
* @method:sleep1 睡觉方法(public)
* @method:sleep2 睡觉方法(private)
*
*/
public class XSZX_Huotou extends XSZX_Fish {
// 火头eat方法
public void eat(){
System.out.println("我是火头,我吃小鱼虾...");
}
// 火头睡觉方法(子类独有的方法,父类没有定义这个方法)
public void sleep1(){
System.out.println("我是火头,我睡觉时睁着眼睛...");
}
// 火头睡觉方法(子类独有的方法,父类没有定义这个方法)
private void sleep2(){
System.out.println("我是火头,我睡觉时睁着眼睛...");
}
}
接下来是测试类:XSZX_MainMethodTest.java
/**
*
* @author yangcq
* @category 向上转型
*
*/
public class XSZX_MainMethodTest {
public static void main(String[] args) {
/**
* 向上转型的好处:可以调用父类中的方法,也可以调用子类中的方法,
* 但是,如果子类中和父类中都有的方法,向上转型只能调用子类中的方法。
* XSZX_Fish jiyu_1 = new XSZX_Jiyu(); // 向上转型
* XSZX_Jiyu jiyu_2 = new XSZX_Jiyu(); // 普通的XSZX_Jiyu对象
*
* 非向上转型的jiyu,可以调用父类中的所有非私有方法和子类中的所有方法。
*
*/
XSZX_Fish jiyu_1 = new XSZX_Jiyu(); //向上转型,定义了一个指向子类XSZX_Jiyu 的 父类XSZX_Fish对象
jiyu_1.eat(); // 输出:我是鲫鱼,我吃水草...
jiyu_1.run(); // 输出:我是鱼类,我用鱼鳍游泳...
XSZX_Jiyu jiyu_2 = new XSZX_Jiyu(); // 非向上转型
jiyu_2.eat();
jiyu_2.sleep1();
jiyu_2.run();
/**
* 通过和非向上转型的jiyu_2对比可以发现,向上转型的缺点也很明显,那就是不能调用子类中独有的方法。
*/
}
}