OOP & Java

概述

软件构造基本流程与目标

软件的构成:

软件 = 程序 + 数据 + 文档
  • 程序:计算机可以接受的一系列指令,可以实现所要求的功能。
  • 数据:使得程序能够适当操作信息的数据结构。
  • 文档:描述程序的研制过程、方法、使用的图文资料。

软件开发的生命周期:

  1. 计划
  2. 分析
  3. 设计
  4. 实现
  5. 测试与集成
  6. 维护

软件开发过程模型

  • 两种基本模型

    • 线性模型
    • 迭代过程
  • 传统软件开发过程模型

    • 瀑布过程
    • 增量过程
    • 原型过程
  • 流行的软件开发过程模型

    • 敏捷开发
    • 测试驱动开发

    其中,瀑布和增量是线性的,其余是迭代的。

    EG1. 瀑布模型

    瀑布模型是最典型的预见性开发方法,严格遵循预先计划的需求分析、设计、编码、集成、测试、维护的步骤顺序执行。

    特点:

    • 线性推进
    • 阶段划分清楚
    • 整体推进
    • 无迭代
    • 管理简单
    • 无法适应需求变化(disadv.)

    EG2. 测试驱动开发/TDD

    测试驱动开发要求先编写测试代码,再编写对应的功能的代码,通过测试来推动整个开发的进行。
    这有助于编写简洁可用、高质量的代码,并加速开发过程。

    基本过程: (红灯-绿灯-重构)

    • 明确功能需求,制定TODO testing list
    • 完成针对此功能需求的测试用例编写
    • 测试代码编译不通过(RED)
    • 编写对应的功能代码
    • 测试通过(GREEN)
    • 重构代码,保证测试通过(REFACTOR)
    • 循环完成所有功能的开发

    优势:太多了,我觉得不是重点。参考粗字编。

软件构造的目标:

  • 可理解性
  • 可维护性
  • 可复用性
  • 时空性能

面向对象思想

How do we understand OOP?

一切都是对象、方法的封装。

面向对象思想模拟客观世界的事物与事物之间的联系为前提。

面向对象基本思想:

  • 任何事物都是对象,对象具有属性和方法。复杂的对象由简单的对象以某种方式构成。
  • 对象之间是普遍联系的,通过类比发现对象之间的相似性与共同属性,这是构成对象类的依据。
  • 对象的相互联系通过“消息”进行。消息驱动对象执行一系列操作,从而完成任务。

面向对象的优势:

  • 模块化
  • 自然性
  • 并发性
  • 重用性
    面向对向方法使得软件具有良好的体系结构,便于软件构件化、复用;使得软件具有良好的扩展性和维护性,抽象程度高,因此具有较高的生产效率。

面向对象的三大特性(ape /eip/):

  • 封装 Encapsulation
  • 继承 Inheritance
  • 多态 Polymorphism ——上课,不同学院的学生有不同的上课方式

Basic Java Language

Java Signs

标识符:由英文字母、数字、下划线、美元符号$组合而成。不能以数字打头。

关键字:没啥好考的。注意标识符不能与关键字同名。

注释符:

  • 行注释://abc
  • 块注释:/* abc */
  • 文档注释: /** abc **/

Java数据类型

数据类型分类:

  • 基本数据类型
    • 数值型
      • 整数类型(byte 8b, short 16, int 32, long 64)
      • 浮点类型(float 32, double 64)
    • 字符型(char 16)
    • 布尔型(boolean 1)
  • 引用数据类型
    • 接口
    • 数组

整数类型:

  • 各个整数类型的范围和字段长度固定,不受具体操作系统影响。—> 保证可移植性
  • 整型常量默认是int,声明long型常量时要加l/L。eg. 16L

浮点类型:

  • 各个浮点类型的范围和字段长度固定,不受具体操作系统影响。—> 保证可移植性
  • 常量默认是double,声明float需要加f/F。
float f = 3.14 // ILLEGAL
float f = 3.14f // CORRECT

数据类型的转换:

  • 整数和浮点数据类型按照精度有如下高低顺序:(H) double > float > long > int > short > byte (L)。
  • 低–>高,自动转换。如 float f = 200。
  • 高–>低,需要手动强转。如 int i = (int) 300.5f。
  • 任何的转换过程都有可能造成精度丢失。甚至可能造成较大的误差(溢出)

常量:

  • 直接常量
  • 符号常量:必须有final关键字。

