在学习静态编程语言中的泛型时,我在一本书中读到了以下内容:
通常,如果类具有将其用作返回类型的函数,或者如果类具有该类型的val属性,则类或接口泛型类型可以以out作为前缀。但是,如果类具有该泛型类型的函数参数或var属性,则不能使用out。
我理解规则所说的,但我很乐意(通过示例)理解没有此规则可能会有什么(即在声明泛型类/接口时使用out时没有约束),以及为什么返回类型可以来自类型t,而类/接口仍然可以包含out t并不“危险”。
示例,其中无法理解类属性将表现为协变的问题是什么:
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???
}
}
此外,本书显示以下代码:
抽象类E
虽然泛型类型是构造函数的输入,但代码仍在编译中。这不违反规定吗?
首先让我们通过在type参数
前面加上out
关键字
来明确我们得到了什么。考虑以下class
:
class MyList<out T: Number>{
private val list: MutableList<T> = mutableListOf()
operator fun get(index: Int) : T = list[index]
}
这里的关键字使MyList在T中协变,这意味着您可以执行以下操作:
// note that type on left side of = is different than the one on right
val numberList: MyList<Number> = MyList<Int>()
如果删除out关键字并再次尝试编译,则会出现类型不匹配错误。
通过在type
参数
前面加上out
,您基本上是在声明type
是T
的生产者,在上面的示例中MyList
是Numbers的生产者。这意味着无论您将实例化
T
作为Int
或Double
或Number
的其他子类型,您都可以从MyList
中获取Number(因为Number
的每个子类型都是一个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
现在让我们回答without
关键字为什么T
应该只在返回位置?没有这个约束会发生什么?,即如果MyList
有一个以T
为参数的函数。
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>() }
}
这就是为什么需要约束,它基本上是为了保证类型安全。
对于抽象类E
请注意,构造函数参数既不在in位置,也不在out位置。即使类型参数声明为out,您仍然可以在构造函数参数中使用它。如果您将类实例作为更泛型类型的实例来使用,那么这种差异可以防止类实例被滥用:您只是不能调用潜在危险的方法。构造函数不是稍后(在创建实例之后)可以调用的方法,因此它不会有潜在的危险。
问题是:
val x = DogSomething()
val y: PetSomething<Pet> = x // would be allowed by out
y.petDoSomething(Cat())
请注意,狗狗身上的宠物物品只需处理狗狗。
虽然泛型类型是构造函数的输入,但代码仍在编译中。这不违反规定吗?
它不是,因为构造函数不是相关意义上的成员;无法在上面的y上调用它。
您引用了:“但是,如果类具有该泛型类型的函数参数或var属性,则不能使用out。”
构造函数不是成员函数或属性,因此不受此规则的约束。在构造函数的站点将该类型用于参数是安全的,因为在构造它时该类型是已知的。
考虑这些类:
abstract class Pet
class Cat: Pet()
class Dog: Pet()
class PetOwner<out T: Pet>(val pet: T)
当您调用PetOwner构造函数并传入Cat时,编译器知道您正在构造PetOwner
函数参数和var属性对于out类型来说是不安全的,因为对象已经被构造,并且可能已经被传递给某个变量,而该变量已经将其向上转换为其他变量。
假设编译器允许您为var属性定义out T,如下所示:
class PetOwner<out T: Pet>(var pet: T)
然后你可以这样做:
val catOwner: PetOwner<out Cat> = PetOwner(Cat())
val petOwner: PetOwner<out Pet> = catOwner
petOwner.pet = Dog()
val cat: Cat = catOwner.pet // ClassCastException!
类型安全规则阻止了这种情况的发生。但这对于构造函数参数是不可能的。在将参数传递给构造函数和拥有可以传递的实例之间,无法将对象传递给其他变量并向上转换其类型。
我有反应代码
我正在尝试返回一个对象,它应该是IClass的一个实现,具有一个通用类型,是IType的一个实现。 我要返回的实际类扩展了Class (abstract ),其泛型类型为ActualType: 抽象类对象实现了IClass接口,可以有任何扩展IType的类型 ActualType只是实现了IType接口 我在编译时得到一个“类型不匹配:无法从ActualClass转换为IClass”错误。我不明白
7.2. 接口类型 接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。 io.Writer类型是用的最广泛的接口之一,因为它提供了所有的类型写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等。io包中定义了很多其它有用的接口类型。Reader可以代表任意可以读取bytes的类型,Closer可以是任意可以关闭的值,例如一
在阅读了这个问题或这篇文章后,我仍然对和之间的细微差异感到有点困惑。 在本例中,我的目标是将一个简单对象分配给更广泛的
问题内容: 我正处于起步阶段,想全神贯注于Go。目前,我正在模拟一个API请求,该请求返回一个包含对象数组的JSON格式的字符串。我正在尝试找出最合适的方法来遍历每个记录并访问每个字段。最终,每个字段都将被写入Excel电子表格,但是现在我只想打印每个字段的键和值。 这就是我所拥有的(我会在Go Playground中提供它,但不支持HTTP请求): 一切工作正常,直到尝试循环访问包含每个记录的属
考虑一个具有的API,如下所示: 很简单,只有页面大小和跳过计数属性。 此外,现在我还有一些类,它们也包含但未分页。 在我的测试中,我希望他们都能实现一个接口,这样我就可以用一些更基本的测试来生成一个通用的基本测试类。为此,我添加了我认为会起作用的内容: 我将PagedResults更改为: 错误 但现在编译器抱怨PagedResultBase继承的所有地方的使用情况(?)从。 但是,如果我将接口