Java转Go全过程02-面向对象编程部分

Java开发已经是红海一片,面临着35岁危机的压力,需要适时的调整策略,以应对可能会出现的不确定性。毕竟,命运掌握在自己手里,比掌握在公司手里 安全感会强很多。

尝试的其中一条路即为:Java转Go,海外开发web3相关,也有其他的尝试,会开辟相应的专栏收录。

针对每一个部分,都会在最后准备练习题,可以多做几遍,用于巩固知识,多动手!

后续golang语言学习过程中产生的全部内容,会发布在 web3这个专栏中。

Go语言基础快速突破[面向对象编程](1-2周)

1. 类型系统

1.1 为类型添加方法

在Go语言中,方法是作用在特定类型上的函数。通过func关键字和接收者(receiver)定义,接收者可以是任何用户定义的类型。

示例代码

go 复制代码
type MyInt int

// 方法定义
func (myInt MyInt) Double() int {
return int(myInt * 2)
}

func main() {
a := MyInt(5)
fmt.Println(a.Double()) // 输出: 10
}

注意事项

  • 方法必须定义在与类型相同的包中。
  • 方法可以定义在结构体上,也可以定义在其他用户自定义类型上。

1.2 值语义和引用语义

Go语言中,类型的方法可以是指针接收者或值接收者。这决定了方法调用时的参数传递方式。

值语义

值接收者在方法调用时会复制整个对象。

go 复制代码
type Point struct{ X, Y int }

func (p Point) Move(dx, dy int) {
p.X += dx
p.Y += dy
}

func main() {
p := Point{1, 2}
p.Move(2, 3)
fmt.Println(p) // 输出: {1 2}
}

引用语义

指针接收者在方法调用时只复制指针(地址)。

go 复制代码
type Point struct{ X, Y int }

func (p *Point) Move(dx, dy int) {
p.X += dx
p.Y += dy
}

func main() {
p := &Point{1, 2}
p.Move(2, 3)
fmt.Println(p) // 输出: &{3 5}
}

何时使用

  • 如果方法需要修改接收者的值,或者接收者是一个大对象,推荐使用指针接收者。
  • 如果方法不需要修改接收者,且接收者是小对象,使用值接收者即可。

1.3 结构体

Go语言中的结构体(struct)是复合数据类型,用于组合不同类型的字段。结构体可以拥有方法,支持嵌入(embedding),从而实现类似继承的效果。

定义结构体

go 复制代码
type Person struct {
FirstName string
LastName  string
Age       int
}

初始化结构体

go 复制代码
p := Person{
FirstName: "John",
LastName:  "Doe",
Age:       30,
}

// 或者
p := new(Person)
p.FirstName = "John"
p.LastName = "Doe"
p.Age = 30

结构体的方法

go 复制代码
func (p Person) FullName() string {
return p.FirstName + " " + p.LastName
}

func (p *Person) SetAge(age int) {
p.Age = age
}

嵌入(Embedding)

Go语言通过嵌入实现类似继承的功能,但更灵活。

go 复制代码
type Animal struct {
Name string
}

func (a Animal) Speak() {
fmt.Println("Hello, I am", a.Name)
}

type Dog struct {
Animal // 嵌入Animal
Breed string
}

func main() {
dog := Dog{
Animal: Animal{Name: "Buddy"},
Breed:  "Golden Retriever",
}
dog.Speak() // 输出: Hello, I am Buddy
}

结构体的可见性

  • 首字母大写表示该字段或方法是导出的(public)。
  • 首字母小写表示该字段或方法是非导出的(private)。
go 复制代码
type Person struct {
PublicField  string // 导出
privateField string // 非导出
}

匿名结构体

匿名结构体没有名字,通常用于临时场景。

go 复制代码
p := struct {
FirstName string
LastName  string
}{"John", "Doe"}

fmt.Println(p.FirstName, p.LastName) // 输出: John Doe

2. 初始化

