Go和Java的异同(面向对象和错误处理)

一、面向对象
1.Java是纯正的面向对象语言,Go相对于Java而言,就比较简洁,没有例如类的继承、接口的实现、构造函数和析构函数、隐藏的 this 指针等,也没有 public、protected、private 之类的访问修饰符。
2.类型系统
类型系统是指一个语言的类型体系结构。一个典型的类型系统通常包含如下基本内容:

  • 基本类型,如 byte、int、bool、float、string 等;
  • 复合类型,如数组、切片、字典、指针、结构体等;
  • 可以指向任意对象的类型(Any 类型);
  • 值语义和引用语义;
  • 面向对象,即所有具备面向对象特征(比如成员方法)的类型;
  • 接口。
    类型系统描述的是这些内容在一个语言中如何被关联。
    (1)Java中存在两套完全独立的类型系统:值类型系统和以 Object 类型为根的对象类型系统(类)。
    Java 语言中的 Any 类型就是整个对象类型系统的根 —— java.lang.Object 类型,只有对象类型系统中的实例才可以被 Any 类型引用(值类型需要装箱)。
    (2)Go 语言中的大多数类型都是值语义,包括:
  • 基本类型,如布尔类型、整型、浮点型、字符串等;
  • 复合类型,如数组、结构体等(切片、字典、指针和通道都是引用语义);
    所有值语义类型都支持定义成员方法,包括内置基本类型。不过在此之前,需要将基本类型通过 type 关键字设置为新的类型,类似Java的装箱功能,将基本的数字类型转化为面向对象类型。
    (3)Go的接口实现,只需要实现该接口要求的所有方法即可,无需显式声明实现的接口(实际上,Go 语言根本就不支持传统面向对象编程中的继承和实现语法)。
    (4)任何类型都可以被 Any 类型引用。在 Go 语言中,Any 类型就是空接口,即 interface{}。
    3.类相关的实现
    (1)Go 语言的面向对象编程与Java 完全不同,没有 class、extends、implements 之类的关键字和相应的概念,而是借助结构体来实现类的声明。
    (2)Go 语言中也不支持构造函数、析构函数,取而代之地,可以通过定义形如 NewXXX 这样的全局函数(首字母大写)作为类的初始化函数。
    (3)在 Go 语言中,未进行显式初始化的变量都会被初始化为该类型的零值,例如 bool 类型的零值为 false,int 类型的零值为 0,string 类型的零值为空字符串,float 类型的零值为 0.0。
    (4)由于 Go 语言不支持 class 这样的代码块,要为 Go 类定义成员方法,需要在 func 和方法名之间声明方法所属的类型(有的地方将其称之为接收者声明)
    在这里插入图片描述
    (5)在类的成员方法中,可以通过声明的类型变量来访问类的属性和其他方法(Go 语言不支持隐藏的 this 指针,所有的东西都是显式声明)。
    在这里插入图片描述
    这里的set方法与get不同,需要传入指针,是因为SetXXX 方法需要在函数内部修改成员变量的值,并且该修改要作用到该函数作用域以外,所以需要传入指针类型(结构体是值类型,不是引用类型,所以需要显式传入指针)。
    (6)以把接收者类型为指针的成员方法叫做指针方法,把接收者类型为非指针的成员方法叫做值方法,二者的区别在于值方法传入的结构体变量是值类型(类型本身为指针类型除外),因此传入函数内部的是外部传入结构体实例的值拷贝,修改不会作用到外部传入的结构体实例。
    一个自定义数据类型的方法集合中仅会包含它的所有「值方法」,而该类型对应的指针类型包含的方法集合才囊括了该类型的所有方法,包括所有「值方法」和「指针方法」,指针方法可以修改所属类型的属性值,而值方法则不能。
    (7)Java 支持默认调用类的 toString 方法以字符串格式打印类的实例,Go 语言也有类似的String()方法。
    4.Go通过组合实现类的继承和方法的重写
    (1)严格来说,Go 语言并不是一门面向对象编程语言,至少不是面向对象编程的最佳选择(Java 才是最根正苗红的),不过我们可以基于它提供的一些特性来模拟实现面向对象编程。要实现面向对象编程,就必须实现面向对象编程的三大特性:封装、继承和多态。
    (2)封装:将函数定义为归属某个自定义类型,这就等同于实现了类的成员方法,如果这个自定义类型是基于结构体的,那么结构体的字段可以看做是类的属性。
    (3) Go 虽然没有直接提供继承相关的语法实现,但是我们通过组合的方式间接实现类似功能,所谓组合,就是将一个类型嵌入到另一个类型,从而构建新的类型结构。更灵活,没有Java单继承的限制。
    还可以通过任意调整被组合类型的位置来改变类的内存布局。
    (4)多态:可以通过在子类中定义同名方法来覆盖父类方法的实现,在面向对象编程中这一术语叫做方法重写。只不过 Go 语言不同于 Java,没有专门提供引用父类实例的关键字(super、parent 等)。
    这种同一个方法在不同情况下具有不同的表现方式,就是多态。
    (5)在 Go 语言中,还可以以指针方式继承某个类型的属性和方法。
    当我们通过组合实现类之间的继承时,由于结构体实例本身是值类型,如果传入值字面量的话,实际上传入的是结构体实例的副本,对内存耗费更大,所以组合指针类型性能更好。
    5.类属性和成员方法的可见性
    (1)Java提供了四个关键字修饰属性和方法的可见性,分别是private(类私有)、默认(包私有)、protected(包和子类私有)和public(公共)。
    (2)Go没有类似Java的权限关键字,Go 语言基于包为单位组织和管理源码,因此变量、类属性、函数、成员方法的可见性都是基于包这个维度的。不管是变量、函数,还是自定义类的属性和成员方法,它们的可见性都是根据其首字母的大小写来决定的(大写-包外可访问,小写-仅包内可见)。
    6.Go语言的接口和Java的接口概念完全不同。Java中,接口作为不同类之间强制实现的契约存在。Java是单继承的语言,无论是类与类的继承还是类与接口的实现,都有着严格的层级关系,而且必须显式地声明。这种接口叫做侵入式接口。
    Go语言中,一个类只要实现了某个接口要求的所有方法,我们就说这个类实现了该接口(没有显式声明)。这种接口叫做非侵入式接口。
    Go中的接口继承与类的继承类似,也是通过组合来完成的。
    Go中的接口只包含方法,不包含任何属性。Java中的接口除了抽象方法还有常量。
    7.在Go和Java中,接口都不支持直接实例化,不同的是,Go接口支持赋值操作,从而快速实现接口与实现类的映射,与之相比,Java 要实现接口与实现类的映射,只能基于 IoC 容器通过依赖注入实现,要复杂得多。
    接口赋值在 Go 语言中分为如下两种情况:
  • 将实现接口的类实例赋值给接口;
  • 将一个接口赋值给另一个接口。
    (1)如果类中实现接口的成员方法都是值方法,则进行接口赋值时,传递类实例的值类型或者指针类型均可,否则只能传递指针类型实例,从代码性能角度来说,值拷贝需要消耗更多的内存空间,统一使用指针类型代码性能会更好。
    在这里插入图片描述
    在这里插入图片描述

