《Head First Java》读书笔记(叁)

异常

异常也是对象
在这里插入图片描述

② 如果你想亲自抛出异常,一定要先声明:

public void takeRisk() throws BadException {	// 抛出之前一定要声明"throws..."
	if(true){
		throw new BadException();				// 异常的本质还是类,抛出的实际上是new出的对象
	}
}
// 另外,这个自己写的BadException异常类一定要继承Throwable类

try-catch-finally结构

try {
	doSomething();				// 如果这句被catch到异常,try的之后的代码(doOtherthing())不会执行
	doOtherthing();
} catch(Exception e) {
	e.printStackTrace();		// 只有catch到了相应的异常,该程序块才会执行	
} finally {
	doNothing();				// finally中的程序块是无论如何都要执行的(无论如何!)
}

有一个细节:

  如果try或catch语句块中有return,在中间就返回了,那么finally是否还会执行呢?

  。流程会跳到finally然后回到return,再return返回

还有一个细节:

  try一定要配合catchfinally(注意是“”)

  如果是try和finally配合而没有catch,则异常必须要先声明(此时是在ducking);否则可以选择性地声明

④ 上面的代码中,捕捉的是Exception类,这当然可以(依据是异常也是多态的);但并不推荐,因为会区分不开是哪种具体的异常。

因此最好为每个需要处理的异常编写不同的catch块

★ 极其需要注意:当有多个catch块时,第一个(最上边)catch的异常一定是范围最小的,也就是说,要从小排到大

因为JVM会按顺序取尝试捕捉异常,当上面的异常处理范围更大,下面的小的异常就没有机会被用到。因此编译器直接让这种写法无法通过。

⑤ 对于某块有风险的代码可能引发的异常,你可以catch到,也可以duck掉

ducking就是在“踢皮球”:抛出异常后,位于栈顶的该方法立即被弹出,异常传递给栈上的下一个方法(也就是调用方)

duck的实现也很简单,无非是有throws声明,但不写catch:

void foo() throws BadException {		// 声明了throws
	doSomething();						// 仅仅是声明而已,什么都不做
}

⑥ 这个异常如果一直未被处理,则会被一直duck到main();

甚至,main()也可以duck掉该异常,但这样的话JVM只能死给你看。

 
 

序列化

① 存储状态的两种选择:

纯文本存储:给其他非java程序使用

序列化存储:给自己写的java程序使用

② 首先明确,对象有状态实例变量)和行为方法

而序列化的目的就是:状态保持

序列化和解序列化是对对象的操作而不是对的操作;具体的讲,是对对象的状态实例变量)的操作;再具体些,是把每项变量的值写到特定格式的文件中

可以理解为:

序列化是把对象碾平、脱水、保存;解序列化是把对象展开、泡开、恢复

③ 序列化图解:
在这里插入图片描述

将对象序列化存储,写入文件

try {
	FileOutputStream fileStream = new FileOutputStream("myGame.var");	// 文件不存在则自动创建
	ObjectOutputStream os = new ObjectOutputStream(fileStream);
	os.writeObject(null);
	os.close();				// fileStream跟着关闭
} catch (Exception e) {
	e.printStackTrace();
}

解序列化,还原取出对象

try {
	FileInputStream fileStream = new FileInputStream("myGame.var");
	ObjectInputStream os = new ObjectInputStream(fileStream);
	Object one = os.readObject();		 // 读出的是Object类型;读取顺序与写入顺序相同
	CameClass someone = (GameClass)one;	 // 类型转换
	os.close();
} catch (Exception e) {
	e.printStackTrace();
}

串流stream)要两两连接才能真发挥作用,因为每个串流(类)只需要做好一件事情;

这样就可以通过不同的组合达到最大的适应性

对象版图上的所有东西都被序列化

也就是说,当某个对象被序列化时,不仅被该对象引用的实例变量被序列化,引用的对象也被序列化。并且这些都是自动进行的!

想要可以被序列化,必须实现Serializable接口

序列化是全有或全无的。整个对象版图中,如果某个对象不能被序列化(未实现Serializable接口),则全部失败

⑨ 如果想让某个实例变量被序列化,就把它标记为 transient瞬时)的