Go语言提供了多种初始化结构体的方式,每种方式都有其适用场景。

字段名初始化

使用字段名进行初始化,这是最清晰的方式。

go 复制代码
type Person struct {
FirstName string
LastName  string
Age       int
}

// 字段名初始化
p1 := Person{
FirstName: "John",
LastName:  "Doe",
Age:       30,
}

顺序初始化

按照结构体字段定义的顺序进行初始化。

go 复制代码
// 顺序初始化(必须按字段顺序)
p2 := Person{"Jane", "Smith", 25}

零值初始化

结构体的零值是所有字段的零值。

go 复制代码
// 零值初始化
var p3 Person
fmt.Println(p3) // 输出: { 0}

new函数初始化

使用new函数创建结构体指针。

go 复制代码
// new函数初始化
p4 := new(Person)
p4.FirstName = "Bob"
p4.LastName = "Johnson"
p4.Age = 35

3. 匿名组合

Go语言通过嵌入实现代码复用,这是一种组合而非继承的方式。

基本嵌入

将一个结构体嵌入到另一个结构体中。

go 复制代码
type Animal struct {
Name string
Age  int
}

func (a Animal) Describe() string {
return fmt.Sprintf("I am %s, %d years old", a.Name, a.Age)
}

type Dog struct {
Animal // 嵌入Animal结构体
Breed string
}

func main() {
dog := Dog{
Animal: Animal{Name: "Buddy", Age: 3},
Breed:  "Golden Retriever",
}

fmt.Println(dog.Describe()) // 输出: I am Buddy, 3 years old
fmt.Println(dog.Name)       // 直接访问嵌入字段
}

方法提升

嵌入结构体的方法会被提升到外层结构体。

go 复制代码
type Cat struct {
Animal
Color string
}

func (c Cat) Meow() {
fmt.Println("Meow!")
}

func main() {
cat := Cat{
Animal: Animal{Name: "Whiskers", Age: 2},
Color:  "Orange",
}

cat.Describe() // 调用Animal的方法
cat.Meow() // 调用Cat自己的方法
}

字段冲突处理

当嵌入的结构体有相同字段名时,需要明确指定。

go 复制代码
type Base struct {
Name string
}

type Derived1 struct {
Base
Name string // 与Base.Name冲突
}

type Derived2 struct {
Base
Name string
}

func main() {
d1 := Derived1{
Base: Base{Name: "BaseName"},
Name: "DerivedName",
}

fmt.Println(d1.Name) // 输出: DerivedName
fmt.Println(d1.Base.Name)   // 输出: BaseName
}

4. 可见性

Go语言通过大小写来控制字段和方法的可见性。

导出字段(Public)

首字母大写的字段和方法可以被其他包访问。

go 复制代码
// person.go
package main

type Person struct {
	FirstName string // 导出字段
	LastName  string // 导出字段
	age       int    // 非导出字段
}

func (p Person) GetAge() int { // 导出方法
	return p.age
}

func (p *Person) setAge(age int) { // 非导出方法
	p.age = age
}

非导出字段(Private)

首字母小写的字段和方法只能在当前包内访问。

go 复制代码
// 在同一个包内可以访问非导出字段
func (p *Person) SetAge(age int) {
p.age = age // 可以访问非导出字段
}

跨包访问示例

go 复制代码
// main.go
package main

import "otherpackage"

func main() {
	p := otherpackage.Person{
		FirstName: "John", // 可以访问
		LastName:  "Doe",  // 可以访问
		// age: 30,        // 编译错误:无法访问非导出字段
	}

	age := p.GetAge() // 可以访问导出方法
	// p.setAge(30)    // 编译错误:无法访问非导出方法
}

5. 接口

Go语言的接口是一种抽象类型,定义了对象的行为规范。

5.1 其他语言的接口

Java接口

Java中的接口是显式声明的,类必须明确实现接口。

java 复制代码
interface Animal {
    void speak();
}

