《码出高效》系列笔记(一):面向对象中的类

向代码致敬,寻找你的第[83]行。

Posted by MatthewHan on 2019-08-19

良好的编码风格和完善统一的规约是最高效的方式。

前言

虽然在GitHub有着17000+的star和大量的学习者,这本书即使涵盖的知识点对于很多入行较久developer来说并没有太大深入研究的价值,但是当时刚刚出炉的开发手册PDF精简版却一点一点的影响了我。也许是知乎上关注的技术话题下孤尽先生发布的回答吸引到了我,我开始注意到了平时编码中的那些事。

本篇汲取了本书中较为精华的知识要点和实践经验加上读者整理,作为本系列里的第一篇章第一节:面向对象之类篇。

本系列目录

集成IDE

《码出高效》不但有实体书,同时在IDE中有对应的插件,可以帮助你在实际开发中扫描代码编写存在的问题和可能会引发的隐患。

在Intellij IDEA中集成

Intellij IDEA在Plugins中直接搜索alibaba,如图所示的第一个点击install即可,第二个名为cloud Toolkit是阿里中间件团队出品的Apache dubbo快速开发部署的插件,可以快速创建Apache Dubbo工程,阿里中间件团队博客。

代码扫描

在需要扫描的地方邮件,最后有个编码规约扫描,当然也可以将整个工程扫描。

扫描结果

扫描的结果,可以根据tips进行代码的整改,当然这个插件也没有那么智能,有些特定场景不需要这些提示,可以将自行定义。

edit

面向对象

一切万物皆对象

面向过程让计算机有步骤地按照顺次做一件事情,是一种过程化的叙事思维。但是在大型软件开发中,流程互相穿插,模块互相耦合,往往牵一发动全身。面向对象就是计算机世界里解决复杂软件工程的方法论,拆解问题复杂度。

无类鬼

面向过程的结构相对松散,强调如何流程化地解决问题;面向对象的思维往往更加内聚,强调低耦合,高内聚,先抽象模型,定义共性行为,再解决实际问题。
传统意义上,面向对象有三大特性:封装继承多态。在《码出高效》这本书中将抽象也作为面向对象的特性之一,进而成为四大特性。
OOP(Object Oriented Programming)

  • 第一特性封装:使模块之间的耦合度变低,增强复用性,更具有维护性。例如多个业务类中socket对象需要对请求消息体的对象转化成字节数组并且计算消息体长度,在多个业务类中分别去实现是不合适的,不但低效而且一旦需求变更将会更改大量的代码,抽象出共性的行为特征封装成专门维护请求消息对象的类才是正确的选择。
  • 第二特性继承:在代码中广泛存在,子类继承父类,获得父类的部分属性和行为,也是增强复用性的体现。例如我们最常见的集合中的ListSet接口就是继承于Collection接口,而Collection则继承于Iterable接口。假如自定义的一个exception类,我们可以将它继承RuntimeException,通过super方法去个性化构建一个我们想要catch到的异常格式。
  • 第三特性多态:多态使得模块在复用性的基础上更加具有扩展性,让运行期更具有想象空间。可以进行覆写Override和重载Overload就是很好地体现了多态的意义。前面篇章中的工厂模式也是很好体验了多态的作用。
  • 第四特性抽象:抽象其实是完全囊括了以上三种特性,应该是所有程序员的核心素养之一。体现出程序员对业务的建模能力,以及对架构的宏观掌控力,从抽象到具体,逐步形象化的过程。比如Object类,是任何类的默认父类,是对万事万物的抽象,高度概括事物的自然行为和社会行为。
    我们在写代码中会常见到这些:getClass()是用来说明本质上是谁;toString()是当前名片;finalize()是再度向销毁时触发的方法;clone()是繁殖对象的一种方式;hashCodeequals()是判断你其他元素是否相等的身份证。

抽象类与接口

语法维度 抽象类 接口
定义关键字 abstract interface
子类继承或实现的关键字 extends extends
方法访问控制符 无限制 有限制,默认是public abstract类型
属性访问控制符 无限制 有限制,默认是public abstract类型
静态方法 可以有 不能有
static{静态代码块} 可以有 不能有
本类型之间扩展 单继承 多继承
本类型之间扩展的关键字 extends extends

总结:抽象类在被继承时体现的是is-a关系,而接口被实现时是can-do关系。抽象类可以包含抽象方法、属性变量、实体方法。抽象方法与接口一样,继承他的类必须要覆写实现。抽象类往往是用作同类食物的抽象,比如各类排行榜(常见的土豪排行榜、天台排行榜、天梯排行榜)他们中有着相似的特征。飞机会飞,鸟类也会飞,而fly()不应该被定义在抽象类中,因为飞机和鸟类除了都会飞以外很难再找到其他的共同特征。
抽象类是模板式设计,类似一个模具,而接口是契约式设计,更像一个合同。

