OOP & Java
概述
软件构造基本流程与目标
软件的构成:
软件 = 程序 + 数据 + 文档
- 程序:计算机可以接受的一系列指令,可以实现所要求的功能。
- 数据:使得程序能够适当操作信息的数据结构。
- 文档:描述程序的研制过程、方法、使用的图文资料。
软件开发的生命周期:
- 计划
- 分析
- 设计
- 实现
- 测试与集成
- 维护
软件开发过程模型
两种基本模型
- 线性模型
- 迭代过程
传统软件开发过程模型
- 瀑布过程
- 增量过程
- 原型过程
流行的软件开发过程模型
- 敏捷开发
- 测试驱动开发
其中,瀑布和增量是线性的,其余是迭代的。
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(字节输入流)类的对象,通常不直接使用它来读取用户键盘的输入,而是采取两种常用的封装方式:
使用字符流封装
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); sout(stdin.readLine());
使用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堆空间
- 任务:
- 跟踪每个对象,一旦处于不可达状态,回收其占用的内存
- 清理内存分配,回收产生的内存碎片。
- 优点:
- 屏蔽内存管理的细节,提高开发效率
- 开发者无权操纵内存,减少内存泄漏的风险。
- 缺点:
- 不受开发者控制,不受控的垃圾回收会带来多余的时间开销。
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各一次
- 判定条件覆盖:判定+条件
- 条件组合覆盖:所有判定结点的所有可能的取值组合各自取一次
- 路径覆盖:每个路径至少执行一次
黑盒测试
检查程序能否适当地接受输入并产生正确的输出。
等价类:类内数据等价
- 有效等价类:合理的、有意义的输入。用来考查程序能否完成指令的功能
- 无效等价类:不合理的、没有意义的输入。用来考查被测系统的容错性。
性能测试与压力测试
性能测试:检查系统是否满足要求的,为了保留系统的扩展空间而进行的,稍稍超过正常范围的测试。
压力测试:性能测试的一种。目的是测试在一定负载下系统长时间运行下的稳定性与性能。
代码覆盖率测试
代码覆盖率 = 代码的覆盖程序,一种度量方式。
- 语句覆盖
- 判定
- 条件
- 路径
代码质量
如何评价:
- 可维护性
- 可读性
- 可扩展性
- 可复用性
- 可测试性
- 简洁性
如何提高:
- 遵循编码规范
- 编写高质量的单元测试
- 代码审查
- 开发未动,文档先行
- 持续重构
集合与策略、迭代器模式
集合类概述
集合又被称为容器,与数组相似,但是不同的是:
- 数组长度固定,而集合长度可变
- 数据存放基本类型的数据,集合存放对象的引用
常见的集合: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
流的继承
抽象流类型:四大家族
字节流 | 字符流 | |
---|---|---|
输入流 | InputS | Reader |
输出流 | OS | Writer |
以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. orboolean 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
数据访问对象模式
即形成一个类似于数据管理系统的模式。
需要的角色:
- 数值对象,如学生Student
- 数据访问对象接口,如学生管理系统的接口,定义了搜索、排序等多个数据操作方法
- 接口的实现(实体类),实体类当中除了实现数据操作方法之外,数值对象也存储在实体类当中。

优点:隔离数据层,不会影响到实体对象与数据库的交互
缺点:代码量增加一层
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();
终止线程:线程可能由于以下两个原因之一而终止:
- run方法正常退出,线程自然终止
- 一个没有捕获的异常终止了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()。注意上锁,使得它只能被一个线程访问。
问题:
- 为什么缓冲区的判断条件是while(condition)不是if(condition)
- java中要求wait()方法为什么出现在同步块当中
答案:
- 防止线程被错误唤醒
- 防止出现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建立了一个抽象的耦合
- 支持广播通信,简化了一对多系统的设计难度
- 符合开闭原则
缺点:
- 花费时间多
- 存在循环依赖时会使系统崩溃
- 只知道变化,不知道怎么发生变化