(2)在 Go 语言中,只要两个接口拥有相同的方法列表(与顺序无关),那么它们就是等同的,可以相互赋值。不过,这里有一个前提,那就是接口变量持有的是基于对应实现类的实例值,所以接口与接口间的赋值是基于类实例与接口间的赋值的。
在这里插入图片描述
此外,接口赋值并不要求两个接口完全等价(方法完全相同)。如果接口 A 的方法列表是接口 B 的方法列表的子集,那么接口 B 也可以赋值给接口 A。
8.类型断言
(1)Java提供了 instanceof 关键字来进行接口和类型的断言,这种断言其实就是判定一个对象是否是某个类(包括父类)或接口的实例。
(2)Go没有提供类似的关键字,而是通过类型断言运算符 .(type) 来实现,其中 type 对应的就是要断言的类型。
注意:在 Go 语言结构体类型断言时,子类的实例并不归属于父类,即使子类和父类属性名和成员方法列表完全一致,因为类与类之间的「继承」是通过组合实现的,并不是 Java中的父子继承关系。父类实现了某个接口,不代表组合类它的子类也实现了这个接口。(其实这里已经和传统的面向对象编程中的父子类完全不是一个概念了,其本质原因就是 Go 使用了组合而非继承来构建类与类之间的关联和层次关系。)
(3)Go还可以基于反射在运行时动态进行类型断言,使用 reflect 包提供的 TypeOf 函数即可实现。
对于基本数据类型,比如 int、string、bool 这些,不必通过反射,直接使用 variable.(type) 表达式即可获取 variable 变量对应的类型值
9.空接口
(1)Java号称血统最纯正的面向对象编程语言中,「万事万物皆对象」,所有类都继承自Object类型,所以 Object 类型变量可以指向任何类的实例。
(2)Go 语言打破了传统面向对象编程中类与类之间继承的概念,而是通过组合实现方法和属性的复用,所以不存在类似的继承关系树,也就没有所谓的祖宗类,而且类与接口之间也不再通过 implements 关键字强制绑定实现关系,所以 Go 语言的面向对象编程非常灵活。
在 Go 语言中,类与接口的实现关系是通过类所实现的方法在编译期推断出来的,如果我们定义一个空接口的话,那么显然所有的类都实现了这个接口,反过来,我们也可以通过空接口来指向任意类型,从而实现类似 Java 中 Object 类所承担的功能,而且显然 Go 的空接口实现更加简洁,通过一个简单的字面量即可完成:
在这里插入图片描述
注意:空接口和接口零值不是一个概念,前者是 interface{},后者是 nil。
10.反射泛型
(1)Java支持反射,最典型的应用场景就是IOC容器。
(2)Go 也支持反射功能,并且专门提供了一个 reflect 包用于提供反射相关的 API。reflect 包提供的两个最常用、最重要的类型就是 reflect.Type 和 reflect.Value。前者用于表示变量的类型,后者用于存储任何类型的值,分别可以通过 reflect.TypeOf 和 reflect.ValueOf 函数获取。
(3)Java自1.5之后支持泛型。
(4)Go没有在语言层面支持泛型,可以通过空接口结合反射实现。
二、 错误处理

