前几天面试京东的时候,被问到c++与java函数的区别。其中有一条是,java中有没有虚函数?本文将从虚函数的定义及java字节码层面上告诉你答案。
一、虚函数的定义
定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。----------(百度百科)
举个c++中虚函数的例子:
#include<iostream>运行可以得到结果:
using namespace std;
class A
{
public:
void FUN()
{
cout<<"FUN in A is called"<<endl;
}
};
class B : public A
{
public:
void FUN()
{
cout<<"FUN in B is called"<<endl;
}
};
int main()
{
A a;
B b;
a.FUN();
b.FUN();
return 0;
}
FUN in A is called
FUN in B is called
代码小改一下:
#include<iostream>
using namespace std;
class A
{
public:
void virtual FUN()
{
cout<<"FUN in A is called"<<endl;
}
};
class B : public A
{
public:
void FUN()
{
cout<<"FUN in B is called"<<endl;
}
};
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
上面字节码中,有许多指令,如aload_2、invokespecial、invokevirtual(注:aload指令是用来将数据加载到栈,invokespecial用来调用类的构造函数,invokevirtual用来调用普通函数)。
可以发现,对于A.FUN的调用,java虚拟机所采用的调用方式就是invokevirtual。这些字节码的调用指令,是由《java虚拟机规范》规定的,在制定规范之初,大牛们就直接用invokevirtual来命名调用普通函数的指令。从字节码指令的命名上也可以看出,java中的普通成员函数就是虚函数。
从网上有看到这样一种说法,大家可以作为一个参考。
C++ Java
虚函数 -------- 普通函数纯虚函数 -------- 抽象函数
抽象类 -------- 抽象类
虚基类 -------- 接口
结论:java类中普通成员函数就是虚函数。