TypeConverter 实现的索引越界错误

Index out of bound error with TypeConverter implementation

我正在尝试将此数据 class 保存到数据库中:

    @Entity
    @Parcelize
    data class Rocket(
        @SerializedName("active")
        val active: Boolean = false,
        @SerializedName("boosters")
        val boosters: Int = 0,
        @SerializedName("company")
        val company: String = "",
        @SerializedName("cost_per_launch")
        val costPerLaunch: Int = 0,
        @SerializedName("country")
        val country: String = "",
        @SerializedName("description")
        val description: String = "",
        @SerializedName("diameter")
        val diameter: Diameter = Diameter(),
        @SerializedName("engines")
        val engines: Engines = Engines(),
        @SerializedName("first_flight")
        val firstFlight: String = "",
        @SerializedName("first_stage")
        val firstStage: FirstStage = FirstStage(),
        @SerializedName("flickr_images")
        val flickrImages: List<String> = listOf(),
        @SerializedName("height")
        val height: Height = Height(),
        @PrimaryKey
        @SerializedName("id")
        val id: String = "",
        @SerializedName("landing_legs")
        val landingLegs: LandingLegs = LandingLegs(),
        @SerializedName("mass")
        val mass: Mass = Mass(),
        @SerializedName("name")
        val name: String = "",
        @SerializedName("payload_weights")
        val payloadWeights: List<PayloadWeight> = listOf(),
        @SerializedName("second_stage")
        val secondStage: SecondStage = SecondStage(),
        @SerializedName("stages")
        val stages: Int = 0,
        @SerializedName("success_rate_pct")
        val successRatePct: Int = 0,
        @SerializedName("type")
        val type: String = "",
        @SerializedName("wikipedia")
        val wikipedia: String = ""
    ): Parcelable
    
    @Parcelize
    data class Diameter(
        @SerializedName("feet")
        val feet: Double = 0.0,
        @SerializedName("meters")
        val meters: Double = 0.0
    ): Parcelable
    
    @Parcelize
    data class Engines(
        @SerializedName("engine_loss_max")
        val engineLossMax: Int = 0,
        @SerializedName("isp")
        val isp: Isp = Isp(),
        @SerializedName("layout")
        val layout: String = "",
        @SerializedName("number")
        val number: Int = 0,
        @SerializedName("propellant_1")
        val propellant1: String = "",
        @SerializedName("propellant_2")
        val propellant2: String = "",
        @SerializedName("thrust_sea_level")
        val thrustSeaLevel: ThrustSeaLevel = ThrustSeaLevel(),
        @SerializedName("thrust_to_weight")
        val thrustToWeight: Double = 0.0,
        @SerializedName("thrust_vacuum")
        val thrustVacuum: ThrustVacuum = ThrustVacuum(),
        @SerializedName("type")
        val type: String = "",
        @SerializedName("version")
        val version: String = ""
    ): Parcelable
    
    @Parcelize
    data class FirstStage(
        @SerializedName("burn_time_sec")
        val burnTimeSec: Int = 0,
        @SerializedName("engines")
        val engines: Int = 0,
        @SerializedName("fuel_amount_tons")
        val fuelAmountTons: Double = 0.0,
        @SerializedName("reusable")
        val reusable: Boolean = false,
        @SerializedName("thrust_sea_level")
        val thrustSeaLevel: ThrustSeaLevel = ThrustSeaLevel(),
        @SerializedName("thrust_vacuum")
        val thrustVacuum: ThrustVacuum = ThrustVacuum()
    ): Parcelable
    
    @Parcelize
    data class Height(
        @SerializedName("feet")
        val feet: Double = 0.0,
        @SerializedName("meters")
        val meters: Double = 0.0
    ): Parcelable {
        fun toStringMetric(): String {
            return "Height: $meters m"
        }
    
        fun toStringImperial(): String {
            return "Height: $feet ft"
        }
    }
    
    @Parcelize
    data class LandingLegs(
        @SerializedName("material")
        val material: String = "",
        @SerializedName("number")
        val number: Int = 0
    ): Parcelable
    
    @Parcelize
    data class Mass(
        @SerializedName("kg")
        val kg: Int = 0,
        @SerializedName("lb")
        val lb: Int = 0
    ): Parcelable {
        fun toStringMetric(): String {
            return "Mass: $kg kg"
        }
    
        fun toStringImperial(): String {
            return "Mass: $lb lbs"
        }
    }
    
    @Parcelize
    data class PayloadWeight(
        @SerializedName("id")
        val id: String = "",
        @SerializedName("kg")
        val kg: Int = 0,
        @SerializedName("lb")
        val lb: Int = 0,
        @SerializedName("name")
        val name: String = ""
    ): Parcelable
    
    @Parcelize
    data class SecondStage(
        @SerializedName("burn_time_sec")
        val burnTimeSec: Int = 0,
        @SerializedName("engines")
        val engines: Int = 0,
        @SerializedName("fuel_amount_tons")
        val fuelAmountTons: Double = 0.0,
        @SerializedName("payloads")
        val payloads: Payloads = Payloads(),
        @SerializedName("reusable")
        val reusable: Boolean = false,
        @SerializedName("thrust")
        val thrust: Thrust = Thrust()
    ): Parcelable
    
    @Parcelize
    data class Isp(
        @SerializedName("sea_level")
        val seaLevel: Int = 0,
        @SerializedName("vacuum")
        val vacuum: Int = 0
    ): Parcelable
    
    @Parcelize
    data class ThrustSeaLevel(
        @SerializedName("kN")
        val kN: Int = 0,
        @SerializedName("lbf")
        val lbf: Int = 0
    ): Parcelable
    
    @Parcelize
    data class ThrustVacuum(
        @SerializedName("kN")
        val kN: Int = 0,
        @SerializedName("lbf")
        val lbf: Int = 0
    ): Parcelable
    
    @Parcelize
    data class Payloads(
        @SerializedName("composite_fairing")
        val compositeFairing: CompositeFairing = CompositeFairing(),
        @SerializedName("option_1")
        val option1: String = ""
    ): Parcelable
    
    @Parcelize
    data class Thrust(
        @SerializedName("kN")
        val kN: Int = 0,
        @SerializedName("lbf")
        val lbf: Int = 0
    ): Parcelable
    
    @Parcelize
    data class CompositeFairing(
        @SerializedName("diameter")
        val diameter: Diameter = Diameter(),
        @SerializedName("height")
        val height: Height = Height()
    ): Parcelable

