我是Java初学者。我理解类继承的概念,但有一点我不太明白。我正在阅读Java for Dummies,它正在解释多态性。它以此代码为例:
class Player {
public void move() {...
class BetterPlayer extends Player {
public void move() {...
public class TicTacToeApp {
public static void main(String[] args) {
Player p1 = new Player();
Player p2 = new BetterPlayer();
playTheGame(p1, p2);
}
public static void playTheGame(Player p1, Player p2) {
p1.move();
p2.move();
}
}
为什么将p2创建为Player对象?这是我的理解:
如果p2是BetterPlayer对象(声明如下:BetterPlayer p2 = new BetterPlayer ......):
-upcasting是自动的,所以它仍然适用于playTheGame方法
任何需要BetterPlayer对象的方法都可以使用-p2
但是因为它是作为Player对象创建的,所以现在任何时候p2都被需要BetterPlayer对象的方法使用,它必须显式地转换为BetterPlayer对象,对吧?这似乎更多的工作没有任何好处,所以我猜这样做必须有一些好处;它是什么?
答案 0 :(得分:5)
变量p2未创建为Player对象。它是指向BetterPlayer对象的引用。
无论p2是被声明为Player还是BetterPlayer引用,它都可以用作Player引用。
将其声明为播放器的一个优点是,您无法耦合任何仅适用于BetterPlayer的代码,并保持打开将来使用代码用于另一个子类选项的选项 - 例如,BestPlayer。
答案 1 :(得分:4)
任何玩家在逻辑上都可以玩游戏(这就是让玩家成为玩家的原因);所以playTheGame不应该关心Player是一个BetterPlayer。多态性的优势正是playTheGame不会被迫关心它不应该关心的事情。
我们不会通过将BetterPlayer分配给Player变量(p2)来引起真正的问题,因为如果我们知道我们要调用一堆特别需要BetterPlayer的函数,那么我们只是不会那样做。 “医生,当我告诉类型系统忘记实际上很重要的事情时会很痛。”在目前的情况下,它并不重要,所以我们没有丢弃特定类型信息的问题。
但是,即使我们宣称p2是一个BetterPlayer,我们仍然可以将它传递给playTheGame,而无需编写任何演员阵容。调用函数时的upcast是隐式的,在函数内部我们不需要向下转换,因为我们不关心我们是否有一个BetterPlayer - 因为我们只使用基本的Player接口。然后,在很多时候(包括在当前代码中)我们首先不需要变量,所以更重要的是类型是什么。 playTheGame(new Player(), new BetterPlayer())
工作正常。
答案 2 :(得分:2)
关键是BetterPlayer
可以传递给playTheGame()
,就好像它是Player
一样。当playTheGame()
调用p2.Move()
时,它会调用函数BetterPlayer.move()
,而不是Player.move()
。
这意味着您可以提出Player
的任何子类,它可以在playTheGame()
中正常运行,而无需对playTheGame()
进行任何更改。这意味着您可以使用您喜欢的任何Player
行为创建您喜欢的任何.move()
子类,并且playTheGame()
可以正常使用它。
首先,您使用旧代码调用新代码而不更改旧代码,这很重要。
另一方面,您提供了一种方法,可以将Player
的任何内容插入到某些预先存在的代码中,从而提供抽象屏障。 playTheGame()
Player
不仅不需要了解Player
,也不必坚持使用{{1}}进行调用。
答案 3 :(得分:2)
该书将p2宣布为播放器仅用于说明目的。它可以被声明为BetterPlayer,程序执行完全相同。他们希望表明可以将BetterPlayer分配给Player类型的变量。
答案 4 :(得分:1)
子类与超类具有IS-A关系。 BetterPlayer IS-A播放器。
关于“向上转发”的第一个问题,你的措辞是有缺陷的。你可以将p2传递给方法,因为它需要一个Player和BetterPlayer IS-A Player。
关于你的第二个问题,是的。
对于你的第三个问题,你又是正确的。您可以将p2定义为BetterPlayer,然后您可以在需要播放器的任何地方使用它,以及任何需要BetterPlayer的地方。
答案 5 :(得分:1)
优点是p1.move()
和p2.move()
调用不同的函数,而playTheGame
不必执行类似的操作(伪代码,因为我的Java生锈了):
if (p1 is Player)
call Player.move(p1)
else if (p1 is BetterPlayer)
call BetterPlayer.move(p1)
if (p2 is Player)
call Player.move(p2)
else if (p2 is BetterPlayer)
call BetterPlayer.move(p2)
答案 6 :(得分:1)
让代码更容易维护是件好事 - 如果你需要StupidPlayer
,你只需要改变
Player p2 = new BetterPlayer();
到
Player p2 = new StupidPlayer();
它已经完成了。无需修改其他方法。
答案 7 :(得分:0)
将一种类型的对象声明为另一种类型的对象有什么好处?
在这个例子中,没有任何优势。
多态性的一个更实际的例子是可以包含Players,BetterPlayers和任何其他Player子类的玩家数组(Player[]
)或列表(List<Player>
)。
答案 8 :(得分:0)
一个非常典型的类比是绘制形状的系统。
您可以创建许多不同的形状,并需要将它们放在屏幕上。编写没有多态的代码是很棘手的。如果所有特定的形状(方形,圆形,三角形,粘合茶壶,等等),那么你可以把它们放在一个Shape类型的单个数组中然后你可以说像
foreach Shape s in ArrayOfShapes{
s.draw(); // They all implement draw so it makes our life easy.
}
答案 9 :(得分:0)
好处是除了子类之外,您还可以利用父类的所有函数/属性。这是一种使代码更加灵活的方式,当您了解创建对象系列的更多信息时,您将开始欣赏所提供的额外灵活性。例如,假设我有一个包含5个方法的父类,我可以创建一个子类或子类,并为该类添加更多功能,让您可以访问另外3个更专业的方法。基本思想是子类应该扩展父类的功能,使您能够重用更多代码并使每个类更短,更有凝聚力。如果你想获得对面向对象编程艺术的欣赏,我建议你看看GRASP Patterns wiki。它有足够简单的代码示例,可以帮助您看到类扩展其他类的价值。