接口篇(三):接口和类型查询及转化
PHP 中的接口/类型查询
在 PHP 语言中,我们可以通过类型运算符 instanceof 判断某个变量是否属于指定类或接口的实例:
<?php
interface I {
public function hello();
}
class A implements I {
public function hello() {
print("Hello from A\n");
}
}
class B extends A {
public function greeter() {
print("Hello from B\n");
}
}
$a = new A;
$b = new B;
var_dump($b instanceof B);
var_dump($b instanceof A);
var_dump($b instanceof I);
以上示例代码三个 var_dump
语句打印的结果都是 bool(true)
。Go 也支持类似的查询,不过对关键字惜字如金的 Go 语言不是通过类似 instanceof
这种类型运算符来实现接口和类型查询的,那么它是怎么实现的呢?下面我们来一一揭晓。
Go 语言的接口/类型查询
接口查询和转化
首先来看接口查询。
以上篇教程介绍的 Number
类、Number1
和 Number2
接口为例,在 Go 语言中,要查询接口 Number2
指向的对象实例 num2
是否属于接口 Number1
,可以这么做:
var num1 oop1.Number = 1;
var num2 oop2.Number2 = &num1;
if num3, ok := num2.(oop1.Number1); ok {
fmt.Println(num3.Equal(1))
}
我们通过 num2.(oop1.Number1)
这个表达式判断 num2
是否是 Number1
的实例,如果是,则返回转化为 Number1
接口类型的实例 num3
,ok
值为 true
,然后执行 if
语句块中的代码;否则 ok
值为 false
,不执行 if
语句块中的代码。所以 num2.(oop1.Number1)
做了两件事情,一个是做接口查询,将查询结果作为第二个返回值,另一个是对类型进行转化,转化后的类型是圆括号中对应的查询接口。
需要注意的是,接口查询是否成功要在运行期才能够确定,它不像接口赋值,编译器只需要通过静态类型检查即可判断赋值是否可行。
类型查询和转化
接下来我们来看下类型查询。
Go 语言的类型查询实现语法和接口查询一样,我们以前面类的继承教程中定义的 Animal
、Dog
类为例,它们都位于 oop
包中,由于接口/类型查询语法左侧的变量类型必须是接口类型,所以我们需要在 oop
包中新增一个 IAnimal
接口(首字母大写才能在包外可见,这一点和类名、方法名、变量名、属性字段名一样):
type IAnimal interface {
GetName() string
Call() string
FavorFood() string
}
这样一来,Animal
和 Dog
类都实现了 IAnimal
接口,要查询 IAnimal
接口指向的实例是否属于 Dog
类型,可以这么做(Animal
中的 name
属性首字母改成大写):
var animal = oop.Animal{"小狗"}
var ianimal oop.IAnimal = oop.Dog{animal}
if dog, ok := ianimal.(oop.Dog); ok {
fmt.Println(dog.GetName())
}
同样,如果 ianimal
变量是 oop.Dog
类型,则 ok
值为 true
,dog
值为转化为 Dog
类型后的实例,执行 if
语句块中的代码;否则 ok
值为 false
。
需要注意的是,在 Go 语言类型查询时,归属于子类的实例并不归属于父类,因为类与类之间的「继承」是通过组合实现的,并不是 PHP/Java 语言中的那种父子继承关系,比如上述代码中我们把 ianimal.(oop.Dog)
替换成 ianimal.(oop.Animal)
则查询结果 ok
值为 false
。
类型查询并不经常使用,它更多是个补充,需要配合接口查询使用,此外,还可以利用反射进行类型查询,正如我们在变长参数教程中演示的那样:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch reflect.TypeOf(arg).Kind() {
case reflect.Int:
fmt.Println(arg, "is an int value.")
case reflect.String:
fmt.Printf("\"%s\" is a string value.\n", arg)
case reflect.Array:
fmt.Println(arg, "is an array type.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
因此,如果要获取 ianimal
的实际类型,可以通过 reflect.TypeOf(ianimal)
获取:
var animal = oop.Animal{"小狗"}
var ianimal oop.IAnimal = oop.Dog{animal}
fmt.Println(reflect.TypeOf(ianimal));
if dog, ok := ianimal.(oop.Dog); ok {
fmt.Println(dog.GetName())
}
fmt.Println(reflect.TypeOf(ianimal));
转化前后打印的类型值都是 oop.Dog
。
对于基本数据类型,比如 int
、string
、bool
这些,不必通过反射,直接通过 type
关键字即可获取对应的类型值:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "is an int value.")
case string:
fmt.Printf("\"%s\" is a string value.\n", arg)
case bool:
fmt.Println(arg, "is a bool value.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
Go 语言标准库中的 Println()
函数底层就是基于类似的类型查询对传入参数值进行打印的。
12 Comments
Animal的name属性声明成私有的,var animal = oop.Animal{"小狗"} 这个语句在编译的时候会报错。应该要调用构造方法去实例化
嗯 是的
按照上篇教程写的,oop1包里面只有Number1接口吧,为什么会出现oop1.Number
var ianimal oop.IAnimal = oop.Dog{animal}
应该是var ianimal oop.IAnimal = oop.Dog{&animal}
我看了下自己当时的源码 Number 后来又移到了 oop1 包里面
看代码是否报错 如果报错则需要传指针类型 否则不传指针类型也无妨 运行结果ok即可 但最好传递指针类型
教程说的基本都能看懂,问题是没有完整的示例代码,自己敲的时候例如目录不对应之类的,总数报错,感觉很影响学习进度,学院君可以有没有完整的源代码?
维护源码也是件很费神的事情 一种方式是视频的方式把所有过程录播下来 一种方式是在代码仓库为每篇教程创建一个标签 否则后面的教程覆盖前面的代码 还是没有什么意义 后续教程会改进 有什么问题 欢迎在评论中与我讨论
看懵了
没理解 if num3, ok := num2.(oop1.Number1); ok { fmt.Println(num3.Equal(1)) } 这里为啥是; 我想的是一行多个赋值,应该是, ok 呢