隐式接口

Java中接口

Java 的接口不仅可以定义方法签名,还可以定义变量,这些定义的变量可以直接在实现接口的类中使用,这里简单介绍一下 Java 中的接口:

  1. // 定义 Human 接口有 run 跟 say 两个方法
  2. interface HumanInterface {
  3. public void run();
  4. public void say();
  5. }
  6. // 实现 Human 接口 必须实现所有方法 run 与 say
  7. class Human implements HumanInterface {
  8. public int age;
  9. public String name;
  10. public void run() {
  11. System.out.println("I can run");
  12. }
  13. public void say() {
  14. System.out.println("I can say");
  15. }
  16. }
  17. // 歌手
  18. class Singer extends Human {
  19. public String collection; // 专辑
  20. public void sing() {
  21. System.out.println("I can sing");
  22. }
  23. }
  24. // 学生
  25. class Student extends Human {
  26. public String lesson;
  27. public Student(String lesson){
  28. this.lesson = lesson;
  29. }
  30. public void learn() {
  31. System.out.println("I need learn " + this.lesson);
  32. }
  33. }
  34. class Test {
  35. public static void main(String[] args) {
  36. Singer singer = new Singer();
  37. // 父类Human实现了接口的主法
  38. singer.run();
  39. singer.say();
  40. // Singer类自己的方法
  41. singer.sing();
  42. System.out.println("\n");
  43. Student student = new Student("Golang");
  44. // 父类Human实现了接口的主法
  45. student.run();
  46. student.say();
  47. // Student类自己的方法
  48. student.learn();
  49. /**
  50. * I can run
  51. * I can say
  52. * I can sing
  53. *
  54. *
  55. * I can run
  56. * I can say
  57. * I need learn Golang
  58. */
  59. }
  60. }

Java 中 class 通过 implement 显式地声明实现 interface 的所有方法。

但是在 Go 语言中实现接口就不需要使用类似的方式,用go实现

  1. package main
  2. import "fmt"
  3. // interface
  4. type HumanInterface interface {
  5. Say()
  6. Run()
  7. }
  8. type Human struct {
  9. name string
  10. age int
  11. }
  12. // 实现 Human 接口 方法 run 与 say
  13. func (h Human) Say() {
  14. fmt.Printf("I can say, I am %s, %d years old\n", h.name, h.age)
  15. }
  16. func (h Human) Run() {
  17. fmt.Printf("%s is running\n", h.name)
  18. }
  19. type Singer struct {
  20. Human
  21. collecton string
  22. }
  23. func (s Singer) Sing() {
  24. fmt.Printf("%s can sing %s\n", s.name, s.collecton)
  25. }
  26. type Student struct{
  27. Human
  28. lesson string
  29. }
  30. func (s Student) Learn() {
  31. fmt.Printf("%s nee learn %s\n", s.name, s.lesson)
  32. }
  33. func main() {
  34. hailun := Singer{Human{"海伦", 18}, "《桥边姑娘》"}
  35. tom := Student{Human{"Tom", 18}, "《Go从入门到放弃》"}
  36. human := Human{"我是人类", 26}
  37. var men HumanInterface
  38. fmt.Println("\n------hailun-----")
  39. // men 存 hailun
  40. men = hailun
  41. men.Say()
  42. men.Run()
  43. // men.Sing() 不能调用 Sing() Men 没有 Sing()
  44. fmt.Println("\n------tom-----")
  45. // men 存 tom
  46. men = tom
  47. men.Say()
  48. men.Run()
  49. // men.Learn() 同理
  50. fmt.Println("\n------human-----")
  51. // men 存 human
  52. men = human
  53. men.Say()
  54. men.Run()
  55. fmt.Println("\n------x-----")
  56. x := make([]HumanInterface, 3)
  57. // 这三个都是不同类型的元素,但是他们实现了 interface 同一个接口
  58. x[0], x[1], x[2] = hailun, tom, human
  59. for _, value := range x{
  60. value.Say()
  61. value.Run()
  62. }
  63. }
  64. /*
  65. ------hailun-----
  66. I can say, I am 海伦, 18 years old
  67. 海伦 is running
  68. ------tom-----
  69. I can say, I am Tom, 18 years old
  70. Tom is running
  71. ------human-----
  72. I can say, I am 我是人类, 26 years old
  73. 我是人类 is running
  74. ------x-----
  75. I can say, I am 海伦, 18 years old
  76. 海伦 is running
  77. I can say, I am Tom, 18 years old
  78. Tom is running
  79. I can say, I am 我是人类, 26 years old
  80. 我是人类 is running
  81. */

