Java 为什么类/接口可以';如果类/接口具有val属性或泛型类型的函数,则不能以out作为前缀?
在Kotlin学习泛型时,我在一本书中读到以下内容:Java 为什么类/接口可以';如果类/接口具有val属性或泛型类型的函数,则不能以out作为前缀?,java,kotlin,generics,Java,Kotlin,Generics,在Kotlin学习泛型时,我在一本书中读到以下内容: // note that type on left side of = is different than the one on right val numberList: MyList<Number> = MyList<Int>() 通常,如果类具有将其用作返回类型的函数,或者如果类具有该类型的val属性,则类或接口泛型类型的前缀可以为out。但是,如果类具有该泛型类型的函数参数或var属性,则不能使用out 我
// note that type on left side of = is different than the one on right
val numberList: MyList<Number> = MyList<Int>()
通常,如果类具有将其用作返回类型的函数,或者如果类具有该类型的val属性,则类或接口泛型类型的前缀可以为out。但是,如果类具有该泛型类型的函数参数或var属性,则不能使用out
我理解规则所说的,但我很乐意(通过示例)理解没有这个规则会有什么(即在声明泛型类/接口时使用out时没有约束),以及为什么它不是“危险的”返回类型可以来自类型T,并且仍然类/接口可以包含outT
无法理解类属性将作为协变变量的问题的示例:
class Pet{....}
class Dog:Pet{...}
class PetSomething <T : Pet>
{
T t;
public fun petDoSomething(T t)
{
.... // what can be the problem here?
}
}
class DogSomething
{
dogDoSomething()
{
d : Dog = Dog()
petDoSomething(d)
//what is the problem here???
}
}
类宠物{….}
班犬:宠物{…}
类宠物
{
T;
公共娱乐宠物用品(T)
{
....//这里有什么问题?
}
}
什么东西
{
dogDoSomething()
{
d:Dog=Dog()
petDoSomething(d)
//这里有什么问题???
}
}
此外,本书还显示以下代码:
抽象类E(t:t){val x=t}
尽管泛型类型是构造函数的输入,但代码仍在编译中。它不是违反了规则吗?问题是:
val x = DogSomething()
val y: PetSomething<Pet> = x // would be allowed by out
y.petDoSomething(Cat())
val x=DogSomething()
val y:PetSomething=x//将被out允许
y、 petDoSomething(Cat())
请注意,Dog上的petDoSomething
东西
只需处理Dog
s
尽管泛型类型是构造函数的输入,但代码仍在编译中。这不违反规定吗
它不是,因为构造函数不是相关意义上的成员;无法在上面的y
上调用它。您引用了:“但是,如果类具有该泛型类型的函数参数或var属性,则不能使用out。”
构造函数不是成员函数或属性,因此不受此规则约束。在构造函数的站点上为参数使用该类型是安全的,因为在构造该类型时,该类型是已知的 考虑以下类别:
abstract class Pet
class Cat: Pet()
class Dog: Pet()
class PetOwner<out T: Pet>(val pet: T)
然后你可以这样做:
val catOwner: PetOwner<out Cat> = PetOwner(Cat())
val petOwner: PetOwner<out Pet> = catOwner
petOwner.pet = Dog()
val cat: Cat = catOwner.pet // ClassCastException!
val-catOwner:PetOwner=PetOwner(Cat())
val petOwner:petOwner=catOwner
petOwner.pet=Dog()
val cat:cat=catOwner.pet//ClassCastException!
类型安全规则阻止这种情况发生。但是这对于
val
构造函数参数是不可能的。在将参数传递给构造函数和拥有一个可以传递的实例之间,没有办法将对象传递给其他变量并向上转换其类型。首先让我们通过在类型参数
前面加上out
关键字
来明确得到什么。考虑下面的<代码>类< /代码>:
class MyList<out T: Number>{
private val list: MutableList<T> = mutableListOf()
operator fun get(index: Int) : T = list[index]
}
如果删除out关键字并再次尝试编译,则会出现类型不匹配错误
通过在类型
参数
前面加上out
,您基本上是将类型
声明为T
的生产者,在上面的示例中MyList
是数字生产者。
这意味着,无论您将实例化为Int
或Double
或Number
的其他子类型,您总是能够从MyList
中获得一个数字(因为Number
的每个子类型都是数字
)。这还允许您执行以下操作:
fun process(list: MyList<Number>) { // do something with every number }
fun main(){
val ints = MyList<Int>()
val doubles = MyList<Double>()
process(ints) // Int is a Number, go ahead and process them as Numbers
process(doubles) // Double is also a Number, no prob here
}
// if you remove out, you can only pass MyList<Number> to process
这就是为什么需要约束,它基本上是为了保证类型安全
至于正在编译的抽象类E(t:t){valx=t}
,有以下说法
请注意,构造函数参数既不在in中也不在out中
位置。即使类型参数声明为out,您仍然可以
在构造函数参数中使用它。差异保护阶级
如果您将其作为
更一般的类型:你不能调用潜在的危险
方法。构造函数不是以后可以调用的方法
(在创建实例之后),因此它不可能
危险
你能重新表述你问题的第一部分吗?我不明白。“如果没有这个规则(即在声明泛型类/接口时使用out时没有约束),可能会发生什么情况?”@Tenfour04,我为这个问题添加了一个示例。也许这有助于你理解我的要求。如果不是,我将尝试另一种方法,因为这对我来说有点困难。“构造函数不是成员函数或属性,因此不受此规则的约束。”但是,所述规则实际上并不完整(您也不能有
var-pets:List
。@Tenfour04,感谢您的解释和示例(在这种情况下,示例非常有用)。请您也参考我在帖子中创建的示例,并告诉我是否存在ClassCastException的情况?您的示例与上面的示例没有什么不同,只是它没有使用在构造函数中定义属性的快捷方式。该属性仍在实例化时赋值。如果是,编译器将显示错误你对泛型做了一些不安全的事情,或者如果你做了一个潜在的不安全的手动转换(使用as
关键字),就会显示一个警告。我上面的例子不会编译,因为你不能将out
与var
属性使用的类型组合。我很抱歉在两周后回复你。但直到今天,我还没有回复“战斗”来理解泛型。所以直到今天我才有足够的(我希望)知识来回应你的帖子。是否有可能创建/找到不适用于列表的示例,
fun process(list: MyList<Number>) { // do something with every number }
fun main(){
val ints = MyList<Int>()
val doubles = MyList<Double>()
process(ints) // Int is a Number, go ahead and process them as Numbers
process(doubles) // Double is also a Number, no prob here
}
// if you remove out, you can only pass MyList<Number> to process
fun add(value: T) { list.add(T) } // MyList has this function
fun main() {
val numbers = getMyList() // numbers can be MyList<Int>, MyList<Double> or something else
numbers.add(someInt) // cant store Int, what if its MyList<Double> ( Int != Double)
numbers.add(someDouble) // cant do this, what if its MyList<Int>
}
// We dont know what type of MyList we going to get
fun getMyList(): MyList<Number>(){
return if(feelingGood) { MyList<Int> () }
else if(feelingOk> { MyList<Double> () }
else { MyList<SomeOtherSubType>() }
}