Go: Methods and interfaces
Method
A method is a function with a special receiver argument.
func (v Vertex) Abs() float64{
return math.Sqrt(v.X*v.X + v.Y*v.Y)
// In this example, the `Abs` method has a receiver of type `Vertex` named `v`
}
// is equivalent to (in terms of functionality)
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// You can declare a method on non-struct types, too.
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
// You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).
Pointer receiver
The receiver of a method can be a pointer. This means the receiver type has the literal syntax *T
for some type T
. (Also, T
cannot itself be a pointer such as *int
.)
type Vertex struct {
X, Y float64
}
// golang pointer receiver method
func (v* Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f // recall that v.X is the same as dereferencing (*v).X
}
// it can be written as, again, a function
func ScaleFunc(v* Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
Method with pointer receiver versus function with a pointer argument
There is one difference between method with pointer receiver and a function with pointer argument.
// func with ptr arg
var v Vertex
ScaleFunc(v, 5) // compile error
ScaleFunc(&v, 5) // works
// method with ptr receiver
var v Vertex
p := &v
p.Scale(5) // works
v.Scale(5) // also works; auto-referencing
That is, as a convenience, Go interprets the statement v.Scale(5)
as (&v).Scale(5)
since the Scale
method has a pointer receiver. This won’t work for normal functions.
The equivalent thing happens in the reverse direction. The same thing happens when you have a method with value receiver and a function with a value argument.
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// func with val arg
var v Vertex
fmt.Println(AbsFunc(v)) // works
fmt.Println(AbsFunc(&v)) // compile error
// method with val receiver
var v Vertex
fmt.Println(v.Abs()) // works
p := &v
fmt.Println(p.Abs()) // also works; (*p).Abs(); auto-dereferencing
In this case, the method call p.Abs()
is interpreted as(*p).Abs()
.
Choosing a value or pointer receiver
Two reasons to use a pointer receiver:
- Method with a pointer receiver can modify the value that its receiver points to.
- Avoid copying the value on each method call. This can be more efficient if the receiver is a large struct.
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
In this example, even though the Abs
method needn’t modify its receiver, it is with receiver type *Vertex
.
In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.
Interfaces
Interfaces basic
An interface type is defined as a set of method signatures.
A value of interface type can hold any value that implements those methods.
type Abser interface {
Abs() float64
}
func main() {
var a1 Abser // a value of Abs() interface type
var a2 Abser // another value of Abs() interface type
f := MyFloat(-1.414) // default constructor
v := Vertex{3, 4}
a1 = f // a MyFloat implements Abser
a2 = &v // a *Vertex implements Abser
a2 = v // invalid! Vertex (the value type) doesn't implement Abser because the Abs method is defined only on *Vertex (the pointer type).
fmt.Println(a1.Abs())
fmt.Println(a2.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
Interfaces are implemented implicitly
A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword.
Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.
Interface values
Under the hood, interface values can be thought of as a tuple of a value and a concrete type:(value, type)
An interface value holds a value of a specific underlying concrete type.
Calling a method on an interface value executes the method of the same name on its underlying type.
Interface values with nil underlying values
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main() {
var i I
var t *T
i = t
describe(i)
i.M() // If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
i = &T{"hello"}
describe(i) // Note that an interface value that holds a nil concrete value is itself non-nil.
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the method M
in this example).
A nil
interface value holds neither value nor concrete type.
Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.
type I interface {
M()
}
func main() {
var i I
describe(i)
i.M() // panic: runtime error: invalid memory address or nil pointer dereference, i is nil itself!
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
The empty interface
The interface type that specifies zero methods is known as the empty interface:interface{}
An empty interface may hold values of any type. (Every type implements at least zero methods).
func main() {
var i interface{}
describe(i)
i = 42
describe(i)
i = "hello"
describe(i)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
Empty interfaces are used by code that handles values of unknown type. For example, fmt.Print
takes any number of arguments of type interface{}
.
Type assertions
A type assertion provides access to an interface value’s underlying concrete value.
func main() {
var i interface{} = "hello"
s := i.(string) // type assertion; this statement asserts that the interface value i holds the concrete type string and assigns the underlying string value to the variable s.
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok) // "hello" and true
f, ok := i.(float64)
fmt.Println(f, ok) // 0 and false
f = i.(float64) // panic!
}
Type switch
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
Stringers
One of the most ubiquitous interfaces is Stringer
defined by the fmt
package.
type Stringer interface {
String() string
}
A Stringer
is a type that can describe itself as a string. The fmt
package (and many others) look for this interface to print values.
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
type Stringer interface {
String() string
}
func (ip IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
Errors
Go programs express error state with error
values.
The error
type is a built-in interface similar to fmt.Stringer
:
type error interface {
Error() string
}
As with fmt.Stringer
, the fmt package looks for the error
interface when printing values.
Functions often return an error
value, and calling code should handle errors by testing whether the error equals nil
.
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
A nil error
denotes success; a non-nil error
denotes failure.
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %v\n", float64(e))
}
func Sqrt(x float64) (float64, error) {
if x < 0 {
var e ErrNegativeSqrt = ErrNegativeSqrt(x)
return -1, e
}
return math.Sqrt(x), nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
Readers
package main
import (
"fmt"
"io"
"strings"
)
func NewReader(s string) *Reader // this is from "strings" package. NewReader returns a new Reader reading from s. It is similar to bytes.NewBufferString but more efficient and read-only.
func main)() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8) // init a byte slice of size 8, i.e., 8 bytes
/*
type Reader interface {
Read(p []byte) (n int, err error)
}
Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered.
*/
for {
n, err := r.Read(b)
fmt.Printf("n = %v, err = %v, b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
Skipped concepts: images
Images is skipped for now.
推荐阅读
-
Go: Methods and interfaces
-
spring+jax 出现java.io.Serializable is an interface, and JAXB can't handle interfaces
-
sendmail: fatal: parameter inet_interfaces: no local interface found for ::1
-
postfix报错postfix: fatal: parameter inet_interfaces: no local interface found for ::1
-
【转】Design for performance - Remote interfaces
-
grpc unable to determine Go import path for
-
Go语言metrics应用监控指标基本使用说明
-
go几个小技巧
-
Go语言metrics应用监控指标基本使用说明
-
go语言怎么和mysql数据库进行链接