面向对象#

面向对象编程#

面向对象是将编程的粒度看作是一个个独立而又相互调用的对象。 对象之间消息的传递依靠对象提供的接口,也就是 public 方法 5Object-oriented programming https://en.wikipedia.org/wiki/Object-oriented_programming

面向过程是将编程的粒度看作是一个个独立而又相互调用的函数。函数之间的消息传递依靠的是函数参数以及返回值。

这两种思想的差异在于解决问题时,我们是将原问题分解为一个个步骤还是一个个对象。

在现代大型分布式系统中,更加提倡使用面向资源编程的方式 1凤凰架构 http://icyfenix.cn/methodology/forward-msa/governance.html。 但这并不是说完全摒弃了面向对象编程方式,我们通常在服务间交互用 RESTful,服务内部继续使用 OOP。

编译单元#

编译单元是我们编写源文件的最小单位,一个编译单元对应一个 .java 文件。

在同一个编译单元中,只能有一个 public 类。

因此,外部世界只能通过这个唯一的 public 类 “接口” 来跟这个对象通信。

如果我们希望防止客户端程序员访问越界内容,则通过访问权限控制关键字 privated 声明是标准做法。

值得注意的是,在 Java 源码中,我们推荐使用 驼峰式命名methodNameClassName

继承和组合#

在面向对象模型中,我们经常会遇到一些相似或相同的概念,我们希望能在学习之初将它们区分清楚。

父类 = 基类 = 超类;子类 = 继承类 = 导出类。

组合是指在一个类的内部直接实例化另一个类,属于套娃式的写法。 类之间的组合关系一般在运行期确定,而在编译期根本不知道应该调用哪一个类的方法。

继承是指在父类的基础上添加新的功能。 类间的继承关系一般在编译期就确定了,因此它并不如组合灵活,因此更推荐使用组合写法。

从可见性上来说,在组合关系中,类间的实现互相不可知,属于黑盒式代码; 而在继承关系中,子类可以看见父类的实现,属于白盒式代码。

Java 中所有的类都继承自 Object,我们将这种现象命名为 单根继承结构。 《Java 编程思想》指出,单根继承结构有利于垃圾回收器的实现,究其原因,未能找到官方说明。 个人猜测,若借鉴操作系统中父进程和子进程之间的关系,我们在 Object 对象中维护一个计数器。 每创建一个新对象,就将计数器 +1,在对象走完生命周期后,将计数器 -1。 若没有剩余的对象后,计数器应该为 0,这时回收 Object 对象就理所当然了。

多态和后期绑定#

想要理解多态和后期绑定,我们首先需要理解什么是绑定。 绑定(也叫调用)可以是指确定对象之间的调用关系,也可以是指确定某个方法的实参 6Late binding https://en.wikipedia.org/wiki/Late_binding 9Create a Resource Controller https://spring.io/guides/gs/rest-service/#_create_a_resource_controller。 后期绑定 = 动态绑定 = 动态链接;前期绑定 = 静态绑定。

按照绑定发生的时机来划分,可以分为前期绑定和后期绑定。 后期绑定是指调用发生的时机是运行期,前期绑定是指调用发生的时机在编译期。 面向对象编程语言通常会同时具备前期绑定和后期绑定的能力,而面向过程的编程语言通常只支持前期绑定。

前期绑定是指编译后的程序,会把所有的变量、表达式等存放在虚方法表(v-table)7Virtual method table https://en.wikipedia.org/wiki/Virtual_method_table 的固定位置。 而后期绑定是则无法根据现有信息确定,某个方法或某些变量和参数是否已经存放在了虚方法表中, 因此,不得已才在运行期查找是否存在某个方法或参数。

动态绑定是 Java 的默认行为,最直接的体现就是多态。多态是指我们用父类型的引用指向了子类的对象。 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法 2Java 多态 https://www.runoob.com/java/java-polymorphism.html。 多态的特性提高了代码的扩展能力。

以上文字描述可能会显得有些枯燥,但理解原理对理解代码有很大帮助,示例代码参考 动态绑定

容器和泛型#

在我们需要保存某些数据,又不知道需要存储多少数据,也不知道存多长时间,这时数组就不合适了。 我们非常需要一个在运行时计算容量、又能够自动管理的数据结构,幸运的是,Java 为我们提供了容器类。

为了应对现实世界中不同的数据类型,Java 也为我们准备了 ListMapSetQueueStack 等数据结构。

容器也被称为集合,容器中需要存放什么类型的数据,我们可以用泛型语法来声明。 借助 Java 自带的多态特性,泛型是类型安全的容器,它解决了向下转型带来的不安全问题。 因为用泛型语法的声明语句,在编译时,编译器会自动进行类型检查。如果是多态,它又会自动帮助我们类型转换。

对象的生命周期#

变量的存活期以花括号为边界。

{
    int x = 12;
    // Only x is avaliable
    {
        int q = 96;
        // Both x & q avaliable
    }
    // Only x is avaliable
    // q is "out of scope"
}

