前几天面试京东的时候,被问到c++与java函数的区别。其中有一条是,java中有没有虚函数?本文将从虚函数的定义及java字节码层面上告诉你答案。

一、虚函数的定义

定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。----------(百度百科)

举个c++中虚函数的例子:

#include
using namespace std;
class A
{
public:
void FUN()
{
cout<
}
};
class B : public A
{
public:
void FUN()
{
cout<
}
};
int main()
{
A a;
B b;
a.FUN();
b.FUN();
return 0;
}

运行可以得到结果:

FUN in A is called

FUN in B is called

代码小改一下:

#include
using namespace std;
class A
{
public:
void virtual FUN()
{
cout<
}
};
class B : public A
{
public:
void FUN()
{
cout<
}
};
int main()
{
A a;
B b;
A *p;
p = &a;
p->FUN();
p = &b;
b.FUN();
return 0;
}

同样可以得到运行结果:

FUN in A is called

FUN in B is called

读者可以试试将第6行的关键字virtual去掉试运行下,会发现,运行结果如下:

FUN in A is called

FUN in A is called

通过这个例子,可以看到虚函数的作用------实现多态。指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。

再来看看java语言,

package test;
/**
* Created by ping on 2015/10/12.
*/
class A{
public void FUN(){
System.out.println("FUN in A is called");
}
}
class B extends A{
public void FUN(){
System.out.println("FUN in B is called");
}
}
public class VirtualTest {
public static void main(String args[]) {
A a = new A();
B b = new B();
A p;
p = a;
p.FUN();
p = b;
p.FUN();
}
}

在上面的代码中,我们同样定义了一个基类指针(在java中应该叫引用)去指向不同的对象,可以发现同样可以实现多态。

也就是说,java的普通成员函数(没有被static、native等关键字修饰)就是虚函数,原因很简单,它本身就实现虚函数实现的功能------多态。

下面,我们从更底层的方面来讲述java中虚函数。

二、java字节码

还是之前的代码,我们用javac编译之后,编译器会生成相应的class字节码文件。通过java自带字节码查看器查看

javap -verbose VirtualTest

java virtual 虚方法 java虚构函数_虚函数

上面字节码中,有许多指令,如aload_2、invokespecial、invokevirtual(注:aload指令是用来将数据加载到栈,invokespecial用来调用类的构造函数,invokevirtual用来调用普通函数)。

可以发现,对于A.FUN的调用,java虚拟机所采用的调用方式就是invokevirtual。这些字节码的调用指令,是由《java虚拟机规范》规定的,在制定规范之初,大牛们就直接用invokevirtual来命名调用普通函数的指令。从字节码指令的命名上也可以看出,java中的普通成员函数就是虚函数。

从网上有看到这样一种说法,大家可以作为一个参考。

C++                    Java 虚函数     --------   普通函数

纯虚函数   --------   抽象函数

抽象类     --------   抽象类

虚基类     --------   接口

结论:java类中普通成员函数就是虚函数。