数组

  • 声明数组

    int[] a; // Java语言规范提倡
    int b[]; // C语言风格,acceptable
  • 创建数组

    • 一维数组

      int[] c = new int[2]; //指定长度
      
      int[] d = new int[]{0, 1, 2} //在创建的同时赋值
    • 二维数组

      int[][] e = new int[2][3];
      
      int[][] f = new int[5][]; //逐行赋值
      int[] f[0] = new int[3];
      int[] f[1] = new int[99]; //不同行的列数可以不同
      
      int[][] g = new int[][]{{1, 1}, {1, 2}};
  • 访问数组:范围在0 ~ length - 1,越界会抛出异常。

控制流程

条件、循环、跳转语句。

和C语言不能说一模一样只能说完全相同。

输入与输出

标准输入 System.in 是一个InputStream(字节输入流)类的对象,通常不直接使用它来读取用户键盘的输入,而是采取两种常用的封装方式:

  1. 使用字符流封装

    BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
    sout(stdin.readLine());
  2. 使用Scanner类进行封装

    Scanner stdin = new Scanner(System.in);
    sout(stdin.nextLine());

标准输出 System.out 是一个PrintStream类的对象,可以直接使用其中的方法 print(), println(), write()等来在控制台输出。

  • print()和println()参数一样,区别在于println换行。
  • write()用来输出字节数组,不换行。

异常

异常指不期而至的状况。Exception继承于Throwable类。

异常关键字:

  • try - 用于监听。try语句块内发生异常时,异常才会被抛出。
  • catch - 用于捕获try语句块抛出的异常。
  • finally - 此语句块总会执行。用于回收try块内打开的物力资源 。只有finally块执行完成后才会回来执行try或者catch块当中的return或throw语句。如果finally块当中使用了return/throw等终止方法的语句,则不会跳回,直接停止。
  • throws - 用于方法签名当中,声明该方法可能抛出的异常。
  • throw - 用于抛出异常。

throw和throws:

  • 共同点:只抛出异常,不处理,消极的。
  • 不同点:throw用于方法内抛出对象,并且抛一个;throws用于方法头,表示异常的声明,可以一次抛出多个。

try-catch和throw(s):try-catch可以抛出并处理异常,而throws(s)不会处理,只交由函数的上层调用处理。

try-catch,try-finally, try-catch-finally are all acceptable patterns of handling exceptions.

Java虚拟机

虚拟机指通过软件模拟的具有完整硬件系统的,运行在一个完全隔离环境中的计算机系统。

Java虚拟机(aka. JVM) 通过软件来模拟Java字节码的指令集,是Java程序的运行环境。

JVM不仅仅支持Java语言,Groovy,Kotlin等都可以转换成字节码文件,通过JVM进行运行和处理。

JVM体系结构主要包括两个子系统、两个组件:

  • 类装载器子系统 Class Loader
  • 执行引擎子系统 Execution Engine
  • 运行时数据区组件 Runtime Data Area
  • 本地接口组件 Native Interface

Java堆在逻辑上被分成三个区域:

  • 新生代
  • 老年代
  • 元空间

JVM垃圾回收:

  • C和C++使用显式分配器,将堆空间完全暴露给用户。
    • 优点:程序员可以很好地利用堆空间内存
    • 缺点:每次分配需要手动释放,否则容易引起内存泄露。
  • Java使用隐式分配器,回收交给垃圾回收器
    • 垃圾回收器位于执行引擎
    • 主要对象是JVM堆空间
    • 任务:
      • 跟踪每个对象,一旦处于不可达状态,回收其占用的内存
      • 清理内存分配,回收产生的内存碎片。
    • 优点:
      1. 屏蔽内存管理的细节,提高开发效率
      2. 开发者无权操纵内存,减少内存泄漏的风险。
    • 缺点:
      • 不受开发者控制,不受控的垃圾回收会带来多余的时间开销。

JVM、JRE、JDK的区别:

  • 所有Java运行在JVM上。
  • JRE = JVM + Java基础API
  • JDK = JRE + 开发环境(javac编译工具、jar打包程序等)

类和对象(封装)

面向过程与面向对象

面向对象三大特性:

  • 封装 Encapsulation
  • 继承 Inheretance
  • 多态 Polymorphism

面向过程与面向对象比较:

  • 面向过程
    • 优点:简单场景下快速开发,计算效率高
    • 缺点:灵活性差,无法适用复杂情况
    • 适用于:简单场景、高性能计算
  • 面向对象:
    • 优点:低耦合、易复用、易扩展
    • 缺点:性能比较低
    • 适用于:复杂的大型软件

类的声明与构造

this关键字:代表了对象本身。

继承:使用extends,并且在子类的构造方法当中可以使用super(para1, para2, …)来继承父类的构造方法。

修饰符:可以部分地修饰类、方法、变量

  • 非访问修饰符

    • abstract 抽象(须继承)
    • final 最终(不可更改、不可继承)
    • static 静态,优先于对象出现
  • 访问修饰符

    • public 公共类,可以被所有类访问
    • protected 保护类,可以被同包、子类访问
    • default(或缺省) 默认类,只能被同包访问
    • private 私有类,只能自我访问

类修饰符:

  • static不可修饰类,其他6个可以。
  • 一个类文件当中,最多只能有一个类修饰符参与构造的类。但是可以有多个缺省修饰的类。
  • 可以看出来,在类中private、default、final是不能与abstract共存的。

方法修饰符:

  • 所有修饰符都可以使用。
  • 访问修饰符确定了此方法可以在什么样的类当中被访问。
  • abstract表示必须在子类当中Override此抽象方法
  • final表示方法不能被重写、覆盖、修改
  • static表示方法与类一同产生,优先对某个具体的对象。所以static方法是类所有的。由于static是最先产生的,他不能访问非static的方法或者属性。

变量修饰符:

  • abstract不能用,因为变量无法继承。
  • 访问修饰符同上。
  • final表示变量不能被修改/重新赋值
  • static表示变量比对象先产生,是类所有的。

接口与继承

继承

继承应当利用extends关键字。

子类不能直接继承父类的构造方法,需要使用super关键字。

子类能利用父类的属性和方法,并且增加一些新的属性与方法。

优缺点:

  • 优点:
    • 提高可复用性
    • 提高可扩展性
    • 使类与类产生关系,构成多态的基础
  • 缺点:增强了类的耦合性(一个类的改变会影响其他类)

Recall: 面向对象的优势:低耦合、易复用、易扩展

接口与抽象类

抽象类是不能实例化的的类。

抽象类当中有抽象方法,也有不抽象的方法(要有方法体)。

抽象方法只存在于抽象类。

接口比抽象类更抽象,接口当中的所有方法都是抽象方法,没有属性。

接口当中的抽象方法不需要使用public abstract关键字。

此外,接口必须是public的,才能让其他类实现。

有了抽象类,为什么还要接口?

  • 抽象类解决不了多继承的问题
  • 要实现的方法不是当前类的必要方法
  • 不同类型的多个类实现同样的方法

接口也可以用extends!

Java多继承

子类的继承只能有一个父类,为了避免多个父类发生属性与方法的冲突。要想实现多继承,有两种方法:

  • 内部类,在类的内部定义多个父类,引用之。
  • 实现多个接口。

超类与super关键字

所有类都继承了Object类,可以使用Object类当中的方法。

Object类型的变量如果想要进行具体的操作,需要先进行强制转换。

Object类当中的equals()方法用于比较两个对象是否相等,原理是判断两个对象的引用是否指向同一个对象。

super关键字的功能:

  • 在子类的构造方法当中显式地调用父类构造方法,as mentioned prev.
  • 访问父类的成员方法和变量,类似于this关键字。如super.maxHp / super.setShootNum()

多态

多态的好处:

  • 减少耦合
  • 增强可替换性
  • 增加可扩展性
  • 提高灵活性

使用多态的三个必要条件:

  • 继承
  • 重写
  • 父类引用指向子类

多态的三种实现方式:

  • 重写 Override
  • 抽象类和抽象方法
  • 接口

设计模式导论

面向对象设计原则

  • 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。——不要把变化原因各不相同的职责放在一起。
  • 开闭原则:一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当尽量使这个模块可以在不被修改的前提下被扩展。
  • 里氏代换原则:如果一个软件实体使用了一个基类,那么也能够等效地使用它的子类。因为子类继承了父类。
  • 依赖倒转原理:高层模块不应该依赖低层模块,都应该依赖抽象。要针对接口编程,不要针对实现编程——应当尽量使用抽象类与接口,而不使用具体类。
  • 合成/聚合复用原则:尽量使用对象组合,而不是继承来达到利用的目的。——在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。
  • 接口隔离原则:客户端不应该依赖它不需要的接口——即使用分割后的尽量小的接口,确保每个接口都是完全有用的。
  • 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。