class Dog implements Animal {
    public void speak() {
        System.out.println("Woof!");
    }
}

Go接口

Go语言的接口是隐式实现的,只要类型实现了接口的所有方法,就自动实现了该接口。

go 复制代码
type Animal interface {
Speak()
}

type Dog struct{}

func (d Dog) Speak() {
fmt.Println("Woof!")
}

// Dog自动实现了Animal接口,无需显式声明

5.2 非侵入式接口

Go语言的接口设计是非侵入式的,这意味着:

接口定义

接口只定义方法签名,不包含实现。

go 复制代码
type Reader interface {
Read(p []byte) (n int, err error)
}

type Writer interface {
Write(p []byte) (n int, err error)
}

隐式实现

类型不需要显式声明实现了某个接口。

go 复制代码
type File struct {
name string
}

func (f *File) Read(p []byte) (n int, err error) {
// 实现读取逻辑
return len(p), nil
}

func (f *File) Write(p []byte) (n int, err error) {
// 实现写入逻辑
return len(p), nil
}

// File自动实现了Reader和Writer接口

优势

  • 解耦:接口定义者和实现者可以独立演化
  • 灵活性:可以为任何类型定义接口
  • 可测试性:易于进行单元测试

5.3 接口赋值

接口变量可以存储任何实现了该接口的类型值。

基本赋值

go 复制代码
type Animal interface {
Speak()
}

type Dog struct{}

func (d Dog) Speak() {
fmt.Println("Woof!")
}

type Cat struct{}

func (c Cat) Speak() {
fmt.Println("Meow!")
}

func main() {
var animal Animal

animal = Dog{}
animal.Speak() // 输出: Woof!

animal = Cat{}
animal.Speak() // 输出: Meow!
}

接口作为参数

go 复制代码
func MakeSpeak(a Animal) {
a.Speak()
}

func main() {
MakeSpeak(Dog{}) // 输出: Woof!
MakeSpeak(Cat{}) // 输出: Meow!
}

接口切片

go 复制代码
func main() {
animals := []Animal{Dog{}, Cat{}, Dog{}}

for _, animal := range animals {
animal.Speak()
}
// 输出:
// Woof!
// Meow!
// Woof!
}

5.4 接口查询

接口查询用于检查接口变量中存储的具体类型。

类型断言

go 复制代码
type Animal interface {
Speak()
}

type Dog struct{}

func (d Dog) Speak() {
fmt.Println("Woof!")
}

func (d Dog) Fetch() {
fmt.Println("Fetching ball...")
}

func main() {
var animal Animal = Dog{}

// 类型断言
if dog, ok := animal.(Dog); ok {
dog.Fetch() // 输出: Fetching ball...
}

// 类型断言失败
if cat, ok := animal.(Cat); !ok {
fmt.Println("animal is not a Cat")
}
}

类型开关

go 复制代码
func describeAnimal(a Animal) {
switch v := a.(type) {
case Dog:
fmt.Println("This is a Dog")
v.Fetch()
case Cat:
fmt.Println("This is a Cat")
default:
fmt.Printf("Unknown animal type: %T\n", v)
}
}

5.5 类型查询

使用反射进行类型查询。

反射类型查询

go 复制代码
import "reflect"

func main() {
var animal Animal = Dog{}

// 使用反射获取类型
t := reflect.TypeOf(animal)
fmt.Println("Type:", t)

// 检查是否实现了某个接口
if t.Implements(reflect.TypeOf((*Animal)(nil)).Elem()) {
fmt.Println("animal implements Animal interface")
}
}

5.6 接口组合

接口可以通过组合形成更复杂的接口。

基本组合

go 复制代码
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 ReadWriter interface {
Reader
Writer
}

type ReadWriteCloser interface {
Reader
Writer
Closer
}

实际应用

go 复制代码
type File struct {
name string
}

func (f *File) Read(p []byte) (n int, err error) {
return len(p), nil
}

