Java 为什么要对对象使用超类引用?

Java 为什么要对对象使用超类引用?,java,oop,polymorphism,Java,Oop,Polymorphism,给定以下代码: public class Musician { public void play() { // do something } } 我可以使用多态性执行以下操作: Musician m1 = new Guitarist(); Musician m2 = new Drummer(); m1 = m2; 但是,我看不到子类的方法: m1.strummingStrings(); //COMPILATION ERR

给定以下代码:

public class Musician {

    public void play() {
        // do something
    }
}

我可以使用多态性执行以下操作:

    Musician m1 = new Guitarist();
    Musician m2 = new Drummer();

    m1 = m2;
但是,我看不到子类的方法:

    m1.strummingStrings(); //COMPILATION ERROR!
如果我使用:

Guitarist m1 = new Guitarist();
那不是更好吗?使用Musico类型引用子类的对象有什么好处?仅仅是一种可能性,我将能够属性化
m1=m2?或者还有其他优势吗


我看到了这篇文章,但我仍然感到困惑:

如果您正在编写一个实际需要并使用
音乐家的方法,那么您不会意外地想要依赖子类特性

在您的示例中:

 m1.strummingStrings(); //COMPILATION ERROR!
如果您正在为接受
音乐家的类编写测试驱动程序,那么这是一件好事

当然,在函数的开始和结束之间,编码标准在许多方面并不重要,因为读者和维护人员可以完全理解正在发生的事情。因此,在示例中:

void foo() {
     Guitarist g;
     g.play();
}

很少会有大的不同

但是


会有很大的不同。在前一种情况下,客户机代码永远不允许期望吉他手,而在后一种代码中,实现者永远与返回的吉他手耦合,即使将来发现Banjoist可以更容易地实现和返回,由于客户端代码依赖于它。

多态性的优势在于,当您可以对任何
音乐家
调用
play()
时,无论它是
吉他手
、鼓手
,还是您可能尚未创建的
音乐家
的任何其他子类

Musician m1 = new Guitarist();
Musician m2 = new Drummer();
m1.play();
m2.play();
这可能会输出如下内容

Guitarist strumming
Drummer drumming
如果在两个子类中重写
play()
方法。这样,调用
play()
的代码就不需要知道它实际上是
music
的哪个实现,只需要知道它是
music
并且保证有
play()
方法

多态性的优点不是能够从超类引用调用子类方法,例如
strummingStrings
,因为该方法只存在于子类中。它不能保证存在于超类中。如果需要只调用子类方法,如
strummingString
,则需要子类引用


您可以在超类
music
中定义
strummingStrings
,多态性可以工作,但这是一个糟糕的设计。并非所有的音乐家都能在吉他上弹奏弦乐。

吉他手和鼓手被称为
音乐家的
专业。他们表演特定的把戏,他们有特定的玩法。如果你有一个
波段
,你可能会有这样的波段:

public class Band {
    private List<Musician> musicians = new ArrayList<Musician>();
    public void addMusician(Musician m) {
        musicians.add(m);
    }
    public void play() {
        for (Musician m : musicians) {
            m.play();
        }
    }
}

好问题,但您遇到的问题是您的设计固有的

如果您这样做:

public class Musician {

    public void play() {
        // do something
    }
}
.

public class Drummer extends Musician {

    public void turnsDrumStick() {
        // do something
    }

    public void play() { 
        //...perhaps some other things....
        turnsDrumStick();
    }
}
.

public class Guitarist extends Musician {

    public void strummingStrings() {
       // do something
    }

    public void play() {
       strummingStrings();
       //other things
    }
}

因此,“乐队”类由
音乐家的集合组成。band类通过调用play方法告诉他们何时开始播放。实现细节由子类型决定,继承的功能就是在子类型中实现的

按照您编写类的方式,没有理由将Musitor子类化,因为这两个子类都没有实际使用Musitors play()方法。将m1和m2声明为音乐家而不是它们各自的子类的优点是,实例化类不需要知道play()方法是如何工作的;它可以调用m1.play()和m2.play(),子类知道该做什么

要从这种灵活性中获益,您需要重新构造对象。我建议,至少,将Musitor抽象化,然后将子类中的play()方法重写为strum或drum

如果您想更进一步,可以定义一个Play接口,并为名为StrumGuitar和Drum的类实现该接口,从而封装这些行为。然后,例如,Guitarist的play()方法可以将其行为委托给StrumGuitar类。此外,实例化代码只需调用play(),而不必担心它会做什么


这些技术有助于保持代码的逻辑组织性和灵活性,并允许实例化类在运行时不知道子类的类型,这对代码的灵活性是一个巨大的好处。

另一个优点是,如果您希望将所有音乐家放在一个数据结构(如列表)中,您可以这样做,如果没有超类,则无法关联。不确定我是否会称之为重复。你问题的标题不正确。它应该类似于“为什么要对一个对象使用超类引用”,多态性的例子在像这样的孤立片段中往往显得过于成熟和多余。你需要看到一个更大的、设计良好的代码库才能理解这些微妙之处。@反斜杠,我不理解你的意思,因为如果我有一个List List=new ArrayList(),我可以把吉他手或鼓手放进去,即使我已经创建了这两个类的子类。你能指出一本书或一篇文章,在那里可以看到更多这样的技巧吗?
Guitarist foo() {
// ...
}
Musician m1 = new Guitarist();
Musician m2 = new Drummer();
m1.play();
m2.play();
Guitarist strumming
Drummer drumming
public class Band {
    private List<Musician> musicians = new ArrayList<Musician>();
    public void addMusician(Musician m) {
        musicians.add(m);
    }
    public void play() {
        for (Musician m : musicians) {
            m.play();
        }
    }
}
public class Drummer extends Musician {
    @Override
    public void play() {
        this.turnsDrumStick();
    }
    [...]
}
public class Musician {

    public void play() {
        // do something
    }
}
.

public class Drummer extends Musician {

    public void turnsDrumStick() {
        // do something
    }

    public void play() { 
        //...perhaps some other things....
        turnsDrumStick();
    }
}
.

public class Guitarist extends Musician {

    public void strummingStrings() {
       // do something
    }

    public void play() {
       strummingStrings();
       //other things
    }
}