用类制造对象
-
对象与类
-
对象是实体,需要被创建,可以为我们做事情
-
类是规范,根据类的定义来创建对象
-
-
对象 = 属性 + 服务
- 数据:属性或状态
- 操作:函数
-
封装
- 把对象和对数据的操作放在一起
定义类
- 创建对象
new VendingMachine();
VendingMachine v = new VendingMachine()
- 对象变量是对象的管理者
- 让对象做事
.
运算符.insertMoney(10)
成员变量和成员函数
-
成员变量
- 类定义了对象中所具体的变量,这些变量称作成员变量
- 每个对象有自己的变量,和同一个类的其他对象是分开的
-
本地变量
- 定义在函数内部的变量是本地变量
- 本地变量的生存期和作用域都是函数内部
- 成员变量的生存期是对象的生存期,作用域是类内部的成员函数
-
函数与成员变量
- 在函数中可以直接写成员变量的名字来访问成员变量
- 那么究竟访问的是哪个对象呢
-
函数是通过对象来调用的
v.insertMoney()
-
这次的调用临时建立了insertMoney()和v之间的关系,让insertMoney()内部的成员变量指的是v的成员变量
-
this
-
this是成员函数的一个特殊固有的本地变量,它表达了调用这个函数的对象
int price; void setPrice(int price) { // 在这个代码块中,price指的是参数,如果要调用成员变量price,用this.price this.price = price; }
-
-
-
调用函数
- 通过
.
运算符调用某个对象的函数 - 在成员函数内部直接调用自己(this)的其他函数
- 通过
对象初始化
- 成员变量定义初始化
- 成员变量在定义的地方就可以给出初始值
- 没有给出初始值的成员变量会自动获得0值(这个0不能单纯理解为数值0)
- 对象变量的0值表示没有管理任何对象,也可以主动给null值
- 定义初始化可以调用函数,甚至可以使用已经定义的成员变量
- 构造函数
- 如果有一个成员函数的名字和类的名字完全相同,则在创建这个类的每一个对象的时候会自动调用这个函数 --> 构造函数
- 这个函数没有返回类型
- 函数重载
- 一个类可以有多个构造函数,只要他们的参数列表不同
- 创建对象的时候给出不同的参数值,就会自动调用不同的构造函数
- 通过
this()
还可以调用其他构造函数 - 一个类的同名但参数表不同的函数构成重载关系
对象的识别
对象交互
封闭的访问属性
-
对象 = 属性 + 服务
- 数据:属性或状态
- 操作:函数
- 把数据和对数据的操作放在一起 --> 封装
-
private
-
只有这个类内部可以访问
-
类内部指类的成员函数和定义初始化
-
这个限制是对类的而不是对对象的
private int b; public Fraction plus(Fraction r) { int fenzi = a * r.b + r.a * b; int fenmu = b * r.b; return new Fraction(fenzi, fenmu); }
-
pirvate只能用于成员变量或成员函数,不能用于本地变量
-
-
开放的访问属性
- public
- 任何人都可以访问
- 任何人指的是在任何类的函数或定义初始化中可以使用
- 使用指的是调用,访问或定义变量
- 任何人都可以访问
- 没有加public或private,表示friendly,在同一个包内可以访问
- 类的public
- 在一个编译单元(一个java文件)中,只有和编译单元同名的类才能用public
- 一个编译单元最多只能有一个public类,也可没有public类
- 没有public的类只有同包内才能访问
包
类变量
- static关键字修饰的成员变量
- 可以通过
<类名>.<变量名>
访问,或者<对象>.<变量名>
访问 - 对象的管理在类里面而不是对象里面,多个对象指向的类对象是同一个
- 可以通过
类函数
- static的成员的初始化和对象的创建是没有关系的
- static的成员在类装载的时候进行初始化
- static的成员只会初始化一次
记事本
-
需求
-
能存储记录
-
不限制能存储的记录的数量
-
能直到已经存储的记录的数量
-
能查看存进去的每一条记录
-
能删除一条记录
-
能列出所有的记录
-
-
接口设计
add(String note);
getSize();
getNote(int index);
removeNote(int index);
list();
范型容器类
- 容器类
ArrayList<String> notes = new ArrayList<String>();
- 容器类有两个类型:
- 容器的类型
- 元素的类型
ArrayList类的操作
对象数组
- 对象数组中的每个元素都是对象的管理者而非对象本身
对象数组的for-each循环
- 对象数组的for-each,得到的是数组对象管理的对象。对其操作会改变数组对象中对象的值
- 对于容器类来说,for-each循环也是可以使用的
集合容器
- HashSet是一个无序的集合
- Hashset里面元素不能重复
- 在运行
System.out.println(v)
的时候,打印出来的是set里面的数组而不是set对象的地址。因为在set类里面重写了toString()
函数
Hash表
-
HashMap
-
遍历HashMap,不能像ArrayList和HashSet一样直接for-each
for (Integer k: coinnames.keySet()) { String s = coinnames.get(k); System.out.println(s); }
-
媒体资料库的设计
继承
- 为什么在程序中出现代码复制是代码不良的表现
- 程序中出现了代码复制,维护困难,而且不具备扩展性
- extends
- 父类有什么子类就有什么(注意访问权限问题)
子类继承了父类的什么
- protected变量
- 自己可以访问,同包内可以访问,子类可以访问
- 把父类的protected是一种没有办法的办法,要尽量避免
- new一个对象后的执行顺序
- 总是先初始化成员变量,再初始化构造器
- 先初始化父类的成员变量和构造器,再初始化子类的成员变量和构造器
- 子类里面可以定义和父类完全一样的成员变量时。使用该变量时,使用的是子类自己定义的成员变量,父类的该变量被隐藏了
多态变量
-
子类和子类型
- 类定义类型
- 子类定义子类型
- 子类的对象可以被当作父类的对象来使用
- 赋值给父类的变量
- 传递给需要父类对象的函数
- 放进存放父类对象的容器里
-
子类型与赋值
-
子类的对象可以赋值给父类的变量
Vehicle v1 = new Vehicle(); Vehicle v2 = new car(); Vehicle v3 = new Bicycle();
-
-
子类和参数传递
-
子类的对象可以传递给需要父类对象的函数
public class Database { public void add(Item item) { ... } } DVD dvd = new DVD(...); CD cd = new CD(...); database.add(dvd); database.add(cd);
-
-
子类型和容器
- 子类的对象可以放在存放父类对象的容器里
-
多态变量
- Java的对象变量是多态的,它们能保存不止一种类型的对象
- 它们可以保存的是声明类型的对象,或声明类型的子类的对象
- 当把子类的对象赋给父类的变量的时候,就发生了向上造型
向上造型
-
造型cast
-
子类的对象可以赋值给父类的变量
- 注意!Java中不存在对象对对象赋值!!
-
父类的对象不能赋值给子类的变量!
Vehicle v; Car c = new Car(); v = c; // 可以 c = v; // 编译错误!
-
可以用造型:
c = (car)v;
(只有当v这个变量的实际管理的是Car才行)
-
-
造型
- 用括号围起类型放在值的前面
- 对象本身并没有发生任何变化
- 所以不是“类型转换”
- 运行时有机制来检查这样的转化是否合理
ClassCastException
-
注意造型和类型转换的区别
-
向上造型
- 拿一个子类的对象,当作父类的对象来用
- 向上造型是默认的,不需要运算符
- 向上造型总是安全的
多态
- 函数调用的绑定
- 当通过对象变量调用函数的时候,调用哪个函数这件事叫做绑定
- 静态绑定:根据变量的声明类型来决定
- 动态绑定:根据变量的动态类型来决定
- 在成员函数中调用其他成员函数也是通过this这个对象变量来调用的
- 当通过对象变量调用函数的时候,调用哪个函数这件事叫做绑定
- 覆盖override
- 子类和父类中存在名称和参数完全相同的函数,这一对函数构成覆盖关系
- 通过父类的变量调用存在覆盖关系的函数时,会调用变量当时所管理的对象所属的类的函数
Object类
-
Java是一个单根结构,Object类是root
-
所有的类都是继承自Object类
-
Object类的函数
toSring()
equals()
-
@Override
- 重写父类的函数,函数名称和参数必须和父类一致,否则编译就会报错。
- 重写父类的函数时,函数的修饰修饰符一般是保持一致,可以变大,不能缩小
- 父类函数是public,子类重写时不能变为private
- 父类函数是protected,子类重写时可以改为public
城堡游戏
消除代码复制
- 代码复制是不良设计的一种表现
封装
- 增加可扩展性
- 可以运行的代码 != 良好的代码
- 对代码维护的时候最能看出代码的质量
- 如果要增加一个方向,如down或up(针对城堡游戏的代码而言)
- 用封装来降低耦合
- Room类和Game类都有大量的代码和出口有关
- 尤其是Game类中大量使用了Room类的成员变量
- 类和类之间的关系称作耦合
- 耦合越低越好,保持距离是形成良好代码的关键
- 将成员变量设置为private是一个降低耦合的很好方法
可扩展性
- 用接口来实现聚合
- 给Room类实现的新方法,把方向的细节彻底隐藏在Room类内部了
- 今后方向如何实现就和外部无关了
- 用容器类实现灵活性
- Room的方向是用成员变量来表示的,增加或减少方向就要改变代码
- 如果用Hash表来表示方向,那么方向就不是“硬编码”的了
框架加数据
- 以框架 + 数据来提高可扩展性
- 命令的解析是否可以脱落if-else
- 定义一个Handler来处理数据
- 用Hash表来保存命令和Handler之间的关系
抽象
- 抽象函数/抽象类
- 抽象函数 ---- 表达概念而无法实现具体代码的函数
- 抽象类 ---- 表达概念而无法构造出实体的类
- 带有abstract修饰符的函数
- 有抽象函数的类一定是抽象类
- 抽象类不能制造对象
- 但是可以定义变量
- 任何继承了抽象类的非抽象类的对象可以赋给这个变量
- 实现抽象函数
- 继承自抽象类的子类必须覆盖父类中的抽象函数
- 否则自己成为抽象类
- 两种抽象
- 与具体相对
- 表示一种概念而非实体
- 与细节相对
- 表示在一定程度上忽略细节而着眼大局
- 与具体相对
细胞自动机
-
如何看懂别人的程序,有两个办法
-
从main()开始看
-
找最小的类,追它的父类,从细节开始看,最后将细节拼接起来
-
数据与表现分离
-
程序的业务逻辑与表现无关
- 表现可以是图形的也可以是文本的
- 表现可以是当地的也可以是远程的
-
View和Field的关系
- 表现与数据的关系
- View只管根据Field画出图形
- Field只管数据的存放
- 一旦数据更新以后,通知View重写画出整个画面
- 不去精心设计哪个局部需要更新
- 这样简化了程序的逻辑
- 是在计算机运算速度提高的基础上实现的
-
责任驱动的设计
- 将程序要实现的功能分配到合适的类/对象中去是设计中非常重要的一环
狐狸与兔子
- Java不支持多继承,事实上大多数oop语言都不支持多继承
接口
- 接口是纯抽象类
- 所有的成员函数都是抽象函数
- 所有的成员变量都是
public static final
- 类表达的是一种东西,而类表达的是一种概念
- 接口规定了长什么样,但是不管里面有什么
- interface是一种特殊的class
- interface和class相同地位
- 实现接口
- 类用extends,接口用implements
- 类可以实现很多接口
- 接口可以继承接口,但不能继承类
- 接口不能实现接口
- 面向接口的编程方式
- 设计程序时先定义接口,再实现类
- 任何需要在函数间传入传出的一定是接口而不是具体的类
- 是Java成功的关键之一,因为极适合多人同时写一个大程序
- 也是Java要批评的要点之一,因为代码量膨胀起来很快
- Cell和Field的关系
- Cell在Field中,但是Cell的很多操作需要Field的数据
- 方法一:
- 让每个Cell有一个Field的管理者(Cell知道Field)
- 方法二:
- 由外部第三方来建立两者之间的联系(Cell不知道Field)
控制反转
- 注入反转
- 由按钮公布一个守听者接口和一对注册/注销函数
- 你的代码实现那个接口,将守听者对象注册在按钮上
- 一旦按钮被按下,就会反过来调用你的守听者对象的某个函数
内部类
- 匿名类
- 匿名类也是内部类
- 在new对象的时候给出的类的定义形成了匿名类
- 匿名类可以继承某类,也可以实现某接口
- Swing的消息机制广泛使用匿名类
- 内部类
- 定义在别的类内部丶函数内部的类
- 内部类能直接访问外部的全部资源
- 包括任何私有的成员
- 外部是函数时,只能访问那个函数里final的变量
JTable
- 用JTable类可以以表格的形式显示和编辑数据。JTable类的对象并不存储数据,它只是数据的表现。data
MVC模式
- 数据、表现和控制三者分离,各负其责
- M = Model(模型)
- V = View(表现)
- C = Control(控制)
- 模型:保存和维护数据,提供接口让外部修改数据,通知表现需要刷新
- 表现:从模型获取数据,根据数据画出表现
- 控制:从用户得到输入,根据输入调整数据
捕捉异常
-
捕捉异常
try { // 可能产生异常的代码 } catch (Type1 id1) { // 处理Type1异常的代码 } catch (Type2 id2) { // 处理Type2异常的代码 } catch (Type3 id3) { // 处理Type3异常的代码 }
-
异常的发生并不意味着程序中止
捉到了做什么?
-
拿到了异常对象之后
String getMessage();
String toString();
void printStackTrace();
-
但是肯定是回不去了,而具体的处理逻辑则取决于你的业务逻辑需要
-
再度抛出
catch (Exception e) { System.err.println("An exception was thrown"); throw e; }
- 如果再这个层面上需要处理,但是不能做最终的决定
finallly终于
try {
// The guarded region: Dangerous activities
// that might throw A, B, or C
} catch (A a1) {
// Handler for situation A
} catch (B b1) {
// Handler for situation B
} catch (C c1) {
// Handler for situation C
} finally {
// Activities that happen every time
}
- 进入try的四种下场
- 无疾而终
- 发生了异常,而且捉到了
- 发生了异常但是没有捉到
- 发生了异常做到了,但是又抛出了
- 在离开try语句之前,最后都要进finally
异常
-
如果要读文件
-
打开文件
-
判断文件大小
-
分配足够的内存空间
-
把文件读入内存
-
关闭文件
-
没有异常机制的代码
errorCodeType readFile { initialize errorCode = 0; open the file; if (theFilesOpen) { determine its size; if (gotTheFileLength) { allocate that much memory; if (gotEnoughtMemory) { read the file into memory; if (readFailed) { errorCode = -1; } } else { errorCode = -2; } } else { errorCode = -3; } close the file; if (theFiledIdntClose && errorCode == 0) { errorCode = -4; } } else { errorCode = -5; } return errorCode; }
-
用上异常机制的代码
try { open the file; determine its size; allocate that much memory; read the file into memory; close the file; } catch (fileOpenFailed) { doSomething; } catch (sizeDeterminationFailed) { doSomething; } catch (memoryAllocationFailed) { doSomething; } catch (readFailed) { doSomething; } catch (fileCloseFailed) { doSomething; }
-
-
异常
- 有不寻常的事情发生了
- 当这个事情发生的时候,原本打算要接着做的事情不能再继续了,必须得要停下来,让其他地方的某段代码来处理
-
异常机制最大的好处就是清晰地分开了正常的业务逻辑代码和遇到情况时的处理代码
抛出异常
-
异常声明
-
如果你的函数可能抛出异常,就必须在函数头部加以声明
void f() throws TooBig, TooSmall, DivZero { //... void f () {...
-
你可以声明并不会真的抛出的异常
-
-
什么能扔?
- 任何继承了
Throwable
类的对象 - Exception类继承
Throwable
throw new Exception()
throw new Exception("HELP")
- 任何继承了
异常捕捉时的匹配
-
Is-A的关系
-
就是说,抛出子类异常会被捕捉父类异常的catch给捉到
-
如果有多个catch,一旦异常被捕捉了,后面的catch就不会再捕捉了
-
捕捉任何异常
catch(Exception e) { System.out.println("Caught an exception"); }
-
运行时刻异常
- 像ArrayIndexOutOfBoundsException这样的异常是不需要声明的
- 但是如果没有适当的机制来捕捉,就会最终导致程序中止
异常遇到继承
-
异常声明
-
如果你的函数可能抛出异常,就必须在函数头部加以声明
void f() throws TooBig, TooSmall, DivZero { //... void f () {...
-
你可以声明并不会真的抛出的异常
-
如果你调用了一个声明会抛出异常的函数,那么你必须:
- 把函数的调用放到try块中,并设置catch来捕捉所有可能抛出的异常;或者
- 声明自己会抛出无法处理的异常
-
-
异常声明遇到继承关系
- 当覆盖一个函数的时候,子类不能声明抛出比父类的版本更多的异常
- 如果父类的构造器抛出了异常,在子类的构造函数中,必须声明父类可能抛出的全部异常
流
- 流是输入输出的方式
- 流是单向的
- 流的基础类
- 抽象类
InputStream
read()
int read()
read(byte b[])
read(byte[], int off, int len)
skip(long n)
int available()
mark()
reset()
boolean markSupported()
close()
- 抽象类
OutputStream
write()
write(int b)
write(byt[]e b)
write(byte[] b, int off, int len)
flush()
close()
- 抽象类
- 所有的IO操作都有Exception
- 文档地址
文件流
FileInputStream
FileOutputStream
- 对文件读写操作
- 实际工程已经较少使用
- 更常用的是以在内存数据或通信数据上建立的流,如数据库的二进制数据读写或网络端口通信
- 具体的文件读写往往有更专业的类,比如配置文件和日志文件
流过滤器(读写基本数据类型)
- 流过滤器
- 以一个介质流对象为基础层层构建过滤器流,最终形成的流对象能在数据的输入输出过程中,逐层使用过滤器流的方法来读写数据
- Data
DataInputStream
DataOputStream
- 用以读写二进制方式表达的基本数据类型的数据
文本流
-
Reader/Writer
- 二进制数据采用
InputStream/OutputStream
- 文本数据采用
Reader/Writer
- 二进制数据采用
-
在流上建立文本处理(Writer)
PrintWriter pw = new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream("abc.txt"))));
OutputStreamWriter
是Stream和Writer之间的桥梁
-
Reader
-
常用的是
BufferedReader
-
readLine()
BufferedReader in = new BufferedReader( new InputStreamReader( new FileInputStream("src/hello/Hello.java"))); String line; while ((line = in.readLine()) != null) { System.out.println(line); }
-
-
LineNumberReader
- 可以得到行号
getLineNumber()
- 可以得到行号
-
FileReader
InputStreamReader
类的子类,所有方法都从父类中继承而来FileReader(File file)
- 在给定从中读取数据的File的情况下创建一个新
FileReader
- 在给定从中读取数据的File的情况下创建一个新
FileReader(String fileName)
- 在给定从中读取数据的文件名的情况下创建一个新
FileReader
- 在给定从中读取数据的文件名的情况下创建一个新
FileReader
不能指定编码转换方式
汉字编码
InputStreamReader(InputStream in)
- 创建一个默认字符集的
InputStreamReader
- 创建一个默认字符集的
InputStreamReader(InputStream in, Charset cs)
- 创建使用给定字符集的
InputStreamReader
- 创建使用给定字符集的
InputStreamReader(InputStream in, CharsetDecoder dec)
- 创建使用给定字符集解码器的
InputStreamReader
- 创建使用给定字符集解码器的
InputStreamReader(InputStream in, String charsetName)
- 创建使用指定字符集的
InputStreamReader
- 创建使用指定字符集的
格式化输出
-
PrintWriter
-
format("格式",...)
-
printf("格式",...)
-
print(各种基本类型)
-
println(各种基本类型)
-
-
Scanner
- 在
InputStream
或Reader
上建立一个Scanner
对象可以从流中的文本中解析出以文本表达的各种基本类型next...()
- 在
流的应用
- 阻塞/非阻塞
read()
函数是阻塞的,在读到需要的内容之前会停下来等- 使用read()的更“高级”的函数,如
nextInt()
、readLine()
都是这样的 - 所以常用单独的线程来做socket读的等待,或使用nio的channel选择机制
- 使用read()的更“高级”的函数,如
- 对于socket,可以设置SO的时间
setSoTimeout(int timeOut)
对象串型化
ObjectInputStream
类readObject()
ObjectOutputStream
类writerObjec()
Serializable
接口