func (f *File) Write(p []byte) (n int, err error) {
return len(p), nil
}

func (f *File) Close() error {
return nil
}

func main() {
var file ReadWriteCloser = &File{"test.txt"}

// file可以用于任何需要Reader、Writer或Closer的地方
data := make([]byte, 100)
file.Read(data)
file.Write(data)
file.Close()
}

5.7 Any类型

Go 1.18引入了泛型,any类型是interface{}的别名。

基本用法

go 复制代码
func PrintAny(value any) {
fmt.Printf("Value: %v, Type: %T\n", value, value)
}

func main() {
PrintAny(42)      // Value: 42, Type: int
PrintAny("hello") // Value: hello, Type: string
PrintAny(Dog{}) // Value: {}, Type: main.Dog
}

泛型函数

go 复制代码
func Min[T any](a, b T) T {
// 这里需要类型约束,any类型无法比较
return a
}

// 使用类型约束
func Min[T comparable](a, b T) T {
if a < b {
return a
}
return b
}

func main() {
fmt.Println(Min(3, 5)) // 3
fmt.Println(Min("a", "b")) // a
}

类型约束

go 复制代码
type Number interface {
~int | ~float64
}

func Sum[T Number](numbers []T) T {
var sum T
for _, n := range numbers {
sum += n
}
return sum
}

func main() {
ints := []int{1, 2, 3, 4, 5}
fmt.Println(Sum(ints)) // 15

floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(floats)) // 6.6
}

面向对象编程部分练习题

go 复制代码
package main

import (
	"errors"
	"fmt"
	"reflect"
)

// 3.1 类型系统

// 3.1.1 为类型添加方法
// Exercise: 定义一个名为 Counter 的整数类型,并为其添加 Increment 和 Decrement 方法。
type Counter int

func (c *Counter) Increment() {
	*c++
}

func (c *Counter) Decrement() {
	*c--
}

func exercise3_1_1() {
	var counter Counter = 5
	counter.Increment()
	fmt.Printf("After increment: %d\n", counter) // 应输出 6
	counter.Decrement()
	fmt.Printf("After decrement: %d\n", counter) // 应输出 5
}

// 3.1.2 值语义和引用语义
// Exercise: 定义一个结构体 Point,并实现两个版本的 Move 方法:一个使用值接收者,另一个使用指针接收者。
type Point struct {
	X, Y int
}

func (p Point) MoveByValue(dx, dy int) Point {
	p.X += dx
	p.Y += dy
	return p
}

func (p *Point) MoveByPointer(dx, dy int) {
	p.X += dx
	p.Y += dy
}

func exercise3_1_2() {
	p1 := Point{1, 2}
	p2 := p1.MoveByValue(2, 3)
	fmt.Printf("After MoveByValue: p1=%v, p2=%v\n", p1, p2) // p1不变,p2变化

	p3 := &Point{1, 2}
	p3.MoveByPointer(2, 3)
	fmt.Printf("After MoveByPointer: p3=%v\n", *p3) // p3变化
}

// 3.1.3 结构体
// Exercise: 定义一个结构体 Person,包含 FirstName, LastName 和 Age 字段,并添加 FullName 和 SetAge 方法。
type Person struct {
	FirstName string
	LastName  string
	Age       int
}

func (p Person) FullName() string {
	return p.FirstName + " " + p.LastName
}

func (p *Person) SetAge(age int) {
	if age < 0 {
		panic("age cannot be negative")
	}
	p.Age = age
}

func exercise3_1_3() {
	p := Person{"John", "Doe", 30}
	fmt.Println("Full Name:", p.FullName()) // John Doe
	p.SetAge(35)
	fmt.Println("Age after SetAge:", p.Age) // 35
}

// 3.2 初始化
// Exercise: 使用不同的方式初始化结构体 Student,包括字面量、new、& 和构造函数。
type Student struct {
	Name  string
	Grade int
}

func NewStudent(name string, grade int) *Student {
	return &Student{Name: name, Grade: grade}
}

