抽象类作为可序列化对象

19

我的应用程序基本上有以下结构:

我的应用程序的UML

如果没有抽象类ProjectItem,实现这样的结构将是简单明了的,但是在这种情况下,我不知道如何实现。

抽象类ProjectItem需要一个CREATOR,因为它应该是可包裹的(parcelable)。 (例如,在构造函数Project(Parcel in)中使用in.readTypedList(mProjectItems, ProjectItem.CREATOR);

但事实上,由于逻辑原因,只能在其派生类中实现CREATOR

那么,如何实现此结构以使类Project可包裹??

编辑

这是Project的构造函数之一的样子:

private Project(Parcel in) {
    in.readTypedList(mProjectItems, ProjectItem.CREATOR);
}

但正如我之前所说,ProjectItem 不应该实现一个 CREATOR

4个回答

10

我的解决方案与evertvandenbruel非常相似。但是我使用int来标识具体的类,以便我可以使用switch块。我还将该switch块放在static getConcreteClass(Parcel)方法中。

AbstractClass.java

public abstract class AbstractClass implements Parcelable {

public static final int CLASS_TYPE_ONE = 1;
public static final int CLASS_TYPE_TWO = 2;

public static final Creator<AbstractClass> CREATOR = new Creator<AbstractClass>() {
    @Override
    public AbstractClass createFromParcel(Parcel source) {

        return AbstractClass.getConcreteClass(source);
    }

    @Override
    public AbstractClass[] newArray(int size) {
        return new AbstractClass[size];
    }
};

protected String mAbstractClassString;

public AbstractClass(String abstractClassString) {
    mAbstractClassString = abstractClassString;
}

public AbstractClass(Parcel source) {
    mAbstractClassString = source.readString();
}

public static AbstractClass getConcreteClass(Parcel source) {

    switch (source.readInt()) {
        case CLASS_TYPE_ONE:
            return new ConcreteClassOne(source);
        case CLASS_TYPE_TWO:
            return new ConcreteClassTwo(source);
        default:
            return null;
    }
}

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(mAbstractClassString);
}

@Override
public String toString() {
    return "Parent String: " + mAbstractClassString + '\n';
}
}

具体类一.java

public class ConcreteClassOne extends AbstractClass {

private String mString;

public ConcreteClassOne(String abstractClassMemberString, String string) {
    super(abstractClassMemberString);

    mString = string;
}

public ConcreteClassOne(Parcel source) {
    super(source);
    mString = source.readString();
}

@Override
public void writeToParcel(Parcel dest, int flags) {

    dest.writeInt(CLASS_TYPE_ONE);
    super.writeToParcel(dest, flags);
    dest.writeString(mString);
}

@Override
public String toString() {
    return super.toString().concat("Child String: " + mString);
}
}

ConcreteClassTwo.java

public class ConcreteClassTwo extends AbstractClass {

private String mString;
private int mInt;

public ConcreteClassTwo(String abstractClassString, String string, int anInt) {
    super(abstractClassString);
    mString = string;
    mInt = anInt;
}

public ConcreteClassTwo(Parcel source) {
    super(source);
    mString = source.readString();
    mInt = source.readInt();
}

@Override
public void writeToParcel(Parcel dest, int flags) {

    dest.writeInt(CLASS_TYPE_TWO);
    super.writeToParcel(dest, flags);
    dest.writeString(mString);
    dest.writeInt(mInt);
}

@Override
public String toString() {

    String string = super.toString();
    for (int i = 0; i < mInt; i++) {
        string = string.concat("Child String: " + mString + '\n');
    }
    return string;
}
}

1
仅供参考:自Java 7起,字符串类型现在支持在switch语句中使用。对于Java 7之前的用户来说,为了提高可读性而使用int的优化方法也并不差。 - Chantell Osejo

3
所选答案(来自evertvandenbruel的帖子)存在漏洞。正确的代码必须考虑到当只有一个子类被打包时进行打包,而不是超类对象列表。所有其他代码应该相同,关键在于您必须在所有creator中读取type变量(请参见下面的代码)。否则,在尝试取消打包子类对象时会出现排序问题。
例如:
package com.example.parcelable_example.model;

