Go 是静态类型语言。每一个变量都有一个静态的类型,即在编译时类型已知且固定:比如 int
、float32
。
接口类型是类型的一个重要类别,它表示固定的方法集。接口变量可以存储任何具体值(非接口),只要该值实现接口的方法即可。如:
// Reader 是封装基本 Read 方法的接口
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer 是封装基本 Write 方法的接口
type Writer interface {
Write(p []byte) (n int, err error)
}
任何实现了 Read(p []byte) (n int, err error)
方法的类型都被称为实现了 Reader
接口(Writer
同理)。这意味着
Reader
可以保存实现了 Read
方法的任何值:
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
需要明确的是,不管 r
可能包含什么具体值,r
的类型始终是 io.Reader
:Go 是静态类型的语言,而 r
的静态类型是 io.Reader
。
接口类型的一个非常重要的示例是空接口:
interface{}
它表示空的方法集,并且任何值都满足空接口,因为任何值都有零个或者多个方法。
有人说 Go 的接口是动态类型的,但这会产生误导。接口是静态类型的:接口类型的变量始终具有相同的类型,即使在运行时存储在接口变量中的值可能会更改类型,该值也始终满足接口的要求。
**接口类型的变量存储了一对值:分配给该变量的具体值,以及该值的类型描述。**更确切地说,该值是实现接口的基础具体数据项,而类型描述了该数据项的完整类型。例如:
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
r
中包含了 (value, type)
对,即 (tty, *os.File)
。请注意,类型 *os.File
实现的方法不只有 Read
;
尽管接口仅提供对 Read
方法的访问,但是其内部的值仍包含有关该值的所有类型信息。这就是为什么我们可以做下面的事情:
var w io.Writer
w = r.(io.Writer)
因为 r
的具体类型里面包含了 Write
方法,而 r
里面包含的值依然持有它原来的值,所以这个断言是没有问题的。
一个重要的细节是,接口内始终保存
(值, 具体类型)
形式的元素对,而不会有(值, 接口类型)
的形式。接口内部不持有接口值。
反射对象主要有两类:reflect.Type
、reflect.Value
从底层讲,反射只是一种检查存储在接口变量中的值和类型对的机制。首先,我们需要了解反射包的两个类型:Type
和 Value
,
通过这两个类型可以访问接口变量的内容。还有两个函数 reflect.TypeOf
和 reflect.ValueOf
,它们可以从接口值中取出
reflect.Type
和 reflect.Value
。(另外,从 reflect.Value
可以很容易地获取到 reflect.Type
,但是让我们暂时将 Value
和 Type
的概念分开。)
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
// 打印 type: float64
fmt.Println("type:", reflect.TypeOf(x))
}
上面的代码看起来像将 float64
类型的变量 x
传递给了 reflect.TypeOf
,而不是传递的接口值。但实际上,传递的是接口;
// TypeOf 返回 interface{} 中值的反射类型
func TypeOf(i interface{}) Type
当我们调用 reflect.TypeOf(x)
时,x
先被存在一个空接口中,然后再作为参数传递;reflect.TypeOf
从该空接口中恢复类型信息。
相应的,reflect.ValueOf
函数会恢复值信息。
var x float64 = 3.4
// value: <float64 Value>
fmt.Println("value:", reflect.ValueOf(x).String())
reflect.Type
和 reflect.Value
都有许多方法可以让我们执行检查和操作:
Value
具有 Type
方法,该方法返回 reflect.Value
的 Type
类型。Type
和 Value
都有一个 Kind
方法,该方法返回 go 的类型(语言本身的类型,而不是自定义的类型)Value
的很多方法,名字类似于 Int
和 Float64
,可以让我们获取存储在里面的值。SetInt
和 SetFloat
之类的方法,可以修改接口的值。给定 reflect.Value
,我们可以使用 Interface()
方法恢复接口值;
实际上,该方法将类型和值信息打包回接口表示形式并返回结果:
//接口返回v的值作为接口{}。
func (v Value) Interface() interface{}
结果,我们可以说
y := v.Interface().(float64) // y的类型为float64
fmt.Println(y)
打印反射对象 v
表示的 float64
值。一种更简洁的写法是:
// fmt.Println 本身就接受 interface{} 参数
fmt.Println(y)
不可设置的例子:
var x float64 = 3.4
v:= reflect.ValueOf(x)
// panic: reflect.Value.SetFloat using unaddressable value
v.SetFloat(7.1)//错误:会panic错误。
因为调用 reflect.ValueOf(x)
的时候,函数只拿到了 x
的副本,而不是 x
变量本身,如果我们在函数内部修改了 x
那也只是修改了副本而已。
Value
的 CanSet
方法报告 Value
的可设置性:
var x float64 = 3.4
v:= reflect.ValueOf(x)
// false
fmt.Println("settability of v:", v.CanSet())
如果我们想修改它,可以在反射的时候,直接使用 x
的指针:
var x float64 = 3.4
p := reflect.ValueOf(&x) // 注意:取 x 的地址
// type of p: *float64
// settability of p: false
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
我们注意到,这里我们使用了指针,但依然是不能设置其值。这是因为反射对象 p
是不可设置的,实际上我们想要设置的不是 p
,而是 *p
。为了获取 p
指向的内容,我们调用 Value
值的 Elem
方法,该方法指向指针:
v := p.Elem()
// settability of v: true
fmt.Println("settability of v:", v.CanSet())
现在,v
是一个可设置的反射对象了,我们可以使用 v.SetFloat
来修改 x
的值了:
v.SetFloat(7.1)
// 7.1
fmt.Println(v.Interface())
// 7.1
fmt.Println(x)
反射值需要变量的地址才能修改其表示的值。
在下面的例子中,我们使用结构体的地址创建反射对象,因为稍后将要对其进行修改。然后我们将 typeOfT
设置为其反射类型,
并使用简单的方法调用对字段进行迭代。
type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v.", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
0: A int = 23
1: B string = skidoo
此处传递的内容还涉及可设置性的另一点:
T
的字段名是大写(已导出),因为只能设置结构体的导出字段。
因为 s
包含可设置的反射对象,所以我们可以修改结构的字段:
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
如果我们修改代码从
t
而不是&t
创建s
,则对SeteInt
和SetString
的调用将失败,因为无法设置t
的字段。
反射定律: