认识一下Java中方法重载和重写的“真面目”

您所在的位置:网站首页 java五子棋程序设计遇到的困难及其解决办法 认识一下Java中方法重载和重写的“真面目”

认识一下Java中方法重载和重写的“真面目”

2023-05-29 17:32| 来源: 网络整理| 查看: 265

前言

考大家一道题目,下面的类执行结果是什么???

public class DispatcherClient { public static void main(String[] args) { Animal a = new Animal(); Animal a1 = new Dog(); Animal a2 = new Cat(); Execute exe = new Execute(); exe.execute(a); exe.execute(a1); exe.execute(a2); } } class Animal { } class Dog extends Animal { } class Cat extends Animal { } class Execute { public void execute(Animal a) { System.out.println("Animal"); } public void execute(Dog d) { System.out.println("dog"); } public void execute(Cat c) { System.out.println("cat"); } }

不知道大家心里的答案是什么?反正我的答案是错的。

正确的答案是:

图片

为什么是Animal Animal Animal? 而不是Animal dog cat。

类重载本质——静态分派

execute方法是一个重载方法,本质上就是虚拟机JVM如何确定调用哪个方法执行。在java编译后的class文件中存储的只是方法的符号引用,而不是方法在实际运行过程中内存布局的入口地址(直接引用)。而这个方法从符号引用变成直接引用有两种方式,解析和分派。

解析是发生在类加载的解析阶段就会将一部分方法的符号引用转换为直接引用,比如类的静态方法、私有方法、构造方法、父类方法以及final的方法。我们这里不展开阐述,和本例无关。

而我们方法重载的情况下,java采用的是静态分派的方式确定调用方法。

变量类型

在了解静态分派前我们需要了解下变量的类型。

Animal a1 = new Dog();静态类型, 也叫做"外观类型", 比如代码中的"Animal", 它的类型是在编译期就知道。实际类型,也叫"运行时类型", 比如代码中的"Dog", 它是在类运行时才会确定,编译期是不知道的。Execute exe = new Execute(); exe.execute(a); exe.execute(a1); exe.execute(a2);

这里多次调用了execute方法,在方法接收者已经确定是对象exe的前提下,使用哪个重载的方法,就完全取决于传入参数的数量和数据类型。虚拟机在重载时是通过参数的静态类型而不是实际类型作为判断依据的。因为静态类型是编译期可知的,所以,在编译阶段,编译器会根据静态类型决定使用哪个重载版本,如下图例子中的字节码,技术在编译的字节码中确定了它调用的重载方法。

图片

类多态本质——动态分派

既然有静态分派,那么是不是有动态分派呢?什么又是动态派呢?

Java语言的一大特性是多态性,所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

举个简单的例子,比如Human human = flag ? new Man() : new Woman(), human的具体类型是man还是woman在编写代码的时候我们是无法确定,它是由flag这个标记决定,只有在程序运行的时候才能够确定下来,这种让引用变量在运行时绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

多态在Java中有两种实现形式,分别是继承和接口,子类重写父类或者接口中的方法,现在举个例子。

public class DynamicDispatch { static abstract class Animal { protected abstract void eat(); } static class Cat extends Animal { @Override protected void eat() { System.out.println("我吃鱼"); } } static class Dog extends Animal { @Override protected void eat() { System.out.println("我吃骨头"); } } public static void main(String[] args) { Animal cat = new Cat(); Animal dog = new Dog(); cat.eat(); dog.eat(); cat = new Dog(); cat.eat(); } }

运行结果:

图片

这个结果相信和大家想的是一致的,那大家有想过JVM是怎么找到具体的类型执行的呢?我们定义的引用类型就是Animal,JVM是根据什么来找到对应的Cat 或者Dog这些具体的实例执行对应的方法呢?

从字节码角度分析

利用idea的Jclasslib插件查看字节码:

图片

0~15行主要是创建Cat对象和Dog对象的字节码指令。17和21行一模一样,指令都是invokevirtual, 参数都是


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3