《码出高效》第二章

10 Aug 2021

Reading time ~28 minutes

面向对象编程(Object-Oriented Programming,OOP)实践了软件工程的三个主要目标:可维护性、可重用性和可扩展性。

2.1 OOP理念

面向过程的代码结构相对松散,强调如何流程化地解决问题;面向对象的思维更加内聚,强调高内聚、低耦合,先抽象模型,定义共性行为,再解决实际问题。

面向对象有三大特征:封装、继承、多态。本书明确将“抽象”作为面向对象的特性之一,支持面向对象“四大特征”的说法。

封装是在抽象基础上决定信息是否公开,以及公开等级,核心问题是以什么样的方式暴露哪些信息。

抽象是要找到属性和行为的共性,属性是行为的基本生产资料,具有一定的敏感性,不能直接对外暴露;封装的主要任务是对属性、数据、部分内容敏感行为实现隐藏。

迪米特法则就是对封装的具体要求,即A模块要使用B模块的某个接口行为,对B模块中除此行为外的其他信息知道得尽可能少。

继承是面向对象技术的基石,允许创建具有逻辑等级结构的类体系,形成一个继承树,让软件在业务多变的客观条件下,某些基础模块可以被直接复用、间接复用或增强复用,父类的能力通过这种方式赋予子类。

里氏替换原则是指任何父类能够出现的地方,子类都能出现。

继承滥用的危害性,即方法污染和方法爆炸。方法污染是指父类具备的行为,通过继承传递给子类,子类并不具备执行此行为的能力。方法爆炸是指继承树不断扩大,底层类拥有的方法虽然都能够执行,但是由于方法众多,其中部分方法并非与当前类的功能定位相关,很容易在实际编程中产生选择困难症。

提倡组合优先原则来扩展类的能力,即优先采用组合或聚合的类关系来复用其他类的能力,而不是继承。

多态是根据运行时的实际对象类型,同一个方法产生不同的运行结果,使同一个行为具有不同的表现形式。多态是指在编译层面无法确定最终调用的方法体,以覆写为基础来实现面向对象特性,在运行期由JVM进行动态绑定,调用合适的覆写方法来执行。

2.2 初识Java

JDK,Java Development Kit,Java开发工具包。

JIT,Just-in-time编译器。

JRE,Java Runtime Environment,Java运行环境。

JVM,Java Virtual Machine,Java虚拟机。

2.3 类

2.3.1 类的定义

类的访问级别有public和无访问控制符,类型分为class、interface和enum。

Java类主要由两部分组成:成员和方法。

2.3.2 接口与抽象类

抽象类在被继承时体现的是is-a关系,接口在被实现时体现的是can-do关系。

抽象类是模板式设计,而接口是契约式设计。

2.3.3 内部类

  • 静态内部类
  • 成员内部类
  • 局部内部类,定义在方法或者表达式内部
  • 匿名内部类

在JDK源码中,定义包内可见静态内部类的方式很常见,这样做的好处是:

  1. 作用域不会扩散到包外
  2. 可以通过“外部类.内部类”的方式直接访问
  3. 内部类可以访问外部类中的所有静态属性和方法

2.3.4 访问控制权限

  • public:可以修饰外部类、属性、方法,表示公开的、无限制的,是访问限制最松的一级,被其修饰的类、属性和方法不仅可以被包内访问,还可以跨类、跨包访问,甚至允许跨工程访问
  • protected:只能修饰属性和方法,表示受保护的、有限制的,被其修饰的属性和方法能被包内及包外子类访问
  • 无:无访问权限控制符仅对包内可见
  • private:只能修饰属性、方法、内部类,表示私有的,是访问限制最严格的一级,被其修饰的属性或方法只能在该类内部访问,子类、包内均不能访问,更不允许跨包访问

在定义类时,推荐访问控制级别从严处理:

  • 如果不允许外部直接通过new创建对象,构造方法必须是private
  • 工具类不允许有public或default构造方法
  • 类非static成员变量并且与自类共享,必须是protected
  • 类非static成员变量并且仅在本类使用,必须是private
  • 类static成员变量如果仅在本类使用,必须是private
  • 若是static成员变量,必须考虑是否为final
  • 类成员方法只供类内部调用,必须是private
  • 类成员方法只对继承类公开,那么限制为protected

2.3.5 this与super

一个实例变量可以通过this.赋值另一个实例变量;一个实例方法可以通过this.调用另一个实例方法;甚至一个构造方法都可以this.调用另一个构造方法。如果this和super指代构造方法,则必须位于方法体的第一行。在一个构造方法中,this和super只能出现一个,且只能出现一次,否则在实例化对象时,会因子类调用到多个父类构造方法而造成混乱。

由于this和super都在实例化阶段调用,所以不能在静态方法和静态代码块中使用this和super关键字。

2.3.6 类关系

关系是指事物之间存在单向或相互的作用力或影响力的状态。

类有关系的情况,包括:

  • 【继承】extends(is-a)
  • 【实现】implements(can-do)
  • 【组合】类是成员变量(contains-a)
  • 【聚合】类是成员变量(has-a)
  • 【依赖】是除组合和聚合外的单向弱关系(depends-a)
  • 【关联】是相互平等的依赖关系(links-a)