import android.os.Parcel;
import android.os.Parcelable;

public class Cat extends Animal{

    public Cat(String name){
        super(name, "Cat");
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(getType());
        super.writeToParcel(dest, flags);
    }

    public Cat(Parcel source) {
        super(source);      
    }

    public static final Parcelable.Creator<Cat> CREATOR = new Parcelable.Creator<Cat>() {
        public Cat createFromParcel(Parcel in) {
            /** DO NOT FORGET THIS!!! **/
            type = in.readString();
            return new Cat(in);
        }

        public Cat[] newArray(int size) {
            return new Cat[size];
        }
    };

}

为什么你不把那行代码放在Animal的Parcel构造函数里呢? - Roel
1
对于这种情况:我需要将 Cat、Dog 等(Animal 的子类)的列表打包为类型为“Animal”的数组列表。在这种情况下,将使用 Animal 的 CREATOR 以及来自 writeToParcel 的数据,在.readString() 中使用以确定类型,以便可以使用其包装构造函数实例化正确的子类,该构造函数应调用 Animal 的包装构造函数使用 super(in)。如果你在 Animal 构造函数中读取类型,则会出现错误的实现 - 类型已经被读取,以便创建 Dog 对象而不是 Cat 对象。 - Chantell Osejo
为什么?你调用了new Cat(in),它调用了super(source),然后Animal的构造函数读取了类型。为什么应该调用Dogs的构造函数?编辑:我明白了:当使用Animal CREATOR时,它将被读取两次,然后包裹就会被破坏。但是你在这里做的是不可能的,因为你设置了什么类型? - Roel
我通过在包裹构造函数中添加一个名为“readObjectType”的布尔参数来解决了这个问题。当使用抽象CREATOR时,该参数为false,而使用具体的CREATOR时则为true。 - Roel
再说一遍?你不需要除了提供的源代码之外的任何东西。不需要布尔参数。无论使用哪个CREATOR,你都应该始终读取对象类型。关键在于,在parcelable实现中只使用一个CREATOR,但所有CREATOR仍然必须读取类型(因为它们都写入类型,由于抽象超类的writeToParcel()实现)。 - Chantell Osejo

2
这个问题源于一个错误的假设。
以下是原帖的引用:
抽象类ProjectItem需要一个CREATOR,因为它应该是可包含的。
实际上,由于它是抽象的,超类不需要定义CREATOR。
以下是演示该方法的最简示例。
/*   Super class   */

abstract class SuperClass
        implements Parcelable {

    protected SuperClass(Parcel in) {
        mSuperId = in.readLong();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(mSuperId);
    }

}



/*   Sub class   */

public class SubClass
        extends SuperClass {

    protected SubClass(Parcel in) {
        super(in);
        mSubId = in.readLong();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeLong(mSubId);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<SubClass> CREATOR = new Creator<SubClass>() {

        @Override
        public SubClass createFromParcel(Parcel in) {
            return new SubClass(in);
        }

        @Override
        public SubClass[] newArray(int size) {
            return new SubClass[size];
        }

    };

}



/*   Usage   */

class AnotherClass {

    void aMethod() {
        Bundle args = new Bundle();
        args.putParcelable("EXTRA_SUPER_CLASS", subClassObject);
    }

}

在某些情况下,您需要使用父类来实现CREATOR,例如在AIDL中传递抽象类。 - Adeel Ahmad

0
public abstract class A implements Parcelable {
    private int a;

    protected A(int a) {
        this.a = a;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(a);
    }

    protected A(Parcel in) {
        a = in.readInt();
    }
}

public class B extends A {
    private int b;

    public B(int a, int b) {
        super(a);
        this.b = b;
    }

    public static final Parcelable.Creator<B> CREATOR = new Parcelable.Creator<B>() {
        public B createFromParcel(Parcel in) {
            return new B(in);
        }

        public B[] newArray(int size) {
            return new B[size];
        }
    };

    public int describeContents() {
        return 0;
    }
}

1
不幸的是,这并没有太大帮助,因为我的当前结构需要在父类中实现一个“CREATOR”。 - FranBran

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