谈谈Go语言的反射三定律

2020-01-28 12:15:17丽君

简介

Reflection(反射)在计算机中表示 程序能够检查自身结构的能力,尤其是类型。它是元编程的一种形式,也是最容易让人迷惑的一部分。

虽然Go语言没有继承的概念,但为了便于理解,如果一个struct A 实现了 interface B的所有方法时,我们称之为“继承”。

类型和接口

反射建立在类型系统之上,因此我们从类型基础知识说起。

Go是静态类型语言。每个变量都有且只有一个静态类型,在编译时就已经确定。比如 int、float32、*MyType、[]byte。 如果我们做出如下声明:


type MyInt int

var i int
var j MyInt

上面的代码中,变量 i 的类型是 int,j 的类型是 MyInt。 所以,尽管变量 i 和 j 具有共同的底层类型 int,但它们的静态类型并不一样。不经过类型转换直接相互赋值时,编译器会报错。

关于类型,一个重要的分类是 接口类型(interface),每个接口类型都代表固定的方法集合。一个接口变量就可以存储(或“指向”,接口变量类似于指针)任何类型的具体值,只要这个值实现了该接口类型的所有方法。一组广为人知的例子是 io.Reader io.Writer, Reader 和 Writer 类型来源于 io包,声明如下:


// Reader is the interface that wraps the basic Read method.
type Reader interface {
 Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
 Write(p []byte) (n int, err error)
}

任何实现了 Read(Write)方法的类型,我们都称之为继承了 io.Reader(io.Writer)接口。换句话说, 一个类型为 io.Reader 的变量 可以指向(接口变量类似于指针)任何类型的变量,只要这个类型实现了Read 方法:


var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

要时刻牢记:不管变量 r 指向的具体值是什么,它的类型永远是 io.Reader。再重复一次:Go语言是静态类型语言,变量 r 的静态类型是 io.Reader。

一个非常非常重要的接口类型是空接口,即:


interface{}

它代表一个空集,没有任何方法。由于任何具体的值都有 零或更多个方法,因此类型为interface{} 的变量能够存储任何值。

有人说,Go的接口是动态类型的。这个说法是错的!接口变量也是静态类型的,它永远只有一个相同的静态类型。如果在运行时它存储的值发生了变化,这个值也必须满足接口类型的方法集合。

由于反射和接口两者的关系很密切,我们必须澄清这一点。

接口变量的表示