func exercise3_2() {
	// 字面量
	s1 := Student{"Alice", 90}
	fmt.Println("s1:", s1)

	// new
	s2 := new(Student)
	s2.Name = "Bob"
	s2.Grade = 85
	fmt.Println("s2:", *s2)

	// &
	s3 := &Student{"Charlie", 95}
	fmt.Println("s3:", *s3)

	// 构造函数
	s4 := NewStudent("David", 88)
	fmt.Println("s4:", *s4)
}

// 3.3 匿名组合
// Exercise: 定义一个结构体 Employee,嵌入 Person,并添加 Department 字段。
type Employee struct {
	Person
	Department string
}

func exercise3_3() {
	e := Employee{
		Person:     Person{"Jane", "Smith", 28},
		Department: "Engineering",
	}
	fmt.Println("Employee Name:", e.FullName()) // 继承 Person 的方法
	fmt.Println("Department:", e.Department)
}

// 3.4 可见性
// Exercise: 定义一个结构体 BankAccount,包含私有字段 balance 和公有字段 AccountNumber。
type BankAccount struct {
	AccountNumber string
	balance       float64 // 私有字段
}

func NewBankAccount(accountNumber string, initialBalance float64) (*BankAccount, error) {
	if initialBalance < 0 {
		return nil, errors.New("initial balance cannot be negative")
	}
	return &BankAccount{AccountNumber: accountNumber, balance: initialBalance}, nil
}

func (ba *BankAccount) Deposit(amount float64) error {
	if amount <= 0 {
		return errors.New("deposit amount must be positive")
	}
	ba.balance += amount
	return nil
}

func (ba *BankAccount) Withdraw(amount float64) error {
	if amount <= 0 {
		return errors.New("withdrawal amount must be positive")
	}
	if amount > ba.balance {
		return errors.New("insufficient funds")
	}
	ba.balance -= amount
	return nil
}

func (ba *BankAccount) Balance() float64 {
	return ba.balance
}

func exercise3_4() {
	ba, err := NewBankAccount("123456789", 1000)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Account: %s, Balance: $%.2f\n", ba.AccountNumber, ba.Balance())

	err = ba.Deposit(500)
	if err != nil {
		panic(err)
	}
	fmt.Printf("After deposit: $%.2f\n", ba.Balance())

	err = ba.Withdraw(200)
	if err != nil {
		panic(err)
	}
	fmt.Printf("After withdrawal: $%.2f\n", ba.Balance())
}

// 3.5 接口

// 3.5.1 其他语言的接口
// Go 接口是隐式实现的,与其他语言不同。

// 3.5.2 非侵入式接口
// Exercise: 定义一个接口 Speaker,包含 Speak 方法。让 Dog 和 Cat 类型实现该接口。
type Speaker interface {
	Speak() string
}

type Dog struct{}

func (d Dog) Speak() string {
	return "Woof!"
}

type Cat struct{}

func (c Cat) Speak() string {
	return "Meow!"
}

func exercise3_5_2() {
	var speaker Speaker

	speaker = Dog{}
	fmt.Println("Dog says:", speaker.Speak())

	speaker = Cat{}
	fmt.Println("Cat says:", speaker.Speak())
}

// 3.5.3 接口赋值
// Exercise: 将具体类型的变量赋值给接口变量。
func exercise3_5_3() {
	var speaker Speaker
	dog := Dog{}
	cat := Cat{}

	speaker = dog
	fmt.Println("Speaker (dog):", speaker.Speak())

	speaker = cat
	fmt.Println("Speaker (cat):", speaker.Speak())
}

// 3.5.4 接口查询
// Exercise: 查询接口变量是否实现了某个接口。
func exercise3_5_4() {
	var speaker Speaker = Dog{}
	if _, ok := speaker.(Dog); ok {
		fmt.Println("speaker is a Dog")
	}
	if _, ok := speaker.(Cat); ok {
		fmt.Println("speaker is a Cat")
	} else {
		fmt.Println("speaker is not a Cat")
	}
}

