从抽象基类返回子类对象

6

我有一些需要(反)序列化的类。我不想在每个类中重复代码,所以我想创建像这样的子类

public class Swan extends Animal {}

并且一个像这样的基类:

public abstract class Animal {
   protected String name;
   // ...


   public void saveAnimal(String filename) {
       //ObjectOutputStream, save name...
   }

   public static /*returntype*/ loadAnimal(String filename) {
       //ObjectInputStream...
   }
}

基本上我希望这个能够工作:
Swan s1 = new Swan("name");
s1.saveAnimal("/pathToSaveFile");
Swan s2 = (Swan)loadAnimal("/pathToSaveFile") OR 
Swan s2 = loadAnimal("/pathToSaveFile")

如果Animal是抽象的,我该如何做呢?如果方法看起来像这样:
public static <T extends Animal> T loadAnimal(String filename) {
    //loadFromFile
    }

我无法返回new T(name) //无法直接实例化参数。我了解了一些反射知识,但是您无法获取泛型类型T的类。解决方法是将类型的类传递到构造函数中,但动物应该是抽象的。


1
这个问题能帮到你吗?:http://stackoverflow.com/questions/20638749/chaining-returning-base-objects-and-type-mismatch-to-extended-classes - Adam
4个回答

4

由于类型擦除的原因,你无法完全做到你想要的,但这里有一些代码展示了三种选项:

public class Foo {

    public static class Animal {
        public void save(String filename)
        {
            // Write to file
        }
        public static <T extends Animal> T load(String filename, Class<T> cls) throws Exception
        {
            T result = cls.newInstance();
            // initialize result
            return result;
        }
    }

    public static class Swan extends Animal {
        public static Swan load(String filename) throws Exception
        {
            return load(filename, Swan.class);
        }
    }

    public static void main(String[] args) throws Exception
    {
        Swan s = new Swan();
        s.save("somefile");
        Swan s2 = Swan.load("somefile", Swan.class);
        // OR equivalently
        Swan s3 = Animal.load("somefile", Swan.class);
        // OR also equivalent
        Swan s4 = Swan.load("somefile");
    }
}

为了实例化T,您需要访问Class对象,以便执行newInstance()。这需要一个无参构造函数或一些更多的代码来查找和调用适当的构造函数,但这是基本反射技术。如果您在Swan中提供load方法,则可以隐藏对Class的需求。请注意,这不是覆盖,因为继承的静态方法不参与多态。Swan.load仅仅隐藏了Animal中相应的方法。

啊,好的,那我还得把天鹅类作为参数传递。我以为有另一种方法,比如创建一个对象并进行类型转换之类的。谢谢啊! - Frank Haubenreisser
是的。你几乎可以通过第三个选项得到你想要的东西,它将Class对象的需求隐藏在了Swan.load内部。 - Jim Garrison

0
最简单的解决方法是创建一个包装器,其中包含实例类型(特定动物类)和序列化主体。这样,您将首先加载类型,然后实例化并读取正确的类。

为什么在使用Serializable时需要这样做呢?数据中不应该已经包含类型信息吗? - Sotirios Delimanolis

0
如果你这样做,然后检查实例,它就会起作用...
public static Animal  loadAnimal(String filename) {
    //ObjectInputStream...
}

例子:

Swan s1 = new Swan("name");
s1.saveAnimal("/pathToSaveFile");
Animal s2 =  loadAnimal("/pathToSaveFile") //it is ok since Swan is an animal..
if(s2 instanceOf Swan){
    Swan sccc = (Swan)s2;
} 

是的,但我不想让Animal被实例化。这就是为什么我说Animal类是抽象的。无论如何,谢谢 :-) - Frank Haubenreisser
1
@FrankHaubenreisser 这个答案中没有实例化Animal,它只是一个类型。 - LeTex

0

在声明返回类型为抽象类时没有问题。这是面向对象编程中的重要工具。例如,方法Arrays.asList(…)Collections.emptyList()Collections.unmodifiableList(…)的共同点是它们的返回类型是接口List。这并不意味着List被实例化,因为这是不可能的。它只意味着返回了该接口的未指定子类型(实现)。

因此,您可以简单地声明您的方法返回Animal,并让它返回反序列化产生的任何内容,如果将对象强制转换为Animal成功,则它是Animal的具体子类,也许是Swan

如果您想在调用方处删除类型转换,可以将期望的类型作为参数提供。

public static <T extends Animal> T loadAnimal(String filename, Class<T> type) {
    //loadFromFile
    return type.cast(loadedObject);
}

你可以调用它作为

Swan s2 = loadAnimal("/pathToSaveFile", Swan.class);

然而,这并不比显式类型转换提供更多的安全性。无法在编译时保证从外部文件加载的特定对象将包含Swan而不是任意其他动物。

这假设您反序列化了整个Animal实例,而不是它的属性。否则,您可以使用Class对象创建适当的实例,例如通过newInstance,然后再读取其属性。但请注意,这是一个有问题的设计。通常,您创建不同的子类,因为它们将具有不同的属性。


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