码出高效:Java开发手册-第2章(5)

简介: 本章开始讲解面向对象思想,并以Java 为载体讲述面向对象思想在具体编程语言中的运用与实践。当前主流的编程语言有50 种左右,主要分为两大阵营:面向对象编程与面向过程编程。面向对象编程(Object-Oriented Programming,OOP)是划时代的编程思想变革,推动了高级语言的快速发展和工业化进程。OOP 的抽象、封装、继承、多态的理念使软件大规模化成为可能,有效地降低了软件开发成本、维护成本和复用成本。面向对象编程思想完全不同于传统的面向过程编程思想,使大型软件的开发就像搭积木一样隔离可控、高效简单,是当今编程领域的一股势不可......

2.3.5 this 与 super

      对象实例化时,至少有一条从本类出发抵达Object 的通路,而打通这条路的两个主要工兵就是this 和super,逢山开路,遇水搭桥。但是this 和super 往往是默默无闻的,在很多情况下可以省略,比如:

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

      任何类在创建之初,都有一个默认的空构造方法,它是super() 的一条默认通路。构造方法的参数列表决定了调用通路的选择;如果子类指定调用父类的某个构造方法,super 就会不断往上溯源;如果没有指定,则默认调用super()。如果父类没有提供默认的构造方法,子类在继承时就会编译错误,如图2-4 所示。

4.jpg

图2-4 父类默认构造方法缺失

      如果父类坚持不提供默认的无参构造方法,必须在本类的无参构造方法中使用super 方式调用父类的有参构造方法,如public Son(){ super(123); }。

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

      由于this 和super 都在实例化阶段调用,所以不能在静态方法和静态代码块中使用this 和super 关键字。this 还可以指代当前对象, 比如在同步代码块synchronized(this){...} 中,super 并不具备此能力。但super 也有自己的特异功能,在子类覆写父类方法时,可以使用super 调用父类同名的实例方法。最后总结一下this和super 的异同点,如图2-5 所示。

5.jpg

图2-5 this和super的异同点

2.3.6 类关系

      关系是指事物之间存在单向或相互的作用力或者影响力的状态。类与类之间的关系可分成两种:有关系与没关系,这似乎是一句非常正确的废话,难点在于确定类与类之间是否存在相互作用。证明类之间没关系是一个涉及业务、架构、模块边界的问题,往往由于业务模型的抽象角度不同而不同,是一件非常棘手的事情。如果找到了没有关系的点,就可以如庖丁解牛一样,进行架构隔离、模块解耦等工作。有关系的情况下,包括如下6 种类型:

  • 【继承】extends (is-a)。
  • 【实现】implements (can-do)。
  • 【组合】类是成员变量 (contains-a)。
  • 【聚合】类是成员变量 (has-a)。
  • 【依赖】是除组合与聚合外的单向弱关系。比如使用另一个类的属性、方法,或以其作为方法的参数输入,或以其作为方法的返回值输出(depends-a)。
  • 【关联】是互相平等的依赖关系 (links-a)。

      继承和实现是比较容易理解的两种类关系。在架构设计中,要注意组合、聚合、依赖和关联这四者的区别。

      组合在汉语中的含义是把若干个独立部分组成整体,各个部分都有其独立的使用价值和生命周期。而类关系中的组合是一种完全绑定的关系,所有成员共同完成一件使命,它们的生命周期是一样的,极度容易混淆。组合体现的是非常强的整体与部分的关系,同生共死,部分不能在整体之间共享。

      聚合是一种可以拆分的整体与部分的关系,是非常松散的暂时组合,部分可以被拆出来给另一个整体。比如,汽车与轮子之间的关系就是聚合关系,轮子模块包括钢圈、轮胎、气嘴。轮子拆卸下来用到另一个汽车上是完全没有问题的。

      依赖是除组合和聚合外的类与类之间的单向弱关系,就是一个类A 用到类B,那么就说A依赖B,这种关系是偶然的、松散的、临时的。依赖使用另一个类的属性、方法,或以其作为方法的参数输入,或以其作为方法的返回值输出。本书认为,广义上的有向关联也等同于依赖。依赖往往是模块解耦的最佳点。

      关联即是互相平等的依赖关系,可以在关联点上进行解耦,但是解耦难度略大于依赖关系。

      在类图中,用空心的三角形表示继承,用实心的菱形表示组合,用空心的菱形表示聚合,用一条直线表示关联,这四者都是用实线连接的。用三角形来表示实现,用一个箭头表示依赖,与前面的区别是这两者都是用虚线连接的。在画类图时,菱形、箭头、三角形放在哪一侧呢?在很多类图中,这个处理是非常随意的。如果方向画反了,那么类结构的认知也就反了。有一个规律,有形状的图形符号一律放在权力强的这一侧,如表2-3 所示。

      随着业务和架构的发展,类与类的关系是会发生变化的,必须用发展的眼光看待类图。比如表2-3 中的Body 和Head,如果有一天,动物的脑袋可以随意地移植,那么就从组合关系变成聚合关系了。狗与狗绳之间的约束,虽然很弱,但是如果防疫局在狗绳上标记疫苗记录,那么它们之间的关系就会变强,就变成组合关系了。在业务重构过程中,往往会把原来强组合的关系拆开来,供其他模块调用,这就是类图的一种演变。

表2-3 类关系示例图

6.jpg