Go 接口

Go 语言中的接口是一组方法的签名,它是 Go 语言的重要组成部分。

定义接口需要使用 interface 关键字,在接口中我们只能定义方法签名,不能包含成员变量

一个常见的 Go 语言接口是这样的:

定义一个error接口,并定义了Error()方法

  1. type error interface {
  2. Error() string
  3. }

如果一个类型需要实现 error 接口,那么它只需要实现 Error() string 方法。

下面的 RPCError 结构体就是 error 接口的一个实现

  1. type RPCError struct {
  2. Code int64
  3. Message string
  4. }
  5. func (e *RPCError) Error() string {
  6. return fmt.Sprintf("%s, code=%d", e.Message, e.Code)
  7. }

心的读者可能会发现上述代码根本就没有 error 接口的影子,这是为什么呢?
Go 语言中接口的实现都是隐式的,我们只需要实现 Error() string 方法就实现了 error 接口。

Go 语言实现接口的方式与 Java 完全不同:

  • 在 Java 中:实现接口需要显式地声明接口并实现所有方法;
  • 在 Go 中:实现接口的所有方法就隐式地实现了接口;

Go 语言中的两种接口

  1. 使用 runtime.iface 表示第一种接口
  2. 使用 runtime.eface 表示第二种不包含任何方法的接口 interface{}

两种接口都使用 interface 声明,但是由于后者在 Go 语言中很常见,所以在实现时使用了特殊的类型。

需要注意的是,与 C 语言中的 void 不同,interface{} 类型*不是任意类型。如果我们将类型转换成了 interface{} 类型,变量在运行期间的类型也会发生变化,获取变量类型时会得到 interface{}。

多态实现

  1. import "fmt"
  2. //定义接口类型
  3. type Humaner interface {
  4. sayHello()
  5. }
  6. type Teacher struct {
  7. addr string
  8. group string
  9. }
  10. type Student struct {
  11. name string
  12. id int
  13. }
  14. //Student实现了此方法
  15. func (tmp *Student) sayHello() {
  16. fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
  17. }
  18. //Teacher实现了此方法
  19. func (tmp *Teacher) sayHello() {
  20. fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
  21. }
  22. type MyStr string
  23. //MyStr实现了此方法
  24. func (tmp *MyStr) sayHello() {
  25. fmt.Printf("MyStr[%s] sayhi\n", *tmp)
  26. }
  27. //定义一个普通函数,函数的参数为接口类型
  28. //只有一个函数,可以有不同表现,多态
  29. func WhoSayHi(i Humaner) {
  30. i.sayHello()
  31. }
  32. func main() {
  33. s := &Student{"mike", 666}
  34. t := &Teacher{"bj", "go"}
  35. var str MyStr = "hello mike"
  36. //调用同一函数,不同表现,多态,多种形态
  37. WhoSayHi(s)
  38. WhoSayHi(t)
  39. WhoSayHi(&str)
  40. ////创建一个切片
  41. //x := make([]Humaner, 3)
  42. //x[0] = s
  43. //x[1] = t
  44. //x[2] = &str
  45. //
  46. ////第一个返回下标,第二个返回下标所对应的值
  47. //for _, i := range x {
  48. // i.sayHello()
  49. //}
  50. }
  1. package main
  2. import "fmt"
  3. /**
  4. Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。
  5. interface类型默认是一个指针。
  6. */
  7. type Car interface {
  8. NameGet() string
  9. Run(n int)
  10. Stop()
  11. }
  12. type BMW struct {
  13. Name string
  14. }
  15. func (this *BMW) NameGet() string {
  16. return this.Name
  17. }
  18. func (this *BMW) Run(n int) {
  19. fmt.Printf("BMW is running of num is %d \n", n)
  20. }
  21. func (this *BMW) Stop() {
  22. fmt.Println("BMW is stop \n")
  23. }
  24. func (this *BMW) ChatUp() {
  25. fmt.Printf("ChatUp \n")
  26. }
  27. type Benz struct {
  28. Name string
  29. }
  30. func (this *Benz) NameGet() string {
  31. return this.Name
  32. }
  33. func (this *Benz) Run(n int) {
  34. fmt.Printf("Benz is running of num is %d \n", n)
  35. }
  36. func (this *Benz) Stop() {
  37. fmt.Printf("Benz is stop \n")
  38. }
  39. func (this *Benz) ChatUp() {
  40. fmt.Printf("ChatUp \n")
  41. }
  42. func main() {
  43. var car Car
  44. fmt.Println(car) //<nil>
  45. // 多态 一种事物的多种形态,都可以按照统一的接口进行操作。
  46. var bmw BMW = BMW{Name:"宝马"}
  47. car = &bmw
  48. fmt.Println(car.NameGet()) //宝马
  49. car.Run(1) //BMW is running of num is 1
  50. car.Stop() //BMW is stop
  51. benz := &Benz{Name:"大奔"}
  52. car = benz
  53. fmt.Println(car.NameGet()) //大奔
  54. car.Run(2) //Benz is running of num is 2
  55. car.Stop() //Benz is stop
  56. //car.ChatUp() //ERROR: car.ChatUp undefined (type Car has no field or method ChatUp)
  57. benz.ChatUp() //ChatUp
  58. }