设计模式的分类:

  • 目的:
    • 创建型模式
    • 结构型
    • 行为型
  • 范围:
    • 类模式
    • 对象模式

单例模式

保证一个类有且只有一个实例,并且提供一个访问它的全局访问点。如一个系统只有一个时钟,飞机大战中只有一个英雄机。

实现思路:

  • 使用私有的静态变量来定义实例,将实例与整个类捆绑,并且不可被外界访问。
  • 提供一个静态方法getInstance(),首次实例化对象,返回这个唯一的实例。
  • 为了防止多个线程同时访问,调用getInstance(),可以在static后加上synchronized关键字。这保证了线程安全,但是效率低。

简单工厂模式

一个工厂生产很多种不同的pizza。显然如果我们要新增pizza种类,需要修改pizza工厂的代码,违反了开闭原则。

工厂方法模式

一个工厂对应一种pizza,如果需要新增pizza,建立新种工厂与新种pizza类即可,无需修改现有的代码。

简单工厂模式vs工厂方法模式

  • 简单工厂模式
    • 将创建对象的逻辑判断放在工厂类当中,客户不感知具体的类
    • 违反了开闭原则,要增加新的产品,必须修改工厂。
  • 工厂方法模式
    • 将判断逻辑从工厂类转移到客户端,客户端必须感知到具体的工厂类
    • 符合开闭原则,有新的产品只需创建对应的产品类和工厂类即可,无需修改已有的代码。

抽象工厂模式

一个品牌搭配一个工厂生产不同各类的产品。

当只有一个品牌的时候就是简单工厂模式,当有两个以上品牌时,就是抽象工厂模式。

抽象工厂模式横向扩展很容易,即新增一个品牌(huawei, xiaomi + apple)很容易,新建apple工厂与apple产品即可。

但是纵向扩展很难,即新增一种产品(如手机,平板+电脑),需要修改所有的工厂。

优点:

  • 一个产品族的多个对象被设计成一起工作时,能保证客户端始终只会使用同个产品族中的对象。

缺点:

  • 产品族扩展非常困难,要新增一个产品类,既要修改工厂抽象类,还要修改具体的实现类。
  • 增加了系统的抽象性与理解难度。

软件测试与代码质量保障

软件测试的定义与分类

软件测试以需求为中心,不以缺陷为中心。
软件测试的分类:

  • 单元测试——编码阶段,对象是单个模块or组件
  • 集成测试——对应详细设计,对象是一组模块/组件
  • 系统测试——对概要设计,对象是整个系统
  • 验收测试——需求阶段,对象是整个系统

测试用例

测试用例包括了:

  • 测试输入
  • 执行条件
  • 预期结果

设计方法:

  • 黑盒测试(功能测试):着眼程序外部结构,不考虑内部逻辑,主要针对软件界面与功能
  • 白盒测试(结构测试):全面了解程序内部的逻辑结构,对所有逻辑路径进行测试。

设计原则:

  • 正确性
  • 全面性
  • 连贯性
  • 可判定性
  • 可操作性

白盒测试

针对程序的内部结构。
逻辑覆盖方法:(Weak to strong)

  • 语句覆盖:所有语句执行一次
  • 判定覆盖:执行每个分支一次
  • 条件覆盖:所有条件取true和false各一次
  • 判定条件覆盖:判定+条件
  • 条件组合覆盖:所有判定结点的所有可能的取值组合各自取一次
  • 路径覆盖:每个路径至少执行一次

黑盒测试

检查程序能否适当地接受输入并产生正确的输出。
等价类:类内数据等价

  • 有效等价类:合理的、有意义的输入。用来考查程序能否完成指令的功能
  • 无效等价类:不合理的、没有意义的输入。用来考查被测系统的容错性。

性能测试与压力测试

性能测试:检查系统是否满足要求的,为了保留系统的扩展空间而进行的,稍稍超过正常范围的测试。
压力测试:性能测试的一种。目的是测试在一定负载下系统长时间运行下的稳定性与性能。

代码覆盖率测试

代码覆盖率 = 代码的覆盖程序,一种度量方式。

  • 语句覆盖
  • 判定
  • 条件
  • 路径

代码质量

如何评价:

  • 可维护性
  • 可读性
  • 可扩展性
  • 可复用性
  • 可测试性
  • 简洁性

如何提高:

  1. 遵循编码规范
  2. 编写高质量的单元测试
  3. 代码审查
  4. 开发未动,文档先行
  5. 持续重构

集合与策略、迭代器模式

集合类概述

集合又被称为容器,与数组相似,但是不同的是:

  1. 数组长度固定,而集合长度可变
  2. 数据存放基本类型的数据,集合存放对象的引用
    常见的集合:List集合、Set集合、Map集合等。其中List和Set实现了Collection接口。

集合的继承框架见下。

集合框架的内容:

  • 接口:代表集合的抽象数据类型。如Collection, List, Set, Map
  • 实现:是集合接口的具体实现。如ArrayList, LinkedList, HashSet, HashMap
  • 算法:实现的对象的方法执行的一些有用的计算。如搜索、排序等。这些算法被 称为多态,因为相同的方法在相似的接口上有不同的实现。

LinkedList vs ArrayList

  • LinkedList在增加和删除的操作效率更高
  • ArrayList在查找和修改的操作效率更高

LL和AL可以序列化吗?可以,它们都实现了Serializable接口。

HashSet不是线程安全的,所以在多线程访问时要显式同步对HashSet的并发访问。

HashMap是一个散列表,存储的是键值对(key-value)映射。

  • 添加:this.put(key, value)
  • 访问:this.get(key)
  • 删除:remove(key)

策略模式

定义一系列算法,将每个算法封装,使得它们可以相互替换。但是什么情况下用什么策略由Client决定。

涉及角色:

  • Context 负责执行策略。如英雄机
  • Abs strategy 抽象策略类,如射击
  • Concrete strategy 具体的策略类,如散射
  • Client 客户端,如游戏本体

策略模式的重心不是实现算法,而是如何组织、调用算法。

优点:

  • 代码可复用性
  • 可扩展性
  • 高内聚、低耦合

缺点:

  • 只适用于客户端了解所有算法的情况(由客户端决定)
  • 如果策略数量较多的话,对象的数目会很可观

迭代器模式

将遍历的过程交由迭代器实现,使之与聚合对象分离。

例子:

流与输入输出

流是一组有序的数据序列,将数据从一个地方带到另一个地方。

流的分类:

  • 方向:输入6与输出6
  • 数据单位:字节6(传二进制代码Byte)与字符6(传字符)
  • 功能:节点6与处理6

输入与输出流

从控制台读取字符串:BufferedReader +InputStreamReader + Sys.in(输入流)

从文件读取:ISR + FileInputStream (如果不使用BR,this.read()只读一个字符)

写到文件:OutputSWriter + FileOutputStream

流的继承

抽象流类型:四大家族

字节流字符流
输入流InputSReader
输出流OSWriter

以Stream结尾的是字节6,以R/W结尾的是字符6。

所有的流都实现了java.io.Closeable接口。

两种6是可以互相转换的。字节6 $\leftrightarrow$ 字符6。

操作文件

读文件:

byte[] bytes = Files.readAllBytes(path);

String cont = Files.readString(path, charset); //charset is by default UTF-8.

List<String> lines = Files.readAlllines(path, charset);
//以上三个都是Files当中的静态方法

写文件:

Files.writeString(path, content, charset, option);

File.write(path, content, option);

File.write(path, lines, charset);

创建文件和目录:

文件和目录都以File对象的形式存在。

  • 创建新目录:Files.createDirectory(path) 其中路径除了最后一个部件之外其他必须是存在的
  • 创建路径中的中间目录:Files.createDirectories(path)
  • 创建一个空文件:Files.createFile(path) 如果文件已经存在,会异常

复制、移动、删除文件:

  • 复制:Files.copy(fromP, toP)
  • 移动:Files.move(fromP, toP)(如果toP存在,那么复制或者移动会失败。可以通过在函数当中添加option,如REPLACE_EXISTING)
  • 删除文件:Files.delete(Path) with possible excep. or boolean deleted = Files.deleteIfExists(path)

序列化与反序列化

序列化:把对象变成对象输出流ObjectOutputStream

ObjectOutputStream oos = new OOS(new FOS("person.dat"));
oos.writeObject(person1); //把对象person1写到person.dat
oos.close()
// 此处还需要捕获异常

反序列化:把对象输入流ObjectInputStream变成对象

