Java 为什么我们真的需要降级?

Java 为什么我们真的需要降级?,java,downcast,Java,Downcast,我想知道为什么我需要向下投射。我重新阅读了拼贴上的笔记,发现了下面的例子 class Student {...} class Graduate exteds Student { getResearchTopic(){...} // this method only exists in Graduate class. } 我们有一个学生类的参考,希望访问getResearchTopic方法 Student s1 = new Graduate(); if(s1 instanceof Gra

我想知道为什么我需要向下投射。我重新阅读了拼贴上的笔记,发现了下面的例子

class Student {...}

class Graduate exteds Student {
   getResearchTopic(){...} // this method only exists in Graduate class.
}
我们有一个学生类的参考,希望访问getResearchTopic方法

Student s1 = new Graduate();
if(s1 instanceof Graduate){
   ((Graduate)s1).getResearchTopic();
}

这是一个很好的向下投射的例子哈?我的问题是,为什么不首先宣布s1为毕业生?是否有一个现实生活中的例子,我将不得不向下转换而不是使用实际类的实例?

好吧,您可以将引用
s1
声明为
Graduate
类型。声明
super-type
的引用所获得的主要好处是多态性的威力

使用指向子类对象的超类型引用,可以将同一引用绑定到多个子类对象。调用的实际方法将在运行时根据所指向的对象确定。但是,主要的条件是,该方法也应该在子类中定义,否则编译器将无法找到方法声明

在这里,您被迫
downcast
,因为您还没有在超类中定义方法。因为编译器无法在
Student
类中看到该方法的定义。它不知道实际对象
s1
指向什么。请记住,编译器仅检查引用类型以查找meethod声明

通常,当您看到自己在代码中向下转换到子类时,几乎总是一个错误的信号(尽管也有一些例外)。你应该修改你的类


让我们看看使用超类引用而不是子类引用有什么好处:

Student student = new Phd();
student.getResearchTopic();   // Calls Phd class method

student = new Graduate();
student.getResearchTopic();    // Calls Graduate class method
例如:假设您有另一个子类
Student
as:

class Phd extends Student {
    getResearchTopic(){...}
}
您还可以在
Student
类中提供一个定义(默认定义):

class Student {
    getResearchTopic(){...}
}
现在,您创建以下两个对象,它们都由
Student
reference指向:

Student student = new Phd();
student.getResearchTopic();   // Calls Phd class method

student = new Graduate();
student.getResearchTopic();    // Calls Graduate class method
因此,只需一个引用,就可以访问特定于子类的方法


您可以在
工厂方法
模式中看到此功能的一个主要实现,其中单个静态方法根据某些条件返回不同子类的对象:

public static Student getInstance(String type) {
    if (type.equals("graduate")) 
        return new Graduate();
    else if (type.equals("phd"))
        return new Phd();
}
因此,您可以看到相同的方法返回不同子类的对象

以上所有的事情你都可以做,仅仅因为一个概念:


超级类引用可以引用任何子类对象,但反之亦然。

如果您想使用多态性,最好使用
Student
对象,然后“downcast”使用特定于
grade
对象的方法


通常,如果您有一个处理
Student
对象的方法,那么该方法在编译时并不真正知道传入了什么类型的
Student
对象。因此,在运行时,该方法应相应地检查特定类型和流程。

假设您有一个方法,该方法将
学生
作为参数。它所做的大多数事情对所有学生来说都是通用的。但是,如果它是一名
毕业生
,那么它也可能会做其他事情。在这种情况下,您需要确定传入的
学生是否实际上是
毕业生
,并在该实例中执行一些特殊逻辑

也许是这样的:

class StudentDAO {
    public void SaveStudent(Student s) {
       // Do something to save the student data to a database.

       if ( s instanceof Graduate ) {
           // Save their research topic too.
       }
    }
}

请注意,这样做通常是一种糟糕的编程实践,但有时是有意义的。

当您试图创建泛型方法时,向下转换确实有帮助。例如,我经常看到将XML
字符串
解析为
对象
的代码。然后可以将
对象
向下转换为您(作为编码者)知道它所代表的特定
对象

private static XStream xstream = new XStream(new DomDriver());
static {
    xstream.processAnnotations(MyFirstClass.class);
    xstream.processAnnotations(MySecondClass.class);
    // ...
}

public static Object fromXML(String xml) {
    return xstream.fromXML(xml);
}
这让我可以创建一个非常通用的方法,在所有情况下都能实现我想要的功能。然后,当我调用它时,我可以简单地将
对象
向下转换为我所知道的对象。它使我不必为每种对象类型创建单独的解析方法,并提高了代码的可读性

MyFirstClass parsedObject = (MyFirstClass) MyXMLTransformer.fromXML(xml);

使用默认Java反序列化程序反序列化对象时,使用此代码(使用其他反序列化程序时使用类似代码,例如Jackson JSON反序列化程序)


然后您需要将
obj
转换为它的实际类型,因为
readObject()
将始终返回一个普通的
对象
-该方法无法静态验证正在读取的对象的类型

为什么不首先将s1声明为毕业生这是main OP关心的问题,也许你想扩展答案来解决这部分问题。@LuiggiMendoza John。对不起,我刚才误读了这个问题。现在就编辑。事实上,使用向下投射会降低多态性的能力。@LuiggiMendoza。是的,对。我已经扩展了我的答案来解释一般概念。我想说,在这种情况下使用向下投射看起来像是应用程序中的一个设计问题。您必须使用向下转换的一个地方是重写
equals
方法(如可能的dup问题中所示)。我正要发布一个答案=\无论如何花了太长时间。在您的情况下,您可以将该方法重新定义为
private T fromXml(String xml,Class clazz)
,并使其成为真正的泛型方法,这样就避免了向下投射的问题。@LuiggiMendoza我想这也是真的。:)说得好。