2.3.7 序列化

将数据对象转换为二进制的过程称为对象的序列化。将二进制流恢复为数据对象的过程称为反序列化。

常见的序列化方式有三种:

  • Java原生序列化
    • Java类通过实现Serializable接口来实现该类对象的序列化
    • 实现Serializable接口的类一定要显式地定义serialVersionUID属性值。修改类时根据兼容性决定是否修改serialVersionUID值:
      • 如果是兼容升级,请不要修改serialVersionUID字段,避免反序列化失败
      • 如果不是兼容升级,需要修改serialVersionUID值,避免反序列化混乱
  • Hessian序列化:Hessian序列化是一种支持动态类型、跨语言、基于对象传输的网络协议
    • 自描述序列类型
    • 语言无关,支持脚本语言
    • 协议简单,比Java原生序列化高效
  • JSON序列化:将数据对象转换为JSON字符串
    • 可读性好,方便调试

敏感属性不需要进行序列化传输,可以加transient关键字,避免把此属性信息转化为序列化的二进制流。

2.4 方法

2.4.1 方法签名

方法签名包括方法名称和参数列表,是JVM标识方法的唯一索引。

2.4.2 参数

参数在方法中,属于方法签名的一部分,包括参数类型和参数个数。

参数预处理包括两种:

  1. 入参保护
  2. 参数校验

2.4.3 构造方法

构造方法是方法名与类名相同的特殊方法,在新建对象时调用,可以通过不同的构造方法实现不同方法的对象初始化。

  • 构造方法名称必须与类名相同
  • 构造方法是没有返回类型的,即使是void也不能有
  • 构造方法不能被继承,不能被覆写,不能被直接调用
  • 类定义时提供默认的无参构造方法
  • 构造方法可以私有

在创建对象时,会先执行父类和子类的静态代码块,然后再执行父类和子类的构造方法。

2.4.4 类内方法

1、实例方法

又称为非静态方法。

2、静态方法
  • 静态方法中不能使用实例成员变量和实例方法
  • 静态方法不能使用super和this关键字,这两个关键字指代的都是需要被创建出来的对象
3、静态代码块

2.4.5 getter与setter

  • 满足面向对象语言封装的特性
  • 有利于统一控制

易出错的getter和setter方法定义方式:

  • getter/setter中添加业务逻辑
  • 同时定义isXXX()和getXXX()
  • 相同的属性名容易带来歧义

2.4.6 同步与异步

同步调用是刚性调用,是阻塞式操作,必须等待调用方法体执行结束。而异步调用是柔性调用,是非阻塞式操作,在执行过程中,如调用其他方法,自己可以继续执行而不被阻塞等待方法调用完毕。

2.4.7 覆写

向上转型时,通过父类引用执行子类方法时需要注意以下两点:

  • 无法调用到子类中存在而父类本身不存在的方法
  • 可以调用到子类中覆写了父类的方法,这是一种多态实现

想要成功地覆写父类方法,需要满足以下四个条件:

  • 访问权限不能变小
  • 返回类型能够向上转型而成为父类的返回类型
  • 异常也要能向上转型成为父类的异常
  • 方法名、参数类型及个数必须严格一致

2.5 重载

在同一个类中,如果多个方法有相同的方法名称、不同的参数类型、参数个数、参数顺序,即称为重载。

JVM在重载方法中,选择合适的目标方法的顺序如下:

  1. 精确匹配
  2. 如果是基本数据类型,自动转换成更大表示范围的基本类型
  3. 通过自动拆箱与装箱
  4. 通过子类向上转型继承路线依次匹配
  5. 通过可变参数匹配

2.6 泛型

泛型的本质是类型参数化,解决不确定具体对象类型的问题。

  • 尖括号里的每一个元素都指代一种未知类型
  • 尖括号的位置非常讲究,必须在类名之后或方法返回值之前
  • 泛型在定义处只具备执行Object方法的能力
  • 对于编译之后的字节码指令,其实没有这些花头花脑的方法签名,充分说明了泛型只是一种编写代码时的语法检查

使用泛型的好处包括:

  1. 类型安全
  2. 提升可读性
  3. 代码重用

2.7 数据类型

2.7.1 基本数据类型

Java的9种基本数据类型包括boolean、byte、char、short、int、long、float、double和refvar。

refvar是面向对象世界中的引用变量,也叫引用句柄。

引用分成两种数据类型:引用变量本身和引用指向的对象。本书中把引用对象称为refvar,而把引用指向的实际对象简称为refobj。

对象分为三块存储区域:

  • 对象头
    • 占用12个字节,存储内容包括对象标记和类元信息
  • 实例数据
  • 对其填充

2.7.2 包装类型

推荐所有的包装类对象之间值的比较,全部使用equals()方法。

在选择使用包装类还是基础数据类型时,推荐使用如下方式:

  1. 所有的POJO类属性必须使用包装数据类型
  2. RPC方法的返回值和参数必须使用包装数据类型
  3. 所有的局部变量推荐使用基本数据类型

2.7.3 字符串



Reading NotesEasy Coding Share Tweet +1