In order to do that, I have to write type converters that convert non-primitive types. My naive solution is to just convert everything into a string that's divided by `;`.

这是我写的:

class RocketTypeConverter {

    @TypeConverter
    fun fromDiameterToString(diam: Diameter?): String? {
        return diam?.let {
            "${diam.feet};${diam.meters}"
        }
    }

    @TypeConverter
    fun fromStringToDiameter(str: String?): Diameter? {
        str?.let {
            str.split(";").also {
                return Diameter(
                    feet = it[0].toDouble(),
                    meters = it[1].toDouble()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromEnginesToString(engines: Engines?): String? {
        engines?.let {
            engines.apply {
                return "$engineLossMax" +
                        ";" + fromIspToString(isp) +
                        ";" + layout +
                        ";" + "$number" +
                        ";" + propellant1 +
                        ";" + propellant2 +
                        ";" + fromThrustSeaLevelToString(thrustSeaLevel) +
                        ";" + "$thrustToWeight" +
                        ";" + fromThrustVacuumToString(thrustVacuum) +
                        ";" + type +
                        ";" + version
            }
        }
        return null
    }

    @TypeConverter
    fun fromStringToEngines(str: String?): Engines? {
        str?.let {
            str.split(";").also {
                val isp = fromStringToIsp(it[1])
                val thrustSeaLevel = fromStringToThrustSeaLevel(it[6])
                val thrustVacuum = fromStringToThrustVacuum(it[8])
                return if(isp != null && thrustSeaLevel != null && thrustVacuum != null) {
                    Engines(
                        engineLossMax = it[0].toInt(),
                        isp = isp,
                        layout = it[2],
                        number = it[3].toInt(),
                        propellant1 = it[4],
                        propellant2 = it[5],
                        thrustSeaLevel = thrustSeaLevel,
                        thrustToWeight = it[7].toDouble(),
                        thrustVacuum = thrustVacuum,
                        type = it[9],
                        version = it[10]
                    )
                } else {
                    null
                }
            }
        }
        return null
    }

    @TypeConverter
    fun fromFirstStageToString(firstStage: FirstStage?): String? {
        firstStage?.let {
            firstStage.apply {
                return "$burnTimeSec" +
                        ";" + "$engines" +
                        ";" + "$fuelAmountTons" +
                        ";" + (if(reusable) "1" else "0") +
                        ";" +  fromThrustSeaLevelToString(thrustSeaLevel) +
                        ";" + fromThrustVacuumToString(thrustVacuum)
            }
        }
        return null
    }

    @TypeConverter
    fun fromStringToFirstStage(str: String?): FirstStage? {
        str?.let {
            str.split(";").also {
                val thrustSeaLevel = fromStringToThrustSeaLevel(it[4])
                val thrustVacuum = fromStringToThrustVacuum(it[5])
                return if(thrustSeaLevel != null && thrustVacuum != null) {
                    FirstStage(
                        burnTimeSec = it[0].toInt(),
                        engines = it[1].toInt(),
                        fuelAmountTons = it[2].toDouble(),
                        reusable = it[3] == "1",
                        thrustSeaLevel = thrustSeaLevel,
                        thrustVacuum = thrustVacuum
                    )
                } else {
                    null
                }
            }
        }
        return null
    }

    @TypeConverter
    fun fromHeightToString(height: Height?): String? {
        height?.let {
            return "${height.feet};${height.meters}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToHeight(str: String?): Height? {
        str?.let {
            str.split(";").also {
                return Height(
                    feet = it[0].toDouble(),
                    meters = it[1].toDouble()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromLadingLegsToString(landingLegs: LandingLegs?): String? {
        landingLegs?.let {
            return "${landingLegs.material};${landingLegs.number}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToLandingLegs(str: String?): LandingLegs? {
        str?.let {
            str.split(";").also {
                return LandingLegs(
                    material = it[0],
                    number = it[1].toInt()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromMassToString(mass: Mass?): String? {
        mass?.let {
            return "${mass.kg};${mass.lb}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToMass(str: String?): Mass? {
        str?.let {
            str.split(";").also {
                return Mass(
                    kg = it[0].toInt(),
                    lb = it[1].toInt()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromPayLoadWeightToString(payloadWeight: PayloadWeight?): String? {
        payloadWeight?.let {
            return "${payloadWeight.id};${payloadWeight.kg};${payloadWeight.lb};${payloadWeight.name}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToPayloadWeight(str: String?): PayloadWeight? {
        str?.let {
            str.split(";").also {
                return PayloadWeight(
                    id = it[0],
                    kg = it[1].toInt(),
                    lb = it[2].toInt(),
                    name = it[3]
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromSecondStageToString(secondStage: SecondStage?): String? {
        secondStage?.let {
            secondStage.apply {
                return "$burnTimeSec" +
                        ";" + "$engines" +
                        ";" + "$fuelAmountTons" +
                        ";" + fromPayloadsToString(payloads) +
                        ";" + (if(reusable) "1" else "0") +
                        ";" + fromThrustToString(thrust)
            }
        }
        return null
    }

    @TypeConverter
    fun fromStringToSecondStage(str: String?): SecondStage? {
        str?.let {
            str.split(";").also {
                val payloads = fromStringToPayloads(it[3])
                val thrust = fromStringToThrust(it[5])
                return if(payloads != null && thrust != null) {
                    return SecondStage(
                        burnTimeSec = it[0].toInt(),
                        engines = it[1].toInt(),
                        fuelAmountTons = it[2].toDouble(),
                        payloads = payloads,
                        reusable = it[4] == "1",
                        thrust = thrust
                    )
                } else {
                    null
                }
            }
        }
        return null
    }

    @TypeConverter
    fun fromThrustToString(thrust: Thrust?): String? {
        thrust?.let {
            return "${thrust.kN};${thrust.lbf}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToThrust(str: String?): Thrust? {
        str?.let {
            str.split(";").also {
                return Thrust(
                    kN = it[0].toInt(),
                    lbf = it[1].toInt()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromPayloadsToString(payloads: Payloads?): String? {
        payloads?.let {
            return fromCompositeFairingToString(payloads.compositeFairing) + payloads.option1
        }
        return null
    }

    @TypeConverter
    fun fromStringToPayloads(str: String?): Payloads? {
        str?.let {
            str.split(";").also {
                fromStringToCompositeFairing(it[0])?.let { compositeFairing ->
                    return Payloads(
                        compositeFairing = compositeFairing,
                        option1 = it[1]
                    )
                } ?: return null
            }
        }
        return null
    }

    @TypeConverter
    fun fromCompositeFairingToString(compositeFairing: CompositeFairing?): String? {
        compositeFairing?.let {
            return fromDiameterToString(compositeFairing.diameter) +
                    fromHeightToString(compositeFairing.height)
        }
        return null
    }

    @TypeConverter
    fun fromStringToCompositeFairing(str: String?): CompositeFairing? {
        str?.let {
            str.split(";").also {
                val diameter = fromStringToDiameter(it[0])
                val height = fromStringToHeight(it[1])
                return if(diameter != null && height != null) {
                    CompositeFairing(
                        diameter = diameter,
                        height = height
                    )
                } else {
                    null
                }
            }
        }
        return null
    }

    @TypeConverter
    fun fromIspToString(isp: Isp?): String? {
        isp?.let {
            return "${isp.seaLevel};${isp.vacuum}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToIsp(str: String?): Isp? {
        str?.let {
            str.split(";").also {
                return Isp(
                    seaLevel = it[0].toInt(),
                    vacuum = it[1].toInt()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromThrustSeaLevelToString(thrustSeaLevel: ThrustSeaLevel?): String? {
        thrustSeaLevel?.let {
            return "${thrustSeaLevel.kN};${thrustSeaLevel.lbf}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToThrustSeaLevel(str: String?): ThrustSeaLevel? {
        str?.let {
            str.split(";").also {
                return ThrustSeaLevel(
                    kN = str[0].toInt(),
                    lbf = str[1].toInt()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromThrustVacuumToString(thrustVacuum: ThrustVacuum?): String? {
        thrustVacuum?.let {
            return "${thrustVacuum.kN};${thrustVacuum.lbf}"
        }
        return null
    }

    @TypeConverter
    fun fromStringToThrustVacuum(str: String?): ThrustVacuum? {
        str?.let {
            str.split(";").also {
                return ThrustVacuum(
                    kN = str[0].toInt(),
                    lbf = str[1].toInt()
                )
            }
        }
        return null
    }

    @TypeConverter
    fun fromFlickrImagesToString(flickrImages: List<String>?): String? {
        flickrImages?.let {
            var imagesString = ""
            for(image in flickrImages) {
                imagesString += "$image;"
            }
            return imagesString.substring(0, (imagesString.length - 1))
        }
        return null
    }

    @TypeConverter
    fun fromStringToFlickrImages(str: String?): List<String>? {
        str?.let {
            return str.split(";")
        }
        return null
    }

    @TypeConverter
    fun fromPayloadWeightsToString(payloadWeights: List<PayloadWeight>?): String? {
        payloadWeights?.let {
            var weightsString = ""
            for(weight in payloadWeights) {
                weightsString += fromPayLoadWeightToString(weight) + ";"
            }
            return weightsString.substring(0, (weightsString.length - 1))
        }
        return null
    }

    @TypeConverter
    fun fromStringToPayloadWeights(str: String?): List<PayloadWeight>? {
        str?.let {
            val weights = mutableListOf<PayloadWeight>()
            str.split(";").also {
                for(payloadWeight in it) {
                    fromStringToPayloadWeight(payloadWeight)?.let { payloadWeight ->
                        weights.add(payloadWeight)
                    } ?: return null
                }
            }
            return weights
        }
        return null
    }
}

我的方法是有两个模块:launchrocket。每个都有自己的存储库,每个存储库都构建数据库:

private val dao: Database = Room.databaseBuilder(
        context,
        Database::class.java,
        Database.NAME_DB
    ).build()

我遇到的问题是出现此错误:java.lang.IndexOutOfBoundsException: Index: 1, Size: 1

现在,错误发生是因为 fromStringToIsp()。这看起来不像是算法错误,因为 Isp class 只有两个字段,所以我不会在索引上出错。如果我强行通过fromStringToIsp(),其他一些函数也会报告越界错误。

那么我在实施 TypeConverters 时哪里出错了?

您传递给 fromStringToIsp() 的字符串不包含分号,因为在 fromStringToEngines() 中您正在执行以下操作:

str.split(";").also {
    val isp = fromStringToIsp(it[1])
    /* ... */
}

然后,在 fromStringToIsp() 中,您执行:

str.split(";").also {
    return Isp(
        seaLevel = it[0].toInt(),
        vacuum = it[1].toInt()
    )
}

当试图访问 it[1] 时你会得到一个异常,因为 it 只有一个元素,即 it[0].