ObjectInputStream ois = new OIS(new FIS("person.dat"));
Person zhangsan = (Person) ois.readObject();
ois.close();
//还需要try-catch

数据访问对象模式

即形成一个类似于数据管理系统的模式。

需要的角色:

  1. 数值对象,如学生Student
  2. 数据访问对象接口,如学生管理系统的接口,定义了搜索、排序等多个数据操作方法
  3. 接口的实现(实体类),实体类当中除了实现数据操作方法之外,数值对象也存储在实体类当中。

优点:隔离数据层,不会影响到实体对象与数据库的交互

缺点:代码量增加一层

Swing图形用户界面

Swing框架

Swing GUI包含了两种元素:组件和容器。

  • 组件是单独的控制元素,如按钮、文本框,组件要放到容器中才能显示。
  • 容器也是组件,因此容器也可以放到别的容器当中。
  • 组件和容器构成了包含层级关系。

Swing的组件是JComponent类的子类。

容器是一种可以包含组件的特殊组件。Swing当中有两大类容器:

  • 重量级容器,aka 顶层容器,不继承于JComponent,包括了JFrame, Japplet, JDialog。他们只能作为最顶层的容器包含其他组件。
  • 轻量级容器,aka 中间层容器,继承于JComponent,包括JPanel, JScrollBar等。必须包含在其他容器当中。

布局管理器控制着容器当中组件的位置。

所有的Swing组件由EventQueue.invokeLater(()->{statements})激活。

事件:调用方法addActionListener(ActionListener),也可以使用lambda语法:addActionListener(event->{statements})

密码域:char[] getPassword() 不是以String返回

MVC模式

构成:

  • 模型model:存储内容
  • 视图view:显示内容
  • 控制器controller:处理用户输入

模型:

  • 存储完整的内容
  • 实现改变内容和查找内容的方法
  • 没有用户界面,是完全不可见的

视图:

  • 一个模型可以有多个视图
  • 每个视图可以显示 全部内容的不同部分
  • 模型更新时,需要所有视图同步更新

控制器:

  • 使视图与模型分享
  • 处理事件
  • 将事件转化成对模型或者视图的更改

多线程

进程与线程

进程:正在运行的程序的实例

  • 私有空间,彼此隔离
  • 多进程不共享内存
  • 进程之间通过消息传递进行协作
  • 一般来说,进程 = 程序 = 应用,但是一个应用也可能会有多个进程

线程:进程当中一个单一顺序的控制流

  • 操作系统 能够进行运算调度的最小单位
  • 包含在进程当中,是进程的实际运作单位
  • 一个进程可以并发多个线程
  • 一个进程至少包含一个线程
  • 多个线程之间共享内存

Java中对线程的控制

线程的状态:

  • 新建 new
  • 可运行 runnable
  • 阻塞 block
  • 等待 waiting
  • 计时等待 timed waiting
  • 终止 terminated

创建线程的方法:

  • 继承Thread类,并重写run()方法
  • 实现Runnable接口,重写run()方法
    然后调用start()方法。
    不能直接调用run方法,只会执行同个线程当中的run方法,不会启动新的线程

Runnable更加常用,优势在于:

  • 任务与运行机制解耦,降低开销
  • 更容易实现多线程资源共享
  • 避免由于单继承局限所带来的影响

如果引入 lambda语法,还可以进一步简写为:

Thread t = new Thread(() -> {
    sout("Hi.");
})
t.start();

终止线程:线程可能由于以下两个原因之一而终止:

  1. run方法正常退出,线程自然终止
  2. 一个没有捕获的异常终止了run方法,使线程意外终止

线程阻塞:

  • 进入:当线程A试图获取一个内部的对象锁,而此锁被其他线程持有
  • 解除:当其他线程释放该锁,且线程高度器允许本线程持有它

线程等待:

  • 进入:当前线程对象调用了Object.wait方法;或者其他线程调用了Thread.join方法
  • 解除:等待的线程被其他线程对象唤醒;或者调用join的线程结束

线程计时等待:

  • 进入:当前线程对象调用Object.wait(time) / 当前线程调用Thread.sleep(time) / 其他线程调用Thread.join(time)
  • 解除:在指定的时间结束后自动返回

中断线程:阻塞调用(sleep or wait) 将会被InterruptedException异常中断

进程优先级:

  • 默认地,一个线程继承它的父线程的优先级
  • 可以用setPriority(int newPriority) (1~10,default 5)

