你能在Java中编写虚拟函数/方法吗?

193

在Java中是否可能编写虚拟方法,就像在C++中一样?

或者,是否有适当的Java方法可以实现类似的行为?请提供一些示例。

6个回答

345

来自维基百科

Java中,所有非静态方法默认都是"虚函数"。 只有被标记为final关键字(无法重写)和私有方法(不会被继承)的方法是非虚函数


6
这里是 Jon Skeet 的一个答案,链接为 https://dev59.com/kXVC5IYBdhLWcg3wcgmd#295248。 - Quazi Irfan
我想知道这是否真的是正确的,因为根据我所读的,在Java中,动态方法分派仅适用于调用该方法的对象 - 如此解释在这里,因此C++中解释虚函数的示例在这里对于Java来说是无效的。 - Broccoli
@QuaziIrfan 这就是Java和C#之间的区别。 - Sreekanth Karumanaghat
Java中的final方法仍然可以是虚拟的。例如,我们有一个名为Base的类,其中包含some方法,以及扩展Base并具有其自己的实现some方法的Derived类,该方法由final保留字标记。 some方法仍然是虚拟的。 - Andrey

110

Java中能否编写虚函数?

能。实际上,Java中的所有实例方法默认都是虚拟的。只有一些方法不是虚拟的:

  • 类方法(因为通常每个实例都持有与其特定方法有关的vtable指针信息,但此处没有可用的实例)。
  • 私有实例方法(因为没有其他类可以访问该方法,调用实例始终具有定义类本身的类型,因此在编译时可以明确知道)。

以下是一些示例:

"普通"虚函数

以下示例来自另一个答案中提到的维基百科页面的旧版本

import java.util.*;

public class Animal 
{
   public void eat() 
   { 
      System.out.println("I eat like a generic Animal."); 
   }

   public static void main(String[] args) 
   {
      List<Animal> animals = new LinkedList<Animal>();

      animals.add(new Animal());
      animals.add(new Fish());
      animals.add(new Goldfish());
      animals.add(new OtherAnimal());

      for (Animal currentAnimal : animals) 
      {
         currentAnimal.eat();
      }
   }
}

class Fish extends Animal 
{
   @Override
   public void eat() 
   { 
      System.out.println("I eat like a fish!"); 
   }
}

class Goldfish extends Fish 
{
   @Override
   public void eat() 
   { 
      System.out.println("I eat like a goldfish!"); 
   }
}

class OtherAnimal extends Animal {}

输出:

我像普通动物一样进食。
我像鱼一样进食!
我像金鱼一样进食!
我像普通动物一样进食。

使用接口和虚函数的示例

Java 接口方法 都是虚拟的。它们必须是虚拟的,因为它们依赖于实现类提供方法实现。要执行的代码仅在运行时选择。

例如:

interface Bicycle {         //the function applyBrakes() is virtual because
    void applyBrakes();     //functions in interfaces are designed to be 
}                           //overridden.

class ACMEBicycle implements Bicycle {
    public void applyBrakes(){               //Here we implement applyBrakes()
       System.out.println("Brakes applied"); //function
    }
}

使用抽象类实现虚函数的示例。

与接口类似,抽象类 必须 包含虚函数,因为它们依赖于扩展类的实现。例如:

abstract class Dog {                   
    final void bark() {               //bark() is not virtual because it is 
        System.out.println("woof");   //final and if you tried to override it
    }                                 //you would get a compile time error.

    abstract void jump();             //jump() is a "pure" virtual function 
}                                     
class MyDog extends Dog{
    void jump(){
        System.out.println("boing");    //here jump() is being overridden
    }                                  
}
public class Runner {
    public static void main(String[] args) {
        Dog dog = new MyDog();       // Create a MyDog and assign to plain Dog variable
        dog.jump();                  // calling the virtual function.
                                     // MyDog.jump() will be executed 
                                     // although the variable is just a plain Dog.
    }
}

2
这一定是最完整的答案。它提供了两种实现虚函数的方法,因为Java没有关键字。谢谢。 - Christopher Bales
@David 这个答案怎么更好呢?维基百科的引用完整、简洁且正确。相比之下,这个答案未提到一个显而易见的问题:默认情况下,Java 中的 所有 函数(除了维基百科文章中列出的例外)都是虚函数。抽象类和接口都不是虚函数所必需的,因此只会增加误导性的噪音。然后这个“需要极高的沟通技巧和对底层原理的深刻掌握”……天啊。这就是一个自我证伪的陈述:拥有这种技能的人不会浪费宝贵的磁盘空间。 - Peter - Reinstate Monica
  1. 这篇文章中的维基百科引用以“在Java中”开头。没有比这更具体的了。(该维基百科文章已经更改,不再包含该句话。)顺便说一下,我相当确定示例没有包括C。(但是有C++。)
  2. 这些示例具有误导性,因为它们与Java默认具有虚函数无关。
  3. 我的意思是:没有一个具有良好沟通技巧和深刻掌握基本原理的人会写那样的文章。我并不是指整篇文章。
- Peter - Reinstate Monica
  1. 我批评了一下那种自命不凡的风格,这与帖子质量的低劣形成了鲜明对比。问题在于,该帖子最多只为Java虚拟函数添加了两个示例应用程序/特定结构。如果我们假设读者具备基本的编程和Java知识,那么这些具体示例以及第二段中的许多一般性解释都是不必要的,这也是我们提出问题时可以考虑到的。
- Peter - Reinstate Monica
1
几年晚了,但是非常棒的答案。 - Tom O.
显示剩余10条评论

59

Java中所有的函数默认都是虚函数。

如果要写非虚函数,必须通过添加"final"关键字来实现。

这与C++/C#的默认相反。在C++/C#中,类函数默认为非虚函数;您需要添加"virtual"修饰符才能使它们成为虚函数。


5
如 Klaus 在回答中所述,私有函数也是非虚函数。 - Don Larynx

9

在Java中,所有非私有实例方法默认为虚方法。

C++中,私有方法可以是虚的。这可以被用于非虚拟接口(NVI)惯用语。在Java中,您需要将NVI可重写方法设置为受保护的。

根据Java语言规范v3:

8.4.8.1 重写(通过实例方法)如果类C中声明的实例方法m1覆盖了类A中声明的另一个实例方法m2,则当且仅当以下条件均为 true 时,m1才会覆盖m2:

  1. C是A的子类。
  2. m1的签名是m2签名的子签名(§8.4.2)。
  3. 要么 * m2是公共的、受保护的或在与C相同的包中具有默认访问权限,要么 * m1覆盖一个方法m3,m3与m1不同,m3与m2不同,使得m3覆盖了m2。

4
是的,你可以在Java中编写虚拟“函数”。

1
在Java中,所有公共(非私有)变量和函数默认情况下都是虚拟的。此外,使用关键字final的变量和函数不是虚拟的

什么是“虚拟变量”? - neoexpert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接