TypeConverter 实现的索引越界错误

Posted

技术标签:

【中文标题】TypeConverter 实现的索引越界错误【英文标题】:Index out of bound error with TypeConverter implementation 【发布时间】:2021-05-17 23:43:46 【问题描述】:

我正在尝试将这个数据类保存到数据库中:

    @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 类只有两个字段,所以我不会在索引上犯错。如果我暴力破解fromStringToIsp() 通过,其他一些函数也会报越界错误。

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

【问题讨论】:

【参考方案1】:

您传递给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]

【讨论】:

我知道索引越界异常是什么意思。我不明白为什么str 不包含分号。我使用return "$isp.seaLevel;$isp.vacuum"Isp 创建一个字符串。因此,如果数据库实现调用fromStringToType(String): Type,那么fromTypeToString(Type): String 必须事先被调用。 再次阅读我的答案。您在两个级别上使用了相同的分隔符。

以上是关于TypeConverter 实现的索引越界错误的主要内容,如果未能解决你的问题,请参考以下文章

为啥python的列表切片不会产生索引越界错误? [复制]

“数组索引越界”的 Visual Studio 等效错误

TypeConverter() 在 Android 中的 Room 出现 TypeConverter 错误时具有私有访问权限

java中的字符串索引越界错误(charAt)

线性搜索期间二维数组中的数组索引越界错误[关闭]

Objective-C 对数组进行切片会导致索引越界错误