隐式接口
Java中接口
Java 的接口不仅可以定义方法签名,还可以定义变量,这些定义的变量可以直接在实现接口的类中使用,这里简单介绍一下 Java 中的接口:
// 定义 Human 接口有 run 跟 say 两个方法
interface HumanInterface {
public void run();
public void say();
}
// 实现 Human 接口 必须实现所有方法 run 与 say
class Human implements HumanInterface {
public int age;
public String name;
public void run() {
System.out.println("I can run");
}
public void say() {
System.out.println("I can say");
}
}
// 歌手
class Singer extends Human {
public String collection; // 专辑
public void sing() {
System.out.println("I can sing");
}
}
// 学生
class Student extends Human {
public String lesson;
public Student(String lesson){
this.lesson = lesson;
}
public void learn() {
System.out.println("I need learn " + this.lesson);
}
}
class Test {
public static void main(String[] args) {
Singer singer = new Singer();
// 父类Human实现了接口的主法
singer.run();
singer.say();
// Singer类自己的方法
singer.sing();
System.out.println("\n");
Student student = new Student("Golang");
// 父类Human实现了接口的主法
student.run();
student.say();
// Student类自己的方法
student.learn();
/**
* I can run
* I can say
* I can sing
*
*
* I can run
* I can say
* I need learn Golang
*/
}
}
Java 中 class 通过 implement 显式地声明实现 interface 的所有方法。
但是在 Go 语言中实现接口就不需要使用类似的方式,用go实现
package main
import "fmt"
// interface
type HumanInterface interface {
Say()
Run()
}
type Human struct {
name string
age int
}
// 实现 Human 接口 方法 run 与 say
func (h Human) Say() {
fmt.Printf("I can say, I am %s, %d years old\n", h.name, h.age)
}
func (h Human) Run() {
fmt.Printf("%s is running\n", h.name)
}
type Singer struct {
Human
collecton string
}
func (s Singer) Sing() {
fmt.Printf("%s can sing %s\n", s.name, s.collecton)
}
type Student struct{
Human
lesson string
}
func (s Student) Learn() {
fmt.Printf("%s nee learn %s\n", s.name, s.lesson)
}
func main() {
hailun := Singer{Human{"海伦", 18}, "《桥边姑娘》"}
tom := Student{Human{"Tom", 18}, "《Go从入门到放弃》"}
human := Human{"我是人类", 26}
var men HumanInterface
fmt.Println("\n------hailun-----")
// men 存 hailun
men = hailun
men.Say()
men.Run()
// men.Sing() 不能调用 Sing() Men 没有 Sing()
fmt.Println("\n------tom-----")
// men 存 tom
men = tom
men.Say()
men.Run()
// men.Learn() 同理
fmt.Println("\n------human-----")
// men 存 human
men = human
men.Say()
men.Run()
fmt.Println("\n------x-----")
x := make([]HumanInterface, 3)
// 这三个都是不同类型的元素,但是他们实现了 interface 同一个接口
x[0], x[1], x[2] = hailun, tom, human
for _, value := range x{
value.Say()
value.Run()
}
}
/*
------hailun-----
I can say, I am 海伦, 18 years old
海伦 is running
------tom-----
I can say, I am Tom, 18 years old
Tom is running
------human-----
I can say, I am 我是人类, 26 years old
我是人类 is running
------x-----
I can say, I am 海伦, 18 years old
海伦 is running
I can say, I am Tom, 18 years old
Tom is running
I can say, I am 我是人类, 26 years old
我是人类 is running
*/
Go 接口
Go 语言中的接口是一组方法的签名,它是 Go 语言的重要组成部分。
定义接口需要使用 interface
关键字,在接口中我们只能定义方法签名,不能包含成员变量。
一个常见的 Go 语言接口是这样的:
定义一个error接口,并定义了Error()方法
type error interface {
Error() string
}
如果一个类型需要实现 error 接口,那么它只需要实现 Error() string 方法。
下面的 RPCError 结构体就是 error 接口的一个实现
type RPCError struct {
Code int64
Message string
}
func (e *RPCError) Error() string {
return fmt.Sprintf("%s, code=%d", e.Message, e.Code)
}
心的读者可能会发现上述代码根本就没有 error 接口的影子,这是为什么呢?
Go 语言中接口的实现都是隐式的,我们只需要实现 Error() string 方法就实现了 error 接口。
Go 语言实现接口的方式与 Java 完全不同:
- 在 Java 中:实现接口需要显式地声明接口并实现所有方法;
- 在 Go 中:实现接口的所有方法就隐式地实现了接口;
Go 语言中的两种接口
- 使用 runtime.iface 表示第一种接口
- 使用 runtime.eface 表示第二种不包含任何方法的接口 interface{}
两种接口都使用 interface 声明,但是由于后者在 Go 语言中很常见,所以在实现时使用了特殊的类型。
需要注意的是,与 C 语言中的 void 不同,interface{} 类型*不是任意类型。如果我们将类型转换成了 interface{} 类型,变量在运行期间的类型也会发生变化,获取变量类型时会得到 interface{}。
多态实现
import "fmt"
//定义接口类型
type Humaner interface {
sayHello()
}
type Teacher struct {
addr string
group string
}
type Student struct {
name string
id int
}
//Student实现了此方法
func (tmp *Student) sayHello() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}
//Teacher实现了此方法
func (tmp *Teacher) sayHello() {
fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
}
type MyStr string
//MyStr实现了此方法
func (tmp *MyStr) sayHello() {
fmt.Printf("MyStr[%s] sayhi\n", *tmp)
}
//定义一个普通函数,函数的参数为接口类型
//只有一个函数,可以有不同表现,多态
func WhoSayHi(i Humaner) {
i.sayHello()
}
func main() {
s := &Student{"mike", 666}
t := &Teacher{"bj", "go"}
var str MyStr = "hello mike"
//调用同一函数,不同表现,多态,多种形态
WhoSayHi(s)
WhoSayHi(t)
WhoSayHi(&str)
////创建一个切片
//x := make([]Humaner, 3)
//x[0] = s
//x[1] = t
//x[2] = &str
//
////第一个返回下标,第二个返回下标所对应的值
//for _, i := range x {
// i.sayHello()
//}
}
package main
import "fmt"
/**
Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。
interface类型默认是一个指针。
*/
type Car interface {
NameGet() string
Run(n int)
Stop()
}
type BMW struct {
Name string
}
func (this *BMW) NameGet() string {
return this.Name
}
func (this *BMW) Run(n int) {
fmt.Printf("BMW is running of num is %d \n", n)
}
func (this *BMW) Stop() {
fmt.Println("BMW is stop \n")
}
func (this *BMW) ChatUp() {
fmt.Printf("ChatUp \n")
}
type Benz struct {
Name string
}
func (this *Benz) NameGet() string {
return this.Name
}
func (this *Benz) Run(n int) {
fmt.Printf("Benz is running of num is %d \n", n)
}
func (this *Benz) Stop() {
fmt.Printf("Benz is stop \n")
}
func (this *Benz) ChatUp() {
fmt.Printf("ChatUp \n")
}
func main() {
var car Car
fmt.Println(car) //<nil>
// 多态 一种事物的多种形态,都可以按照统一的接口进行操作。
var bmw BMW = BMW{Name:"宝马"}
car = &bmw
fmt.Println(car.NameGet()) //宝马
car.Run(1) //BMW is running of num is 1
car.Stop() //BMW is stop
benz := &Benz{Name:"大奔"}
car = benz
fmt.Println(car.NameGet()) //大奔
car.Run(2) //Benz is running of num is 2
car.Stop() //Benz is stop
//car.ChatUp() //ERROR: car.ChatUp undefined (type Car has no field or method ChatUp)
benz.ChatUp() //ChatUp
}
值接收者和指针接收者实现接口的区别
接口的另一个微妙之处是接口定义没有规定一个实现者是否应该使用一个指针接收器或一个值接收器来实现接口
值接收者实现接口
package main
import "fmt"
type Mover interface {
move()
}
type dog struct {}
// 值接收者实现接口
func (d dog) move() {
fmt.Println("狗会动")
}
func main() {
var m Mover
var wangcai = dog{} // 旺财是dog类型
m = wangcai // m可以接收dog类型
m.move() // 狗会动
var fugui = &dog{} // 富贵是*dog类型
m=fugui // m可以接收*dog类型
m.move() // 狗会动
}
从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是 dog结构体
还是结构体指针*dog类型
的变量都可以赋值给该接口变量。
因为Go语言中有对指针类型变量求值的语法糖,dog指针 fugui 内部会自动求值 *fugui
。
指针接收者实现接口
package main
import "fmt"
type Mover interface {
move()
}
type dog struct {}
// 指针接收者实现接口
func (d *dog) move() {
fmt.Println("狗会动")
}
func main() {
var m Mover
// 指针接收者实现接口 只能接收地址,所以此处会报错
//var wangcai = dog{} // 旺财是dog类型
//m = wangcai // m可以接收dog类型
//m.move() // 狗会动
var fugui = &dog{} // 富贵是*dog类型
m=fugui // m可以接收*dog类型
m.move() // 狗会动
}
此时实现Mover接口的是 *dog
类型,所以不能给m
传入 dog类型
的 wangcai,此时 m
只能存储 *dog
类型的值。
空接口 interface{}
- 空接口没有定义任何方法
- 所以任何类型都实现了空接口
- 可以存储任意类型的数值
它有点类似于C语言的void *类型
var v1 interface{} = 1 // 将int类型赋值给interface{}
var v2 interface{} = "abc" // 将string类型赋值给interface{}
var v3 interface{} = &v2 // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
当函数可以接受任意的对象实例时,我们会将其声明为interface{}
最典型的例子是标准库fmt中PrintXXX系列的函数,例如:
func Printf(fmt string, args ...interface{})
func Println(args ...interface{})
package main
import "fmt"
func print(data interface{}){
fmt.Printf("data type: %T, data value: %v, \n", data, data)
}
func printf(data ...interface{}){
fmt.Printf("data type: %T, data value: %v, \n", data, data)
}
func main() {
printf(123, "Who am I")
//空接口万能类型,保存任意类型的值
var i interface{} = 1
print(i)
i = "abc"
print(i)
print([...]int{1, 2, 3})
print([...]string{"Go"})
print([]int{10, 20, 30})
print(make([]int,3,5))
print(struct {}{})
print(struct {
Name string
Age int
}{"Tom", 18})
/*
data type: []interface {}, data value: [123 Who am I],
data type: int, data value: 1,
data type: string, data value: abc,
data type: [3]int, data value: [1 2 3],
data type: [1]string, data value: [Go],
data type: []int, data value: [10 20 30],
data type: []int, data value: [0 0 0],
data type: struct {}, data value: {},
data type: struct { Name string; Age int }, data value: {Tom 18},
*/
}
类型断言
Golang 中,接口变量的动态类型是变化的,有时我们需要知道一个接口 变量 的动态 类型 究竟是什么?
这就需要使用类型断言,类型断言就是对接口变量的类型进行检查。
那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
- comma-ok断言
- switch测试
comma-ok断言
语法
value, ok := i.(T)
参数
参数 | 描述 |
---|---|
i | interface变量 |
T | 要断言的类型 |
- 类型 T 可以为任意一个非接口类型,或者一个任意接口类型。
- 在一个类型断言表达式 i.(T) 中, i 称为断言值, T 称为断言类型。 一个断言可能成功或者失败。
返回值
| 返回值 | 描述 | | —- | —- | | value | 转换后的 数据 | | ok | 转换成功与否的 bool |
说明
- 将接口 i 转换成 T 类型。
- 如果转换成功,返回转换成功后的值,即 value,ok 为 true
-
类型T说明
如果 T 是具体某个类型,类型断言会检查 i 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 i 的动态值,其类型是 T。
- 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,i 的动态值不会被提取,返回值是一个类型为 T 的接口值。
- 无论 T 是什么类型,如果 i 是 nil 接口值,类型断言都会失败。
package main
import "fmt"
type Student struct {
name string
age int
}
func main() {
list := make([]interface{}, 3)
list[0] = 100 //int
list[1] = "Golang" //string
list[2] = Student{"Ueumd", 18} //Student
//类型查询,类型断言
for index, data := range list {
if value, ok := data.(int); ok == true {
fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
} else if value, ok := data.(string); ok == true {
fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
} else if value, ok := data.(Student); ok == true {
fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
}
}
/*
list[0]=100, type: int
list[1]=Golang, type: string
list[2]={Ueumd 18}, type: main.Student
*/
}
switch测试
for index, data := range list {
switch value := data.(type) {
case int:
fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
case string:
fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
case Student:
fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
}
}
/**
list[0]=100, type: int
list[1]=Golang, type: string
list[2]={Ueumd 18}, type: main.Student
*/