值接收者和指针接收者实现接口的区别

接口的另一个微妙之处是接口定义没有规定一个实现者是否应该使用一个指针接收器或一个值接收器来实现接口

值接收者实现接口

  1. package main
  2. import "fmt"
  3. type Mover interface {
  4. move()
  5. }
  6. type dog struct {}
  7. // 值接收者实现接口
  8. func (d dog) move() {
  9. fmt.Println("狗会动")
  10. }
  11. func main() {
  12. var m Mover
  13. var wangcai = dog{} // 旺财是dog类型
  14. m = wangcai // m可以接收dog类型
  15. m.move() // 狗会动
  16. var fugui = &dog{} // 富贵是*dog类型
  17. m=fugui // m可以接收*dog类型
  18. m.move() // 狗会动
  19. }

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是 dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量。

因为Go语言中有对指针类型变量求值的语法糖,dog指针 fugui 内部会自动求值 *fugui

指针接收者实现接口

  1. package main
  2. import "fmt"
  3. type Mover interface {
  4. move()
  5. }
  6. type dog struct {}
  7. // 指针接收者实现接口
  8. func (d *dog) move() {
  9. fmt.Println("狗会动")
  10. }
  11. func main() {
  12. var m Mover
  13. // 指针接收者实现接口 只能接收地址,所以此处会报错
  14. //var wangcai = dog{} // 旺财是dog类型
  15. //m = wangcai // m可以接收dog类型
  16. //m.move() // 狗会动
  17. var fugui = &dog{} // 富贵是*dog类型
  18. m=fugui // m可以接收*dog类型
  19. m.move() // 狗会动
  20. }

此时实现Mover接口的是 *dog类型,所以不能给m传入 dog类型 的 wangcai,此时 m只能存储 *dog类型的值。

空接口 interface{}

  • 空接口没有定义任何方法
  • 所以任何类型都实现了空接口
  • 可以存储任意类型的数值

它有点类似于C语言的void *类型

  1. var v1 interface{} = 1 // 将int类型赋值给interface{}
  2. var v2 interface{} = "abc" // 将string类型赋值给interface{}
  3. var v3 interface{} = &v2 // 将*interface{}类型赋值给interface{}
  4. var v4 interface{} = struct{ X int }{1}
  5. var v5 interface{} = &struct{ X int }{1}