// 3.5.5 类型查询
// Exercise: 使用类型断言查询接口变量的具体类型。
func exercise3_5_5() {
	var speaker Speaker = Dog{}
	switch v := speaker.(type) {
	case Dog:
		fmt.Printf("speaker is a Dog: %T\n", v)
	case Cat:
		fmt.Printf("speaker is a Cat: %T\n", v)
	default:
		fmt.Println("speaker is unknown")
	}
}

// 3.5.6 接口组合
// Exercise: 定义两个接口 Walker 和 Talker,并组合成一个新的接口 Actor。
type Walker interface {
	Walk() string
}

type Talker interface {
	Talk() string
}

type Actor interface {
	Walker
	Talker
}

type Human struct{}

func (h Human) Walk() string {
	return "Walking..."
}

func (h Human) Talk() string {
	return "Talking..."
}

func exercise3_5_6() {
	var actor Actor = Human{}
	fmt.Println("Actor walks:", actor.Walk())
	fmt.Println("Actor talks:", actor.Talk())
}

// 3.5.7 Any 类型
// Exercise: 使用 interface{} 存储不同类型的值,并演示类型断言。
func exercise3_5_7() {
	var any interface{}

	any = 42
	fmt.Printf("any is int: %d\n", any.(int))

	any = "Hello"
	fmt.Printf("any is string: %s\n", any.(string))

	any = 3.14
	fmt.Printf("any is float64: %.2f\n", any.(float64))

	// 使用类型查询
	switch v := any.(type) {
	case int:
		fmt.Printf("any is int: %d\n", v)
	case string:
		fmt.Printf("any is string: %s\n", v)
	case float64:
		fmt.Printf("any is float64: %.2f\n", v)
	default:
		fmt.Println("any is unknown")
	}
}

// 主函数:运行所有练习
func main() {
	fmt.Println("=== 3.1.1 为类型添加方法 ===")
	exercise3_1_1()

	fmt.Println("\n=== 3.1.2 值语义和引用语义 ===")
	exercise3_1_2()

	fmt.Println("\n=== 3.1.3 结构体 ===")
	exercise3_1_3()

	fmt.Println("\n=== 3.2 初始化 ===")
	exercise3_2()

	fmt.Println("\n=== 3.3 匿名组合 ===")
	exercise3_3()

	fmt.Println("\n=== 3.4 可见性 ===")
	exercise3_4()

	fmt.Println("\n=== 3.5.2 非侵入式接口 ===")
	exercise3_5_2()

	fmt.Println("\n=== 3.5.3 接口赋值 ===")
	exercise3_5_3()

	fmt.Println("\n=== 3.5.4 接口查询 ===")
	exercise3_5_4()

	fmt.Println("\n=== 3.5.5 类型查询 ===")
	exercise3_5_5()

	fmt.Println("\n=== 3.5.6 接口组合 ===")
	exercise3_5_6()

	fmt.Println("\n=== 3.5.7 Any 类型 ===")
	exercise3_5_7()
}
相关推荐
天才首富科学家5 小时前
后端(15)-微信支付后的notify回调收不到
spring boot·后端
zjjuejin5 小时前
Dockerfile 指令全解析:从基础到高阶实践
后端·docker
Cache技术分享5 小时前
178. Java 包
前端·javascript·后端
阴晦5 小时前
llm与RAG的学习与优化
后端
心月狐的流火号5 小时前
详解Java内存模型(JMM)
java·后端
就叫飞六吧5 小时前
企业级主流日志系统架构对比ELKK Stack -Grafana Stack
后端·ubuntu·系统架构
CryptoRzz5 小时前
使用Spring Boot对接印度股票市场API开发实践
后端
River4165 小时前
Javer 学 c++(九):结构体篇
c++·后端
日月卿_宇5 小时前
分布式事务
java·后端