1.error类型
(1) Java中Throwable是所有错误或异常的超类,其下又分为Error和Exception两个大的分类。Error错误,表示Java系统内部和资源耗尽的错误,不会抛出该类异常,只会告知用户然后终止程序运行。Exception又分作运行时异常RuntimeException和检查异常CheckedException,运行时异常是正常抛出的错误,而检查异常是外部错误,需要程序去捕获。
(2)Go 语言为错误处理定义了一个标准模式,即 error 接口,其中只声明了一个 Error() 方法,用于返回字符串类型的错误消息。:
在这里插入图片描述
关于自定义并返回 error 类型错误信息,可以通过 Go 标准错误包 errors 提供的 New() 方法快速创建一个 error 类型的错误实例。

2.defer语句
Go中的defer语句相当于Java中的final语句,不同的是,defer语句的个数没有限制,而且最后执行时按照后入先出的顺序执行所有的defer语句。
defer语句要写在方法最前面,防止后面的代码中断执行后程序感知不到。
defer语句后也可以加一个匿名函数来执行复杂的语句。
在这里插入图片描述

3.panic和recover
当代码运行时出错,而又没有在编码时显式返回错误时,Go 语言会抛出 panic,类似Java的RuntimeException.
无论是 Go 语言底层抛出 panic,还是我们在代码中显式抛出 panic,处理机制都是一样的:当遇到 panic 时,Go 语言会中断当前协程(即 main 函数)后续代码的执行,然后执行在中断代码之前定义的 defer 语句(按照先入后出的顺序),最后程序退出并输出 panic 错误信息,以及出现错误的堆栈跟踪信息。
还可以通过 recover() 函数对 panic 进行捕获和处理,从而避免程序崩溃然后直接退出,而是继续可以执行后续代码,实现类似 Java中 try…catch 语句的功能。
可以类比为 panic、recover、defer 组合起来实现了传统面向对象编程异常处理的 try…catch…finally 功能。

你可能感兴趣的:(Go)