对象的生命周期并不受花括号限制,可以存活于作用域之外。

{
    String s = new String("a string");
} // End of scope

在花括号结束时,变量 s 就消失了,但是 s 指向的 String 对象仍然占据内存空间。 String 对象只要你需要,就会一直存在,直到没有指向该对象的引用时(可能是有一个计数器来记录有多少个指向该对象的引用),由垃圾回收器回收。

由 Java 创建的数据,可以存储在以下几个位置:

  • 寄存器:位于处理器内部,最快,但是数量有限。

  • 堆栈:位于 RAM 中,通过堆栈指针分配和释放空间,创建时知道生命周期。

  • 堆:位于 RAM 中,通用内存池,用于存储 Java 对象,不知道对象的生命周期。

  • 常量存储:位于程序代码内部,安全,且永远不会被改变。

  • 3流对象:对象转化为字节流,通常发送给另一台机器。4持久化对象:对象存放于磁盘上,JDBC 和 Hibernate 提供轻量级持久化支持。
  • 非 RAM 对象:存活于程序之外,程序结束后也可以存在,如流对象 3流对象:对象转化为字节流,通常发送给另一台机器。 和持久化对象 4持久化对象:对象存放于磁盘上,JDBC 和 Hibernate 提供轻量级持久化支持。

如何实现更快的执行速度(C++ 是这样做的):

  • 编译时确定对象的存储空间和声明周期(将对象置于堆栈或静态存储区域)

  • 优先考虑存储空间的分配和释放

  • 缺点:牺牲了灵活性(必须在编写程序时知道对象数量、生命周期、类型信息)

解决灵活性,牺牲执行速度(Java 是这样做的):

  • 运行时确定对象数量、生命周期和类型信息(在堆(内存池)中动态地创建对象)

  • 需要大量时间在堆中分配存储空间

  • 垃圾回收器负责释放存储空间

备注

  • 在堆中分配空间的时间大于在堆栈中分配空间的时间

  • 在堆栈中分配和释放空间,只需要将栈顶指针向下或向上移动

  • 创建堆存储空间的时间依赖于存储机制的设计

异常处理#

异常是一种对象,它从错误地点被 “抛出”,并被专门设计用来处理特定类型错误的响应的异常处理器 “捕获”。

异常处理就像是与程序正常执行路径并行的、在错误发生时执行的另一条路径。 它是另一条完全分离的执行路径,所以它不会干扰正常的执行代码。

异常不能被忽略,所以它保证一定会在某处得到处理。

并发编程#

早期的并发实现通常是编写中断服务程序,由硬件中断触发主进程挂起。隐患是难度大、不能移植。

现在实现并发的思路是把大问题分成多个可独立运行的任务,每个任务占用一个线程,线程受 CPU 调度。 隐患是共享资源容易发生冲突和数据不一致性问题。

在程序中,这些彼此独立运行的部分称之为线程,上述概念被称为 “并发”。并发最常见的例子就是用户界面。

网络编程#

互联网之所以这么精彩,是因为全球每个人都可以参与其中。而 Java Web 作为技术支持,功不可没。 在互联网中,一个最基本的需求就是通信。而只要涉及到网络编程,不可避免地就要用到计算机网络的知识。 只有遵循各种各样的协议,才能顺畅地完成通信。

Web 是一个巨型 客户端-服务器 系统。 服务器是分发信息的软件,它存储了大量用于信息,分布于世界各地。 客户端是驻留在用户机器上的软件,用于和服务器通信,这是普通用户能接触到的最近的设备。

而在网络模型中,我们需要考虑事务处理、最小化延迟、以及数据一致性等问题。 当然,现在有很多工具和中间件可以帮助我们解决这些问题。

由于网络编程涉及到两个端点,客户端和服务端。因此,我们自然而然地将开发工作分为客户端编程和服务端编程。 客户端编程(也叫 前端开发)是指使用浏览器支持的语言(如 JavaScript)编写运行在客户端上的软件。 服务端编程(也叫 后端开发)是指使用服务器支持的语言(如 Java、C++)编写运行在服务端上的软件。

当我们编写完程序,最后一步就是将代码 封装 起来,做成压缩包。这个压缩包通常是 .jar.war 格式。 .jar 包是 Java 应用程序打的包,包含类和一些相关资源,可供其他人调用。 .war 包是 Java Web 打的包,包含一个网站的所有内容(.class 文件、依赖包、配置文件、.html 文件等)。 因此,可以将一个 .war 包理解为一个 Web 项目 8jar 包和 war 包的介绍与区别 https://developer.aliyun.com/article/634962

Hello, World!#

首先配置 CLASSPATH 以显式声明搜索路径,搜索类库的完整路径为 %CLASSPATH%\{import后面的路径}

public class HelloDate {
    public static void main(String[] args) {
        System.out.println("Hello, it's: ");
        System.out.println(new Date());
    }
}