为什么我可以通过引用子类来访问所有方法,为什么还需要引用基类?

3
我正在学习Java的概念,但是在继承这个概念上我有些疑惑。在继承中,我们可以将子类实例分配给基类引用,并且只能访问基类函数。我们可以将继承层次结构中的任何子类实例分配给基类引用。对于分配给特定基类引用的某种类型的实例,我们只能访问基类函数,我没有发现任何区别。
有人能够给我解释一下为什么我们要将子类实例分配给基类引用吗?这样做的必要性是什么?相反,我们不是可以从子类引用中访问那些基类函数吗?
请考虑一个特定的基类和许多继承子类来进行解释。
6个回答

5
你可能想这么做的原因是为了创建更加健壮的设计。例如,Java中的Collections Framework。你有一个List接口,然后你有两个实现,ArrayList和LinkedList。
你可以编写程序专门使用LinkedList或ArrayList。然而,你的程序就依赖于这些特定的实现。
如果你编写程序依赖于超类型List,那么你的程序就可以适用于任何List实现。比如说,你想编写一个对List执行某些操作的方法,你可以这样写:
  public void doSomething(ArrayList a){}

这个方法只能用于ArrayList,而不能用于LinkedList。假设你想要用LinkedList来做同样的事情?难道你就要复制你的代码吗?不需要。

  public void doSomething(List l){}

将能够接受任何类型的列表。

背后的原则是针对接口而非实现进行编程。也就是说,List 定义了所有列表的函数。

有很多这种用法的例子。


3
继承和多态是面向对象编程的基石,有几个不同的目的,简而言之:
  • 通过将特定功能与基类扩展来实现代码重用
  • 通过提供一组抽象的功能来进行接口设计,其中不同的实现适用于不同的要求
  • 通过隐藏某些上下文中不需要的特定功能来实现封装

等等。

最后一点也强调了为什么即使实际实现提供更多功能,也可能使用受限制的功能集。以Collection接口为例。通过使用这个接口,我们关注像isEmptycontainssize这样的方法,而不是实际的实现。


2

你所描述的是多态的本质。这个词源于希腊语,意思是“多种形式”。

如果我给你一个简单的层次结构,就像这样,你可以看到测试代码如何从每个对象中获取不同的计算实现,而不用关心它正在处理什么类型的形状:

public interface Shape
{
    double calculateArea();
}

class Circle implements Shape
{
    private double radius;

    Circle(double r) { this.radius = r; }

    public double calculateArea() { return Math.PI*radius*radius; }
}

class Square implements Shape
{
    private double side;

    Square(double s) { this.side = s; }

    public double calculateArea() { return side*side; }
}

// This would be a separate JUnit or TestNG annotated test.
public class ShapeTest
{
    @Test
    public void testCalculateArea()
    {
        Map<Shape, Double> expected = new HashMap<Shape, Double>()
        {{
            put(new Circle(1.0), Math.PI);
            put(new Square(1.0), 1.0);            
        }};


        for (Shape shape : expected.keySet())
        {
            Assert.assertEquals(expected.get(shape), shape.calculateArea());
        }       
    }
}

1
假设Vehicle是基类,CarPlane是子类。假设Vehicle有一个方法move()。
Car通过在道路上行驶来覆盖它。Plane通过飞行来覆盖它。 为什么move()应该是Vehicle基类的一部分? 因为任何车辆都可以移动。但我们不能在Vehicle中实现move(),因为所有车辆的移动方式不同,即没有共同的行为。我们仍然希望将其放在基类中,以便我们可以拥有多态行为,即我们可以编写像下面这样的代码。正如您所看到的,只有一个名为runVehicle(...)的方法可以在任何Vehicle类上工作。
void runVehicle(Vehicle v)
{
  v.move();
}

Car c=new Car();
runVehicle(c);

Plane p=new Plane();
runPlane(p);

1

多态性

我是一个方法,可以给你一个List<String>。关于我实际给你的东西,你需要知道的只是它是一个列表,并具有列表的行为和语义,即你可以将东西放入其中,它会维护它们的顺序,并且你可以遍历它。

你不需要知道我如何存储东西,如何使它们可访问等等。那些都不重要。对于你来说,它可能是一个LinkedList<String>,一个ArrayList<String>或者完全新的东西。可以这么说,我已经选择了一些东西,你可以愉快地使用它。

当你使用继承来扩展类并添加新行为时,你确实需要引用子类才能访问它。这两种方法有些互补,但是使用情况不同。


0

除非API要求,否则没有真正的必要这样做。例如,如果在特定的API或代码库中存在一个

void ReallyUsefulFunction(BaseClass instance)

如果您想使用某个类,您可以从BaseClass派生一个类,并在SubClass中实现其方法。然后,您现在可以将子类传递给函数。
尽管如此,在技术上,您仍然可以实现自己的类。
void MyReallyUsefulFunction(MyClass instance)

这段代码模仿了相同的功能。但正如MYYM所解释的那样,代码重用等好处可能是巨大的,这就是你想利用多态性的时候。


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