维基百科将virtual methods定义为:
在面向对象的编程中,虚函数或虚方法是一种函数或方法,其行为可以通过具有相同签名的函数在继承类中重写[以提供多态行为]。
根据定义,Java中的每个非静态方法默认为虚拟,除了 final和private方法。对于多态行为不能继承的方法是不虚拟方法。
Java中的静态方法永远不能被覆盖;因此,在Java中将静态方法声明为final是没有意义的,因为静态方法本身就像最终方法一样。它们可以通过具有相同签名的方法简单地在子类中隐藏。显然是这样,因为静态方法永远不会有多态行为:被覆盖的方法必须实现多态,而静态方法则不然。
从前一段开始,可以推动一个重要的结论。默认情况下,C ++中的所有方法都是静态的,因为除非在超类中显式声明为虚拟,否则C ++中的任何方法都不能以多态方式运行。相比之下,除了final,static和private方法之外,Java中的所有方法都是虚拟的,因为它们默认具有多态行为(不需要在Java中将方法显式声明为虚拟,因此,Java没有像“virtual”这样的关键字)。
现在,让我们演示实例变量(静态)也不能通过Java中的以下简单示例进行多态化。
class Super
{
public int a=5;
public int show()
{
System.out.print("Super method called a = ");
return a;
}
}
final class Child extends Super
{
public int a=6;
@Override
public int show()
{
System.out.print("Child method called a = ");
return a;
}
}
final public class Main
{
public static void main(String...args)
{
Super s = new Child();
Child c = new Child();
System.out.println("s.a = "+s.a);
System.out.println("c.a = "+c.a);
System.out.println(s.show());
System.out.println(c.show());
}
}
上述代码段产生的输出如下:
s.a = 5 c.a = 6 Child method called a = 6 Child method called a = 6
在此示例中,调用s.show()
和c.show()
分别通过show()
类型和Super
类型的变量对Child
方法进行调用,调用show()
类中的Child
方法。这意味着show()
类中的Child
方法会覆盖show()
类中的Super
方法,因为它们都具有相同的签名。
但是,这不能应用于两个类中声明的实例变量a
。在这种情况下,s.a
会引用a
类中的Super
并显示5
而c.a
会引用a
Child
1}} class和display 6
表示a
类中的Child
只隐藏(并且不会覆盖非静态方法)a
{ {1}}上课。
经过长时间的讨论,只有一个问题。为什么实例变量(以及其余变量)没有被覆盖?实施这种机制的特殊原因是什么?如果它们被覆盖了会有任何优点或缺点吗?
答案 0 :(得分:3)
我认为覆盖的目的是在不改变签名的情况下改变功能。这仅适用于方法:方法可能具有相同的签名但具有不同的行为。字段只有“签名”,也仅限于类型和名称。他们没有行为,所以没有任何东西可以被覆盖。
无法“覆盖”字段的其他原因是字段通常是私有的(或应该是私有的),因此它们实际上是类实现的血腥细节。然而,方法表示类的外部接口。此外部接口应支持多态,以便更改模块的功能,而无需更改模块之间的关系。
答案 1 :(得分:2)
当引用名称解析时,术语“静态”还有另外一种用法:静态解析是指使用声明的变量类型来解析名称时的动态,当使用变量中存储的数据类型时动态。静态分辨率可以在编译时完成,而动态分辨率必须在运行时完成。解析虚拟成员(这是一种动态解析的形式)需要额外的间接级别,即表查找。允许“虚拟”字段会产生运行时成本。
一旦完成所有成员的静态分辨率,就没有必要使用静态类型变量。此时,您可以使用动态类型语言,例如Python和众多Lisp变体。当您丢弃静态类型时,尝试说出不覆盖(或覆盖)父字段的子字段就没有意义了,因为不再有静态变量类型来暗示其他情况。
class Super(object):
a=5
def show(self):
print("Super method called a = ", end='')
return self.a
class Child(Super):
a=6
def show(self):
print("Child method called a = ", end='')
return self.a
# there's no indication that 's' is supposed to be a Super...
s = Child();
c = Child();
# so it's no surprise that s.a is Child.a
print("s.a =", s.a)
# result: "s.a = 6"
print("c.a =", c.a)
# result: "c.a = 6"
print(s.show())
# result: "Child method called a = 6"
print(c.show())
# result: "Child method called a = 6"
应该注意,常见的lisps具有类型声明和动态分辨率。
(defgeneric show (o))
(defclass super ()
((a :accessor super-a
:initform 5
:initarg :a))
)
(defmethod show ((o super))
(list "Super method called a = "
(slot-value o 'a))
)
(defclass child (super)
((a :accessor child-a
:initform 6
:initarg :a))
)
(defmethod show ((o child))
(list "Child method called a = "
(slot-value o 'a))
)
(defun test (s c)
(declare (type super s))
(declare (type child c))
(list (list "s.a =" (slot-value s 'a))
(list "c.a =" (slot-value c 'a))
(show s)
(show c)
)
)
(test (make-instance 'child) (make-instance 'child))
;; result: '(("s.a =" 6) ("c.a =" 6) ("Child method called a = " 6) ("Child method called a = " 6))
在纯OOP中(根据某些内容),对象不应该有公共字段,只有公共成员,因此静态解析字段不是问题。
C ++中的所有方法(也在C语言中)默认为static [...]
不,因为static
在Java中意味着“不能被覆盖”,并不意味着在C ++中完全没有意义。在两者中,静态方法的基本属性是它们是类方法,通过类而不是实例访问(尽管Java允许它们被访问)。
答案 2 :(得分:2)
看看C#世界。严格禁止使用公共实例变量 - 请参阅StyleCop。唯一推荐的方法是使用可以覆盖的属性。
我认为这是正确的做法。只是不要使用公共实例变量并使用类比属性。
关于为什么在Java中这样,我认为某种类似C ++的风格在Java被设计为一种简化过渡的方式时被采用。
答案 3 :(得分:1)
在实例字段中几乎没有覆盖。您只需要用相同的东西替换它:相同的名称,相同的类型。您可以使用不同长度的数组替换数组,但是您需要仔细查看所有超类,以确保它们不会对长度进行假设。
正如其他人所指出的那样,非私人领域通常需要避免。有时一个类更像是一个C结构,有公共字段而没有方法。这是非常好的做法,但是压倒任何东西仍然没有意义。
覆盖实际上只是关于方法,而不考虑语言。 Java没有像C#这样的属性,它们本身就是(有限的)类,需要更改方法。字段可以看起来和行为类似属性,但它们根本不是一回事。