当函数可以接受任意的对象实例时,我们会将其声明为interface{}
最典型的例子是标准库fmt中PrintXXX系列的函数,例如:

  1. func Printf(fmt string, args ...interface{})
  2. func Println(args ...interface{})
  1. package main
  2. import "fmt"
  3. func print(data interface{}){
  4. fmt.Printf("data type: %T, data value: %v, \n", data, data)
  5. }
  6. func printf(data ...interface{}){
  7. fmt.Printf("data type: %T, data value: %v, \n", data, data)
  8. }
  9. func main() {
  10. printf(123, "Who am I")
  11. //空接口万能类型,保存任意类型的值
  12. var i interface{} = 1
  13. print(i)
  14. i = "abc"
  15. print(i)
  16. print([...]int{1, 2, 3})
  17. print([...]string{"Go"})
  18. print([]int{10, 20, 30})
  19. print(make([]int,3,5))
  20. print(struct {}{})
  21. print(struct {
  22. Name string
  23. Age int
  24. }{"Tom", 18})
  25. /*
  26. data type: []interface {}, data value: [123 Who am I],
  27. data type: int, data value: 1,
  28. data type: string, data value: abc,
  29. data type: [3]int, data value: [1 2 3],
  30. data type: [1]string, data value: [Go],
  31. data type: []int, data value: [10 20 30],
  32. data type: []int, data value: [0 0 0],
  33. data type: struct {}, data value: {},
  34. data type: struct { Name string; Age int }, data value: {Tom 18},
  35. */
  36. }

类型断言

Golang 中,接口变量的动态类型是变化的,有时我们需要知道一个接口 变量 的动态 类型 究竟是什么?

这就需要使用类型断言,类型断言就是对接口变量的类型进行检查。

那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

  • comma-ok断言
  • switch测试

comma-ok断言

语法

  1. value, ok := i.(T)

参数

参数 描述
i interface变量
T 要断言的类型
  • 类型 T 可以为任意一个非接口类型,或者一个任意接口类型。
  • 在一个类型断言表达式 i.(T) 中, i 称为断言值, T 称为断言类型。 一个断言可能成功或者失败。

    返回值

    | 返回值 | 描述 | | —- | —- | | value | 转换后的 数据 | | ok | 转换成功与否的 bool |

说明

  • 将接口 i 转换成 T 类型。
  • 如果转换成功,返回转换成功后的值,即 value,ok 为 true
  • 如果转换失败,value 为 零值,ok 为 false

    类型T说明

  • 如果 T 是具体某个类型,类型断言会检查 i 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 i 的动态值,其类型是 T。

  • 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,i 的动态值不会被提取,返回值是一个类型为 T 的接口值。
  • 无论 T 是什么类型,如果 i 是 nil 接口值,类型断言都会失败。
  1. package main
  2. import "fmt"
  3. type Student struct {
  4. name string
  5. age int
  6. }
  7. func main() {
  8. list := make([]interface{}, 3)
  9. list[0] = 100 //int
  10. list[1] = "Golang" //string
  11. list[2] = Student{"Ueumd", 18} //Student
  12. //类型查询,类型断言
  13. for index, data := range list {
  14. if value, ok := data.(int); ok == true {
  15. fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
  16. } else if value, ok := data.(string); ok == true {
  17. fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
  18. } else if value, ok := data.(Student); ok == true {
  19. fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
  20. }
  21. }
  22. /*
  23. list[0]=100, type: int
  24. list[1]=Golang, type: string
  25. list[2]={Ueumd 18}, type: main.Student
  26. */
  27. }

switch测试

  1. for index, data := range list {
  2. switch value := data.(type) {
  3. case int:
  4. fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
  5. case string:
  6. fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
  7. case Student:
  8. fmt.Printf("list[%d]=%v, type: %T\n", index, value, value)
  9. }
  10. }
  11. /**
  12. list[0]=100, type: int
  13. list[1]=Golang, type: string
  14. list[2]={Ueumd 18}, type: main.Student
  15. */

参考
https://segmentfault.com/a/1190000038552939