Original address: medium.com/golangspec/…
Key points:
- An interface is a collection of methods
- A single type can implement multiple methods
- The same interface can be implemented by multiple types
- The interface declaration can embed other interfaces, import all methods of the embedded interface (exportable methods + non-exportable methods), and import all layers of embedded interfaces into the interface declaration
- Disallow loop embedding of interfaces
- Method names within the interface must be unique: Custom methods and embedded interfaces contain methods with unique names
- Interface variables can hold values of all types that implement the interface: abstract theoretical implementations
- Static VS dynamic typing: Variables of an interface type can be assigned to each other by the types of the interface, dynamic typing
- Interface type variables: dynamic type, dynamic value. A variable of this interface type is nil only if both are nil
- Empty interface: Any type of variable can be hosted, or any type can implement (satisfy) an empty interface
- Interface implementation: A type defines all methods that contain an interface declaration (method name + signature)
- A value of an interface type can access only the methods defined by the interface itself, not other variables of the original type: behavior abstraction, associative comparison to polymorphisms in Java when subclasses are assigned to a parent class
Keywords: Interface, type, method, function, method signature, Exported method, satisfy, implement, etc.
Interface makes code more flexible and extensible, and is the realization of polymorphism in Golang. Interfaces allow you to specify that only certain behaviors are required, rather than specifying a specific type. A behavior is defined as a collection of methods, as shown in code snippet 1:
type I interface {
f1(name string)
f2(name string) (error, float32)
f3() int64
}
Copy the code
No mandatory interface implementation is required. We say that a type implements or satisfies an interface, as long as it defines the method name and signature (input and return parameters) that the interface expects, as shown in section 2:
type T int64 func (T) f1(name string) { fmt.Println(name) } func (T) f2(name string) (error, float32) { return nil, 10.2} func (T) f3() int64 {return 10}Copy the code
In this example, type T satisfies interface I defined in code section 1, and the value of type T can be passed as an argument to any method that receives interface I. Code snippet 3:
type I interface { M() string } type T struct { name string } func (t T) M() string { return t.name } func Hello(i I) { Printf("Hi, my name is %s\n", i.M())} func main() {Hello(T{name: "Michał"}) // "Hi, my name is Michał"}Copy the code
In the Hello function, the method call i.M() is implemented in a specific way: different types of methods can be called when the method is an implementation of a type-satisfied interface. Golang’s important feature: The interface is implemented implicitly, and the programmer does not need to show that the declaration type T implements interface I. This is done automatically by the Go compiler (never ask a human to do what a machine should do). The elegant implementation of this behavior makes it possible to define an interface that is automatically implemented by already written types (with no modifications to previously completed types). Note: This language-level feature automatically implements the new interface if the existing type is not modified when the interface is added. This polymorphic approach has great flexibility.
This feature provides great flexibility for interfaces: a single type can implement multiple interfaces, sample code
type I1 interface { M1() } type I2 interface { M2() } type T struct{} func (T) M1() { fmt.Println("T.M1") } // Type T implements the interface I1 func (T) M2() {fmt.println (" t.m1 ")} // Type T implements the interface I2 func f1(ii1) {i.m1 ()} func f2(ii2) {i.m1 ()} func main() { t := T{} f1(t) // "T.M1" f2(t) // "T.M2" }Copy the code
Or the same interface can be implemented by multiple types, sample code
Type I interface {M()} type T1 struct{} func (T1) M() {ftt.println (" t1.m ")} M () {FMT. Println (" T2. M ")} / / type T2 implements the interface I func f (I) I {i.M ()} func main () {f (T1 {}) / / "T1. M" f (T2 {}) / / T2. The "M"}Copy the code
In addition to the methods required by one or more interfaces, types are free to implement different methods.
In Golang, there are two concepts about interfaces:
- Interface: The set of methods necessary to implement this Interface, by keyword
interface
Definition. - Interface Type: The Interface type variable can hold the type value that implements any particular Interface.
These two concepts are discussed separately below.
Define an interface
Interface definition: methods, embedded other interfaces
The declaration of the interface specifies the methods that belong to the interface, and the method definition is done by its name and signature (input and return parameters), as follows:
Type I interface {// Define an interface I that contains four methods m1() m2(int) m3(int) int m4() int}Copy the code
In addition to containing methods, embedded interfaces are also allowed – in this case, the interface imports all methods of the embedded interface into its own definition, as shown in the following example:
import "fmt"
type I interface {
m1()
}
type J interface {
m2()
I
fmt.Stringer
}
Copy the code
Interface J contains the following method set:
- M1 () – from the embedded interface I
- M2 () – Custom method
- String () – came fromfmt.StringerThe String() method of the interface (the only method in this interface)
The order of methods in an interface does not matter, so interleaving methods and embedded interfaces can occur.
The interface imports all methods of the embedded interface, including exportable methods (uppercase methods) and non-exported methods (lowercase methods).
Inline interface When multiple levels of inline, import all methods that contain the interface
If interface I is embedded with interface J, and interface K is embedded with other interface K, all methods of interface K will also be added to the declaration of interface I, as follows:
type I interface {
J
i()
}
type J interface {
K
j()
}
type K interface {
k()
}
Copy the code
Then interface I contains methods: I (), j(), k()
Disallow loop embedding of interfaces
Circular embedding of interfaces is disallowed and will be detected while compilation (source code): Error “interface type loop involving” is forbidden, and errors are detected during compilation.
type I interface {
J
i()
}
type J interface {
K
j()
}
type K interface {
k()
I
}
Copy the code
The method name within the interface must be unique
Duplicate method I: duplicate method id: duplicate method id: duplicate method id: duplicate method id
type I interface {
J
i()
}
type J interface {
j()
i(int)
}
Copy the code
This form of interface assembly runs through various library definitions, an example of IO.ReaderWriter:
type ReadWriter interface {
Reader
Writer
}
Copy the code
Now that we know how to create an interface, let’s look at values of interface types.
Variable of the interface type
A variable of type interface I can hold any value that implements interface I, as shown in the following example:
Struct {} func (T) method1() {} func main() {var I I = T{} If T implements interface I, I can store the value of a variable of type T fmt.println (I)}.Copy the code
Static versus dynamic typing
The existing type of a variable is specified at compile time, specified at declaration time, and never changed. This situation is called static type or simply type. Variables of interface type also have a static type, the interface itself; In addition, they have dynamic types, which are assignable types. Sample code:
Type I struct {} func (T1) M() {} type I struct {} func (T2) M() { Func main() {var I I = T1{} Printf("%T\n", I) // output main.T1 I = T2{} fmt.Printf("%T\n", I) // Output main.T2 _ = I}Copy the code
The static type of variable I is interface I, and that doesn’t change. The dynamic type, on the other hand, is dynamically variable. After the first assignment, the dynamic type of I is of type T1, but this is not fixed. The second assignment changes the dynamic type of I to type T2. Dynamic type is not set when the variable value of the interface type is nil (zero value of the interface is nil).
How do I get the dynamic type of an interface type variable
-
The Reflect package provides methods to get dynamic types. Example code: The Reflect package will report runtime error when the variable is nil.
fmt.Println(reflect.TypeOf(i).PkgPath(), reflect.TypeOf(i).Name()) fmt.Println(reflect.TypeOf(i).String()) Copy the code
-
The FMT package can also get the dynamic type of a variable by formatting the verb %T: while FMT is also implemented using Reflect, it can also be supported when the variable I is nil.
fmt.Printf("%T\n", i) Copy the code
Interface type nil
Take a look at the following code example:
Type I interface {M()} type T struct {} func (T) M() {} func main() {var T *T // T must be nil if T == nil Else {fmt.Println("t is not nil")} var I I = t // t is null, but I? if i == nil { fmt.Println("i is nil") } else { fmt.Println("i is not nil") } }Copy the code
Output result:
t is nil
i is not nil
Copy the code
The value assigned to variable I is nil, but I is not nil. The interface type variable contains two parts:
- Dynamic Type
- Dynamic value
Dynamic typing was described in the previous section [Dynamic typing VS Static typing].
The dynamic value is the value to which the actual variable is actually assigned, in the example abovevar i I = t
The dynamic value of the variable I is nil, but the dynamic type of I is*T
.
throughfmt.Printf("%T\n", i)
The dynamic type of variable I after output assignment is*main.T
.The interface type variable is nil if and only if both the dynamic type and dynamic value are nil.
In this case, the interface variable is not nil even though it holds a nil pointer.
Known errors are returning uninitialized, non-interface variable values from functions that should return interface types,The sample code:Type I interface {} type T struct {} func F() I {// function F should return interface type I, in this case the interface type return value = return type *T, Var t * t if false {// not reachable but it actually sets value t = &t {}} return t // here returns the variable t is nil} func The main () {FMT. Printf (" % F () = v \ n ", F ()) / / return parameter dynamic value is nil FMT) Printf (" F () is nil: %v\n", F() == nil) fmt.printf ("type of F(): %T", F()))Copy the code
Printout:
F() = <nil>
F() is nil: false
type of F(): *main.T
Copy the code
Just because interface type value returned from function has dynamic value set (* main.t), it isn’t equal to nil.
Because the function returns an interface value of dynamic type *main.T, it does not equal nil
The empty interface
The method set of the interface can be completely empty.
type I interface {}
type T struct {}
func (T) M() {}
func main() {
var i I = T{}
_ = i
}
Copy the code
Empty interfaces are automatically implemented by any type, so any type can be assigned to variables of this empty interface type. A dynamic or static type of an empty interface behaves the same as a non-empty interface. FMT.Println function variable parameter hollow interface is widely used. TODO: How do you implement mutable parameters?
Implementing an interface
Implementation methods Any type of all methods automatically satisfies (implements) this interface. You don’t need to show which interface a declaration type implements, as you do in Java. The Go compiler automatically detects the implementation of the type-pair interface, which is a powerful feature of Golang at the language level. Sample code:
Import (" FMT ""regexp") type I interface {Find(b []byte) []byte // interface I contains method Find, and regexp implementation contains method implementation (method name + signature), Func f(I I) {FMT.Printf("%s\n", Int ([]byte(" ABC ")))} func main() {var re = regexp.MustCompile(' b ')}Copy the code
Here we define an interface I, without modifying the built-in regexp module, such that the: regexp.Regexp type implements interface I.
- A type can implement multiple interfaces, and an interface can be implemented by multiple types
- An interface that implements an interface and assigns a value to the interface type can access only the methods defined by the interface itself
Abstraction of interface type behavior
Interface type values can only access methods of that interface type, which hide other values contained in the original type, such as structs, arrays, Scalar, and so on. Sample code:
type I interface { M1() } type T int64 func (T) M1() {} func (T) M2() {} func main() { var i I = T(10) // M1() method i.m1 () i.m2 () // i.m2 undefined (type I has no field or method M2)}Copy the code
Read more
- The Go Programming Language Specification – The Go Programming Language
- research! rsc: Go Data Structures: Interfaces
- How to use interfaces in Go