This is a creation in Article, where the information may have evolved or changed.
Interface
Overview
If Goroutine and channel are the two cornerstones of go concurrency, then the interface is the key to the data type in the Go language programming. In the real programming of the go language, almost all data structures are expanded around the interface, which is the core of all the data structures in the go language.
The interface in the Go language is a collection of methods (method set) that specifies the behavior of the object: if it (any data type) can do these things, then it can be used here.
type Reader interface {Read(p []byte) (n int, err error)}type Writer interface {Write(p []byte) (n int, err error)}type Closer interface {Close() error}type Seeker interface {Seek(offset int64, whence int) (int64, error)}
The above code defines 4 interfaces.
Suppose we define the following structure in another place:
type File struct { // ...}func (f *File) Read(buf []byte) (n int, err error)func (f *File) Write(buf []byte) (n int, err error)func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error
When we implement file, we may not know the existence of the above 4 interfaces, but in any case, file implements all 4 of the above interfaces. We can assign a file object to any of the above interfaces.
var file1 Reader = new(File) var file2 Writer = new(File) var file3 Closer = new(File)var file4 Seeker = new(File)
Because file can do these things, file can be used here. When file is implemented, it is not necessary to specify which interface is implemented, and it does not even know the existence of these 4 interfaces at all. This "loose coupling" approach is completely different from the traditional object-oriented programming.
In fact, the above 4 interfaces come from the IO package in the standard library.
Interface Assignment Value
We can assign an object instance that implements an interface to an interface or assign another interface to an interface.
(1) Assigning values through object instances
Before assigning an object instance to an interface, ensure that the object implements all the methods of the interface. Consider the following example:
type Integer intfunc (a Integer) Less(b Integer) bool {return a < b}func (a *Integer) Add(b Integer) {*a += b}type LessAdder interface { Less(b Integer) bool Add(b Integer)}var a Integer = 1var b1 LessAdder = &a //OKvar b2 LessAdder = a //not OK
B2 's assignment will report a compilation error, why? Remember the rules of the go language discussed in the < Type method > chapter?
The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type T is the set of any methods with receiver T or T (so is, it al So contains the method set of T).
That is to say, *integer implements all the methods of interface Lessadder, and integer only implements the less method, so it cannot be assigned a value.
(2) assigning values via interfaces
var r io.Reader = new(os.File) var rw io.ReadWriter = r //not ok var rw2 io.ReadWriter = new(os.File) var r2 io.Reader = rw2 //ok
Because R does not have a write method, it cannot be assigned to RW.
Interface nesting
Let's take a look at another interface in the IO package:
// ReadWriter is the interface that groups the basic Read and Write methods.type ReadWriter interface {ReaderWriter}
The interface is nested with IO. Reader and Io.writer two interfaces, in fact, it is equivalent to the following:
type ReadWriter interface {Read(p []byte) (n int, err error) Write(p []byte) (n int, err error)}
Note that the interface in the go language cannot be nested recursively,
// illegal: Bad cannot embed itselftype Bad interface {Bad}// illegal: Bad1 cannot embed itself using Bad2type Bad1 interface {Bad2}type Bad2 interface {Bad1}
Null interface (Empty interface)
The null interface is special, and it does not contain any methods:
interface{}
In the go language, all other data types implement an empty interface.
var v1 interface{} = 1var v2 interface{} = "abc"var v3 interface{} = struct{ X int }{1}
If the function intends to receive any data type, the reference can be declared as interface{}. The most typical example is the functions of the print and Fprint series in the standard library FMT package:
func Fprint(w io.Writer, a ...interface{}) (n int, err error) func Fprintf(w io.Writer, format string, a ...interface{})func Fprintln(w io.Writer, a ...interface{})func Print(a ...interface{}) (n int, err error)func Printf(format string, a ...interface{})func Println(a ...interface{}) (n int, err error)
Note that []t cannot be assigned directly to []interface{}
t := []int{1, 2, 3, 4} var s []interface{} = t
The following error is printed at compile time:
Cannot use T (Type []int) as type []interface {} in Assignment
We must do this in the following way:
t := []int{1, 2, 3, 4}s := make([]interface{}, len(t))for i, v := range t { s[i] = v}
Type conversion (Conversions)
Syntax for type conversions:
Conversion = Type "(" Expression [ "," ] ")" .
When starting with operator * or <-, it is necessary to add parentheses to avoid blurring:
*Point(p) // same as *(Point(p))(*Point)(p) // p is converted to *Point<-chan int(c) // same as <-(chan int(c))(<-chan int)(c) // c is converted to <-chan intfunc()(x) // function signature func() x(func())(x) // x is converted to func()(func() int)(x) // x is converted to func() intfunc() int(x) // x is converted to func() int (unambiguous)
Type switch with type assertions
In the go language, we can use the type switch statement to query the actual data type of an interface variable, with the following syntax:
switch x.(type) {// cases}
x must be an interface type.
Take a look at a detailed example:
type Stringer interface { String() string}var value interface{} // Value provided by caller.switch str := value.(type) {case string: return str //type of str is stringcase Stringer: //type of str is Stringer return str.String()}
The value in the statement switch must be an interface type, and the type of the variable str is the converted type.
If The switch declares a variable in the expression, the variable would have the same corresponding type in each clause. It ' s also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different Type in each case.
What if we only care about one type? What if we know the value is a string and just want to extract it? Only one case type of switch is possible, but you can also use Type assertion (assertions). The type assertion accepts an interface value from which to extract the value of the explicitly specified type. Its syntax borrows from the type switch clause, but uses the explicit type instead of the type keyword, as follows:
x.(T)
Similarly, x must be an interface type.
str := value.(string)
There is a problem with the conversion above, and if the value does not contain a string, the program produces a run-time error. To avoid this problem, you can use the "comma, OK" idiom to safely test whether a value is a string:
str, ok := value.(string)if ok { fmt.Printf("string value is: %q\n", str)} else { fmt.Printf("value is not a string\n")}
If the type assertion fails, STR will still exist, and the type is a string, but it is a zero value, which is an empty string.
We can use type assertions to implement the example of type switch:
if str, ok := value.(string); ok { return str} else if str, ok := value.(Stringer); ok { return str.String()}
This practice does not have much practical value.