内部类

在一个.java源文件中,只能定义一个类名与文件名完全一致的公开类,使用public class关键字来修饰,我们可以在这个类的内部和外部分别去定义另外的类,前者就叫内部类,后者叫外部类,内部类就成了这个类本身的一个属性,与其他属性的定义方式一致,可以使用publicprivateprotected访问权限关键字。可以定义成static静态内部类,当然类型也可以定义成classinterfaceenum

访问权限控制

我们在讲到封装的时候往往不能忽略掉使用关键字来限制类外部对类内属性和方法的随意访问,那么在Java中访问权限主要分为以下四个等级

访问权限控制符 任何地方 包外子类 包内 类内
public OK OK OK OK
protected NO OK OK OK
NO NO OK OK
private NO NO NO OK
  • public:可以修饰外部类属性方法,其他不详写了。
  • protected:只能修饰属性方法,只能够被包内的类访问,当然还有一种情况就是只要是他的子类都可以访问。
  • :这个比较陌生,Intellij IDEA有时会提示是否把一些只在包内访问的方法修改成无控制符。他并非是default,书中也明确说到定义外部类也极少用到无控制符的方式,一般要么定义public class,包外实例化;要么定义内部类,功能内聚。
  • private:修饰属性方法内部类,被其修饰过的属性或方法只能在该类访问,子类no way,包内外部类no way,包外without thinking。

在代码重构时,private方法过旧,可以直接删除,且无后顾之忧。但是删除一个public的方法就要谨慎小心地检查是否被调用。变量就像自己的小孩(我还没小孩),要尽量控制在自己的视线范围内,作用域太大,往往容易出现问题。因此,在定义类时,推荐访问控制级别从严处理:

  1. 如果不允许通过外部通过new创建对象,构造方法必须是private。
  2. 工具类不允许有public或者default构造方法。
  3. 类非static成员变量并且与子类共享,必须是protected。
  4. 类非static成员变量并且仅在本类使用,必须是private。
  5. 类static成员变量如果仅在本类使用,必须是private。
  6. 类static成员变量,必须考虑是否为final。
  7. 类成员方法只供类内部调用,必须是private。
  8. 类成员方法只对继承类公开,那么限制为protected。

this与super

对象实例化时,至少有一条从本类到Object的通路,打通这一条路的工兵就是thissuper,但是thissuper往往是默默无闻的,在很多情况不需要显式的调用,比如:

  • 本类方法调用本类属性
  • 本类方法调用另一个本类方法
  • 子类构造器隐含调用super()

子类基层的父类,而父类坚持不提供默认的无参构造方法,必须在本类的无参构造方法中使用super方法调用父类的有参构造方法,比如以下情形:

1
2
3
4
5
6
7
8
class Father {
public Father(int arg) {}
}
class Son extends Father {
public Son() {
super(123);
}
}
关键字 基本概念 查找范围 特异功能
this 访问本类属性和方法 先找本类,没有则找父类 单独使用时,表示当前对象
super 由子类访问父类中的实例属性和方法 直接查找父类 在子类覆写父类方法时,访问父类同名方法
共同点1 都是关键字,起指代作用 共同点2 在构造方法中必须出现在第一行

类关系

5种类型:

  • 继承:extends(is-a关系)
  • 实现:implements(can-do关系)
  • 组合:类是成员变量(contains-a关系)
  • 聚合:类似成员变量(has-a关系)
  • 依赖:import类(use-a关系)
类关系 英文名 description 权力强侧 示例说明
继承 Generalization 父类与子类之间的关系:is-a 父类方 舔狗继承于动物,完全符合里式代换
实现 Realization 接口与实现类之间的关系:can-do 接口方 舔狗实现了舔的接口行为
组合 Composition 比聚合更强的关系:contains-a 整体方 头只能是身体强组合的一部分,两者完全不可分,具有相同的生命周期
聚合 Aggregation 暂时组装的关系:has-a 组装方 小狗与项圈之间只是暂时的聚合的关系,项圈完全可以复用在另一条舔狗身上
依赖 Dependency 一个类用到另一个类:use-a 被依赖方 女神玩弄舔狗,舔狗作为参数传入,是一种依赖关系(这里是女神依赖舔狗哦,舔狗大翻身!)

序列化

内存中的数据对象只有转换为二进制流才可以进行数据持久化和网络传输。将数据对象转换为二进制流的过程称为对象的序列化。反之,将二进制流恢复为数据对象的过程称为反序列化。序列化常见的场景是RPC框架的数据传输。
常见的序列化方式有三种:

  • Java原生序列化
  • Hessian序列化
  • JSON序列化