守护线程:

  • 使用setDaemon(true); 标识该线程为守护线程
  • 守护线程的唯一用途是保证其能够为其他线程提供服务
  • 结束:在run结束或者main函数结束后

同步与死锁

使用synchronized关键字对代码加锁。synchronized关键字上锁的的代码只能由获取锁的线程执行。

死锁:两个线程互相持有两把锁,互不退让,永远地等待下去

避免:线程获取锁的顺序要一致

生产者-消费者设计模式

要点是设计缓冲区Buffer

当buffer满时,wait住。当buffer没满时则notifyAll()。注意上锁,使得它只能被一个线程访问。

问题:

  1. 为什么缓冲区的判断条件是while(condition)不是if(condition)
  2. java中要求wait()方法为什么出现在同步块当中

答案:

  1. 防止线程被错误唤醒
  2. 防止出现Lost Wake-Up。

好处:

  • 并发:生产者和消费者各司其职,通过异步的方式支持高并发,将一个耗时的流程搞成生产和消费两个阶段
  • 解耦:生产者和消费者解耦,通过缓冲区通讯。

任务与线程池

感觉不是重点,不想写了。

泛型与反射

泛型

什么是泛型:引入参数类型的一种方法

泛型方法:

  • 可以定义在普通类当中,也可以定义在泛型类中。注意,只有声明泛型的方法才是泛型方法
  • 在泛型类当中使用对应的泛型方法,其传入的参数必须与泛型类声明的类型一致
  • 如果泛型方法的泛型与泛型类声明的泛型名称一致,泛型方法的泛型优先生效
  • 类的静态泛型方法,不得使用泛型类当中的泛型,可以重新独立声明。

泛型通配符:

  • < ? extends *ClassName* > :CN的子类
  • < ? super *ClassName* > :超类
  • < ? > 无限定通配符
  • T表示一个确定的类型
  • ?表示不确定的类型,不能用于定义类和泛型方法

模板方法模式

构成:

  • 一个抽象的模板,给出顶层逻辑的的骨架,作为模板方法
  • 具体的实现:实现当中定义的抽象方法

优点:

  • 去除子类的重复代码
  • 提高了复用性
  • 可扩展

反射

通过对象获取类,再对类进行操作,如新建一个新的实例对象或者修改已有的实例。

获取类对象:

  • getClass()
  • forName(className)
  • .class

构造类的实例:

  • 使用Class.newInstance(),注意这个是无参数的,并且如果没有默认的构造函数,会抛出异常
  • 使用Constructor的newInstance(),先用Class.getConstructor(Class paraTypes),得到cons。普通的getCon只能得到公有的构造方法。如果是private/default/protected 需要使用getDeclaredCons
  • 使用Constructor还要先setAccessible

获取和修改成员变量

  • 使用Class.getField(name)方法获取名称为name的变量
  • Field类当中的get和set方法可以查看或者修改值

获取成员方法:

  • Class.getMethod(String name, Class paraTypes)可以获取 一个指定名称与参数类型的成员方法
  • 使用Method.invoke(object, paras)来调用方法

优缺点:

  • 优点:比较灵活,可以在运行时动态获取类的实例
  • 缺点:性能慢,破坏了封装性

网络编程

网络通信

两种常见的网络协议支持,by java.net

  • TCP :TCP(英语:Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
  • UDP :UDP (英语:User Datagram Protocol,用户数据报协议),位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。

URL与Socket通信的区别:

  • Socket通信在服务器端运行通信程序,不停地监听客户端的连接请求,主动等待客户端的请求服务,客户端提出请求时,马上连接并通信。而url进行通信时,被动等待客户端的请求。
  • Socket通信方式是服务器端可以与多个客户端相互通信,而url只对一个客户通信。

观察者模式

构成:

  • 抽象目标(发布者)notify
  • 具体目标
  • 抽象观察者 update
  • 具体观察者

优点:

  • 可以实现表示层与数据逻辑层的分离
  • 在sus-er和pub-er建立了一个抽象的耦合
  • 支持广播通信,简化了一对多系统的设计难度
  • 符合开闭原则

缺点:

  • 花费时间多
  • 存在循环依赖时会使系统崩溃
  • 只知道变化,不知道怎么发生变化

OOP & Java
https://max-wzm.github.io/2022/06/18/course/oop/
作者
Max Wang
发布于
2022年6月18日
许可协议