2.3.7 序列化

      内存中的数据对象只有转换为二进制流才可以进行数据持久化和网络传输。将数据对象转换为二进制流的过程称为对象的序列化(Serialization)。反之,将二进制流恢复为数据对象的过程称为反序列化(Deserialization)。序列化需要保留充分的信息以恢复数据对象,但是为了节约存储空间和网络带宽,序列化后的二进制流又要尽可能小。序列化常见的使用场景是RPC 框架的数据传输。常见的序列化方式有三种:

      (1) Java原生序列化。Java类通过实现Serializable接口来实现该类对象的序列化,这个接口非常特殊,没有任何方法,只起标识作用。Java 序列化保留了对象类的元数据(如类、成员变量、继承类信息等),以及对象数据等,兼容性最好,但不支持跨语言,而且性能一般。

      实现Serializable 接口的类建议设置serialVersionUID 字段值,如果不设置,那么每次运行时,编译器会根据类的内部实现,包括类名、接口名、方法和属性等来自动生成serialVersionUID。如果类的源代码有修改,那么重新编译后serialVersionUID的取值可能会发生变化。因此实现Serializable 接口的类一定要显式地定义serialVersionUID 属性值。修改类时需要根据兼容性决定是否修改serialVersionUID 值:

  • 如果是兼容升级,请不要修改 serialVersionUID 字段,避免反序列化失败。
  • 如果是不兼容升级,需要修改 serialVersionUID 值,避免反序列化混乱。

      使用Java 原生序列化需注意,Java 反序列化时不会调用类的无参构造方法,而是调用native 方法将成员变量赋值为对应类型的初始值。基于性能及兼容性考虑,不推荐使用Java 原生序列化。

      (2)Hessian 序列化。Hessian 序列化是一种支持动态类型、跨语言、基于对象传输的网络协议。Java 对象序列化的二进制流可以被其他语言(如C++、Python)反序列化。Hessian 协议具有如下特性:

  • 自描述序列化类型。不依赖外部描述文件或接口定义,用一个字节表示常用基础类型,极大缩短二进制流。
  • 语言无关,支持脚本语言。
  • 协议简单,比 Java原生序列化高效。

      相比Hessian 1.0,Hessian 2.0 中增加了压缩编码,其序列化二进制流大小是Java序列化的50%,序列化耗时是Java 序列化的30%,反序列化耗时是Java 反序列化的20%。

      Hessian 会把复杂对象所有属性存储在一个Map 中进行序列化。所以在父类、子类存在同名成员变量的情况下,Hessian 序列化时,先序列化子类,然后序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。

     (3)JSON 序列化。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。JSON 序列化就是将数据对象转换为JSON 字符串。在序列化过程中抛弃了类型信息,所以反序列化时只有提供类型信息才能准确地反序列化。相比前两种方式,JSON 可读性比较好,方便调试。

      序列化通常会通过网络传输对象,而对象中往往有敏感数据,所以序列化常常成为黑客的攻击点,攻击者巧妙地利用反序列化过程构造恶意代码,使得程序在反序列化的过程中执行任意代码。Java 工程中广泛使用的Apache Commons Collections、Jackson、fastjson 等都出现过反序列化漏洞。如何防范这种黑客攻击呢?有些对象的敏感属性不需要进行序列化传输,可以加transient 关键字,避免把此属性信息转化为序列化的二进制流。如果一定要传递对象的敏感属性,可以使用对称与非对称加密方式独立传输,再使用某个方法把属性还原到对象中。应用开发者对序列化要有一定的安全防范意识,对传入数据的内容进行校验或权限控制,及时更新安全漏洞,避免受到攻击。

相关文章
|
3天前
|
存储 Java 数据库连接
使用Java开发桌面应用程序
使用Java开发桌面应用程序
14 0
|
3天前
|
Java API 开发工具
java与Android开发入门指南
java与Android开发入门指南
11 0
|
3天前
|
分布式计算 负载均衡 Java
构建高可用性Java应用:介绍分布式系统设计与开发
构建高可用性Java应用:介绍分布式系统设计与开发
8 0
|
3天前
|
前端开发 安全 Java
使用Spring框架加速Java开发
使用Spring框架加速Java开发
7 0
|
3天前
|
前端开发 JavaScript Java
Java与Web开发的结合:JSP与Servlet
Java与Web开发的结合:JSP与Servlet
8 0
|
3天前
|
设计模式 算法 Java
设计模式在Java开发中的应用
设计模式在Java开发中的应用
15 0
|
3天前
|
监控 Java Maven
揭秘Java Agent技术:解锁Java工具开发的新境界
作为JDK提供的关键机制,Java Agent技术不仅为Java工具的开发者提供了一个强大的框架,还为性能监控、故障诊断和动态代码修改等领域带来了革命性的变革。本文旨在全面解析Java Agent技术的应用场景以及实现方式,特别是静态加载模式和动态加载模式这两种关键模式。
24 0
|
4天前
|
存储 Java 开发者
探索Java开发中触发空指针异常的场景
作为一名后端开发者在Java编程的世界中,想必大家对空指针并不陌生,空指针异常是一种常见而又令人头疼的问题,它可能会在我们最不经意的时候突然出现,给我们的代码带来困扰,甚至导致系统的不稳定性,而且最可怕的是有时候不能及时定位到它的具体位置。针对这个问题,我们需要深入了解触发空指针异常的代码场景,并寻找有效的方法来识别和处理这些异常情况,而且我觉得空指针异常是每个Java开发者都可能面临的挑战,但只要我们深入了解它的触发场景,并采取适当的预防和处理措施,我们就能够更好地应对这个问题。那么本文就来分享一下实际开发中一些常见的触发空指针异常的代码场景,并分享如何有效地识别和处理这些异常情况。
19 1
探索Java开发中触发空指针异常的场景
|
5天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
46 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
12天前
|
Java 索引
Java String应用与开发
Java String应用与开发
22 0