如果这样做,当对象被带回来时,该实例变量为被初始化为默认值

⑩ 解序列化比序列化要复杂一些,需要深入理解:

对象从stream中读出来 →

Java虚拟机通过存储的信息判断出对象的class类型 →

Java虚拟机尝试寻找和加载该类 →

新的对象被放在堆上

注意,整个过程中,构造函数没有别执行!这也很好理解,显然调用构造函数会将对象的状态变为默认值,而我们的目的是对象回到存储时的状态

⑫ JVM是根据什么取寻找对象的类的呢?

每个版本的类会有一个唯一的版本ID,它实例化出的类会盖上这个戳记

在序列化时,被存储的对象会有一个唯一的serialVersionUID一些对类的修改会改变类的版本号,id不匹配导致还原失败。

至于哪些修改会导致版本ID的改变,见原书P460

我们可以直接把该ID放在类中,让类在演化过程中维持相同的版本ID :

public class Dog {
	static final long serialVersionUID = -6849794470754667710L;
	// ...
}

静态变量层面的,显然不会被序列化
 
 

文件的操作和读写

操作文件(不包括读写)用到的类:java.io.File

② File类实例化出的对象可以理解为文件的路径,而不是文件本身

( ↑ 这里说的“文件”包括“目录”)

③ 文件操作

// 创建新的目录(就是文件夹)(mkdir/mkdirs)
File f0 = new File("F://loli");				// 先创建目录"loli"
f0.mkdir();
File f1 = new File("F://loli//A");			// 直接创建"F://loli//A"是会失败的;
f1.mkdir();
File f2 = new File("F://loli//B");
f2.mkdir();									

// 列出目录下的内容	
File f = new File("F://loli");
if(f.isDirectory()) {
	String[] dirContents = f.list();
	for(String each : dirContents) {
		System.out.println(each);
	}
}

// 取得绝对路径	
System.out.println(f.getAbsolutePath());

// 删除目录		
f1.delete();								// 先删除子目录
f2.delete();
f.delete();									// 否则根目录删除失败

④ 文件读写

try {
	File f = new File("F://FBI.txt");								// File对象
	FileReader fileReader = new FileReader(f);						// FileReader对象
	BufferedReader reader = new BufferedReader(fileReader);			// BufferedReader对象
	// BufferedReader reader = new BufferedReader(new FileReader(new File("F://FBI.txt")));
	// ↑ 一步到位,毕竟我们只用到了BufferedReader对象
			
	String line = null;
	while((line = reader.readLine()) != null) {						// 利用readLine进行逐行读取
		System.out.println(line);
	}			
	reader.close();													// 一定要记住关闭文件!!!
} catch(Exception e) {
	e.printStackTrace();
}
try {
	BufferedWriter writer = new BufferedWriter(new FileWriter(new File("F://FBI.txt")));
	// ↑ FileWriter第二个参数为true,则以追加形式写入
	writer.write("Loli sukisuki!!!");
	writer.close();
} catch(Exception e) {
	e.printStackTrace();
}

BufferedReader的实现机制

实际上,FileReader就可以直接进行读操作,但为了效率(这涉及到解码器的解码次数),

我们使用了BufferedReader类

它的肚子里有一个长度为8192的char[]数组,每次实际上都往数组中多读了一些,而它的read方法仅仅是读取数组而已

这个8192长度的char[]数组,就是读操作的缓冲区

BufferedWriter的实现机制

实际上,FileWriter对象直接就可以调用write方法写入;如果这样,每次write时会立即写进去

“写入”这个磁盘操作成本很高,远远高于了本身的内存成本

因此我们利用了缓冲区

BufferedWriter可以暂存一堆数据,到了满时才一并写入磁盘;这大大减少了实际磁盘操作的次数,是很经济的;

⑦ 如果想强制缓冲区立即进行磁盘写入,只要调用flush()方法

注意哦,读操作没有flush方法,每次char[]数组为空时,会自动读取,没有通过某个方法直接读满这种说法

 

 

 

 
 

 

 

 

 

 

 

 

 

 

 
☀ 《Head First Java》Kathy Sierra & Bert Bates

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值