一、复习
1.1 包装类
8 种基本数据类型对应 8 个包装类:
8种基本数据类型: byte, short, int ,long, float, double, char, boolean
8个包装类: Byte, Short, Integer,Long, Float, Double, Character, Boolean
基本数据类型 与 对应的包装类 之间可以 自动实现装箱与拆箱。
- 装箱:基本数据类型 -> 包装类对象
- 拆箱:包装类对象 -> 基本数据类型
包装类对象的特点:
- 包装类对象不可变
- 部分包装类对象可以被共享,对于整数类型缓存范围:[-128, 127],对于 Character 缓存范围[0, 127],Boolean 只有 2 个值:true,false
- 包装类对象用运算符计算时只有:两个包装类对象做==和!=计算时不拆箱,比较地址值。其余计算情况都拆箱为基本数据再计算。
包装类的一些方法:(以 Integer 和 Double 为例)
把字符串的转为基本数据类型值:
Integer.parseInt(字符串)
得到 intDouble.parseDouble(字符串)
得到 double
比较两个值的大小
Integer.compare(整数1,整数2)
:得到 int 值。当第 1 个整数>,<,=第 2 个整数,依次返回正整数、负整数、0。Double.compare(小数1,小数2)
:得到 int 值。当第 1 个小数>,<,=第 2 个小数,依次返回正整数、负整数、0。
获取某中类型最大值、最小值
- 最大值:Integer.MAX_VALUE
- 最小值:Integer.MIN_VALUE
Character 还有 2 个比较好用的方法:
Character.toUpperCase(字符)
:转大写Character.toLowerCase(字符)
:转小写
package com.atguigu.review;
public class TestWrapper {
public static void main(String[] args) {
/*
'a'的编码值 97
'A'的编码值是65
相差32
*/
// char c1 = 'a';
char c1 = 'A';
c1 = (char) (c1 - 32);
System.out.println("c1 = " + c1);
// char c2 = 'a';
char c2 = 'A';
c2 = Character.toUpperCase(c2);
System.out.println("c2 = " + c2);
}
}
1.2 比较器
例如:学生类的对象要比较大小(找最大值、排序等)
1、java.lang.Comparable 接口(自然比较接口):
- 抽象方法:int
compareTo(Object obj)
- 谁来实现它:学生类实现它即可
2、java.util.Comparator 接口(定制比较器接口:
- 抽象方法:int
compare(Object o1, Object o2)
- 谁来实现它:单独写一个类实现它。这个单独的类可以是有名字的类,也可以是匿名内部类
1.3 File 类
File 类的对象用于代表一个文件或文件夹。这个文件或文件夹可能在硬盘中存在,也可能不存在。
通过 File 类对象可以:
获取文件或文件夹的一些属性:名称、路径值、大小、是否存在、是否是文件、是否是文件夹、最后修改时间.......
创建或删除、重命名:
- 创建文件:createNewFile。创建文件夹:mkdir、mkdirs
- 删除文件或空文件夹:delete
- 重命名:renameTo
获取上级目录:getParent、getParentFile
文件夹获取下级:list 或 listFiles
1.4 文件 IO 流
- FileInputStream:文件字节输入流。最小可以读取 1 个字节。
- 可以读取任意类型的文件
- FileOutputStream:文件字节输出流。
- 可以将任意类型的数据写到文件中
- FileReader:文件字符输入流。 要从一个纯文件文件中读取文件内容,最小可以读取 1 个字符。
- FileWriter:文件字符输出流。把字符串等纯文本数据写到纯文件中。
如何选择?
(1)如果能够明确这接下写的这段代码,一定是针对纯文本文件进行读和写,那么优先考虑:FileReader、FileWriter
(2)如果接下来你要写的这段代码是针对所有类型的文件的通用代码,那么一定是选择 FileInputStream、 FileOutputStream
关于方法的总结:
FileInputStream、FileReader: 调用 read 方法读数据
- FileInputStream:
- read()读 1 个字节, 返回的就是你读取的这个字节的值
- read(byte[] b)可以读取多个字节到 byte[]数组中:返回的是这次读取的字节的数量
- 如果已经到达流末尾,返回的都是 -1
- FileReader:
- read()读 1 个字符, 返回的就是你读取的这个字符的编码值
- read(char[] b)可以读取多个字符到 char[]数组中:返回的是这次读取的字符的数量
- 如果已经到达流末尾,返回的都是 -1
- FileInputStream:
FileOutputStream、FileWriter:调用 write 方法进行写/输出数据
FileOutputStream:
- write(int):输出 1 个字节
- write(byte[] b, int offset, int len):输出多个字节。从 byte[]数组中输出多个字节。从[offset]开始,输出 len 个字节。
- flush():刷新,从 JVM 中刷到文件中
FileWriter
write(int):输出 1 个字符
write(char[] b, int offset, int len):输出多个字符。从 char[]数组中输出多个字符。从[offset]开始,输出 len 个字符。
flush():刷新,从 JVM 中刷到文件中
所有 IO 流都有
close()
方法。
二、IO 流(续)
2.1 读写纯文本文件过程中编码问题
package com.atguigu.encoding;
import org.junit.jupiter.api.Test;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
public class TestReadWriteFile {
@Test
public void test1() throws IOException {
//文件的编码是GBK,现在的程序编码是UTF-8。
FileReader fr = new FileReader("d:\\gbk.txt");
char[] arr = new char[5];
while(true){
int len = fr.read(arr);
if(len==-1){
break;
}
System.out.println(new String(arr,0,len));
}
fr.close();
/*
结果乱码。
原因:
此时文件编码是GBK,解密密码本的编码是UTF-8
*/
}
@Test
public void test2()throws IOException{
FileReader fr = new FileReader("d:\\gbk.txt", Charset.forName("GBK"));
char[] arr = new char[5];
while(true){
int len = fr.read(arr);
if(len==-1){
break;
}
System.out.println(new String(arr,0,len));
}
fr.close();
}
@Test
public void test3() throws IOException {
FileReader fr = new FileReader("d:\\utf8.txt");
char[] arr = new char[5];
while(true){
int len = fr.read(arr);
if(len==-1){
break;
}
System.out.println(new String(arr,0,len));
}
fr.close();
//结果:尚硅谷
}
@Test
public void test4()throws IOException{
FileReader fr = new FileReader("d:\\utf8.txt", Charset.forName("GBK"));
char[] arr = new char[5];
while(true){
int len = fr.read(arr);
if(len==-1){
break;
}
System.out.println(new String(arr,0,len));
}
fr.close();
//乱码
//此时文件编码是UTF-8,解密密码本的编码是GBK
}
@Test
public void test5()throws IOException{
//往gbk.txt文件中追加 “AI应用Java方向”
FileWriter fw = new FileWriter("d:\\gbk.txt",true);
fw.write("AI应用Java方向");
fw.close();
//文件乱码了
//原因:文件是GBK编码, 程序是UTF-8编码, 加密过程选择了UTF-8方式
}
@Test
public void test6()throws IOException{
//往gbk.txt文件中追加 “AI应用Java方向”
FileWriter fw = new FileWriter("d:\\gbk.txt",Charset.forName("GBK"),true);
fw.write("AI应用Java方向");
fw.close();
}
}
总结:在创建 FileReader 和 FileWriter 时,指定与 【文件】 相同的编码。如果文件编码与程序运行环境的编码一致,不用指定编码。
2.2 纯文本按行读和写
单纯的使用 FileReader、FileWriter 是无法实现按行读和写的。可以借助其他 IO 流来完成:
- 方案一:借助 BufferedReader 和 BufferedWriter(我演示一下,但是不用掌握)
- 方案二:借助 Scanner(读)和 PrintStream(写)(更简洁)
package com.atguigu.line;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.util.Scanner;
public class TestLine {
@Test
public void test1()throws IOException {
FileReader fr = new FileReader("D:\\manylines.txt");
BufferedReader br = new BufferedReader(fr);
while (true){
String line = br.readLine();//按行读
if(line == null){
break;
}
System.out.println(line);
}
br.close();
fr.close();
//上面这个给大家看一下即可
}
@Test
public void test2()throws IOException{
FileReader fr = new FileReader("D:\\manylines.txt");
Scanner input = new Scanner(fr);
while(input.hasNextLine()){
String line = input.nextLine();
System.out.println(line);
}
input.close();
fr.close();
}
@Test
public void test3()throws IOException{
//从键盘输入一些话,按行保存到 messages.txt 文件中(留言)
Scanner input = new Scanner(System.in);
FileWriter fw = new FileWriter("d:\\messages.txt");
while (true) {
System.out.print("请输入你要说的话(stop结束):");
String line = input.nextLine();
if("stop".equals(line)){
break;
}
fw.write(line+"\n");//可以,但是不够标准和完美
}
fw.close();
input.close();
}
@Test
public void test4()throws IOException{
//从键盘输入一些话,按行保存到 messages.txt 文件中(留言)
Scanner input = new Scanner(System.in);
//System.out.println();//往控制台打印
PrintStream ps = new PrintStream("d:\\messages.txt");
while (true) {
System.out.print("请输入你要说的话(stop结束):");
String line = input.nextLine();
if("stop".equals(line)){
break;
}
ps.println(line);
}
ps.close();
input.close();
}
}
2.3 直接读写 Java 对象
涉及的 IO 流:
- ObjectOutputStream:对象输出流
- ObjectInputStream:对象输入流
新名称:
- 输出对象的过程被称为序列化。(序列化的意思,就是将对象变为一串字节序列)
- 读取对象的过程被称为反序列化。
案例 1:对象的读和写
要求:
Java 中规定凡是 Java 对象需要序列化,就必须:
- 实现 java.io.Serializable 接口:哪个类的对象要序列化,哪个类就实现 Serializable 接口。如果是学生类的对象要序列化,那么就让学生类实现 Serializable 接口
package com.atguigu.object;
import com.atguigu.bean.Student;
import org.junit.jupiter.api.Test;
import java.io.*;
public class TestObjectReadWrite {
@Test
public void test1()throws IOException {
//创建一个学生对象
Student s = new Student("张三", 96);
//把这个对象直接存到 d:\\student.txt
FileWriter fw = new FileWriter("d:\\student.txt");
fw.write(s.toString());
fw.close();
/*
看起来写出去了,但是并不符合我们的要求,这里是把Student变为String写出去的。
对将来读数据是有影响的
*/
}
@Test
public void test2()throws IOException{
//读 d:\\student.txt
FileReader fr = new FileReader("d:\\student.txt");
char[] arr = new char[5];
while(true){
int len = fr.read(arr);
if(len==-1){
break;
}
//怎么把arr中的字符 变回 Student对象 ?????(有问题)
}
}
@Test
public void test3()throws IOException {
//创建一个学生对象
Student s = new Student("张三", 96);
//把这个对象直接存到 d:\student.txt
FileOutputStream fos = new FileOutputStream("d:\\student.txt");
// fos.write();//()里面需要byte[],那么s对象怎么转为byte[]????(有问题)
}
@Test
public void test4()throws IOException{
//创建一个学生对象
Student s = new Student("张三", 96);
FileOutputStream fos = new FileOutputStream("d:\\student.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s);
//java.io.NotSerializableException: com.atguigu.bean.Student
//如果Student类没有实现 Serializable接口,就会报不支持序列化的异常NotSerializableException
oos.close();
fos.close();
/*
因为对象序列化后的结果并不是给“普通人”看,而是给另一段Java程序看的,所以不建议序列化的结果存到 .txt的文件中。
因为.txt的文件,“普通人”看到.txt的文件,默认为是可以用记事本,notepad++能正常打开的文件,会误导别人。
建议用非常规的后缀名/文件扩展名来表示。
常规:.txt, .jpg, .mp3, .mp4, .doc,......
非常规的:.aaa, .stu ...
*/
}
@Test
public void test5()throws IOException{
//创建一个学生对象
Student s = new Student("张三", 96);
FileOutputStream fos = new FileOutputStream("d:\\student.aaa");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s);
oos.close();
fos.close();
}
@Test
public void test6() throws IOException, ClassNotFoundException {
//用ObjectOutputStream输出的数据,要用ObjectInputStream读取。
FileInputStream fis = new FileInputStream("d:\\student.aaa");
ObjectInputStream ois = new ObjectInputStream(fis);
Object object = ois.readObject();
System.out.println("object = " + object);
ois.close();
fis.close();
}
}
案例 2:序列化版本 ID 的问题
场景描述:
- 当我已经把一个学生对象写到 student.aaa 文件中
现在
我要修改 Student 类- 修改类之后,重新读取 student.aaa 文件中 学生对象时,报错了
java.io.InvalidClassException: 类无效异常
com.atguigu.bean.Student; Student类无效
local class incompatible: 本地的字节码文件(Student.class)不兼容的、矛盾的
stream classdesc serialVersionUID = -481976849751154153, 流(ObjectInputStream)中类的描述序列化版本ID(类版本)是 -481976849751154153
local class serialVersionUID = -5258063863544938063 本地字节码文件中 序列化版本ID(类版本)是-5258063863544938063
简单说:当初输出学生对象时Student类的版本,与现在读取学生对象的Student类版本 不是一个版本
如果解决已经发生的问题?
- 要给 Student 类增加一个序列化版本 ID 且它的值 必须与 stream 流中的一样。
- private static final long serialVersionUID = stream 中 serialVersionUID;
如果避免此类问题发生?
- 如果是新的类,它在实现 Serializable 接口时,最好就加上序列化版本 ID,此时它的值随意。
- private static final long serialVersionUID = 任意值;
示例代码 1:旧类问题
package com.atguigu.bean;
import lombok.*;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class Student implements Serializable {
@NonNull
private String name;
@NonNull
private int score;
private int age;
private static final long serialVersionUID = -481976849751154153L;
}
package com.atguigu.object;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class TestReadObjectAfterUpdateStudent {
//在我修改了Student类后,读取旧文件 student.aaa文件
@Test
public void test1() throws IOException, ClassNotFoundException {
//用ObjectOutputStream输出的数据,要用ObjectInputStream读取。
FileInputStream fis = new FileInputStream("d:\\student.aaa");
ObjectInputStream ois = new ObjectInputStream(fis);
Object object = ois.readObject();
System.out.println("object = " + object);
ois.close();
fis.close();
/*
java.io.InvalidClassException: 类无效异常
com.atguigu.bean.Student; Student类无效
local class incompatible: 本地的字节码文件(Student.class)不兼容的、矛盾的
stream classdesc serialVersionUID = -481976849751154153, 流(ObjectInputStream)中类的描述序列化版本ID(类版本)是 -481976849751154153
local class serialVersionUID = -5258063863544938063 本地字节码文件中 序列化版本ID(类版本)是-5258063863544938063
简单说:当初输出学生对象时Student类的版本,与现在读取学生对象的Student类版本 不是一个版本.
如何解决?给Student类加 private static final long serialVersionUID = -481976849751154153L;
此时 serialVersionUID的值必须与stream中描述的一样。
*/
}
}
示例代码 2:新类
package com.atguigu.bean;
import lombok.*;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class Teacher implements Serializable {
@NonNull
private String name;
@NonNull
private double salary;
private int age;
private static final long serialVersionUID = 1L;//此时因为Teacher对象还未参与序列化,它的值随意写
}
package com.atguigu.object;
import com.atguigu.bean.Teacher;
import org.junit.jupiter.api.Test;
import java.io.*;
public class TestTeacherReadAndWrite {
@Test
public void test2()throws IOException {
//Teacher是新的类
Teacher t = new Teacher("柴",1500000);
FileOutputStream fos = new FileOutputStream("d:\\teacher.aaa");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(t);
oos.close();
fos.close();
}
@Test
public void test3() throws IOException, ClassNotFoundException {
//用ObjectOutputStream输出的数据,要用ObjectInputStream读取。
FileInputStream fis = new FileInputStream("d:\\teacher.aaa");
ObjectInputStream ois = new ObjectInputStream(fis);
Object object = ois.readObject();
System.out.println("object = " + object);
ois.close();
fis.close();
}
}
案例 3:哪些成员变量的值不会序列化
- 凡是 static 的成员变量,不会参与序列化。因为序列化是针对“对象”,而静态的是属于类的,所有对象的共享的。不需要单独某个对象单独存储。(序列化到文件就是永久存储,专业名称称为持久化)。
- 如果非静态的成员变量中,也有不想参与序列化的,需要加 transient(瞬时的,临时的)。
- 如果说程序员想要单独定制序列化和反序列化规则,那么必须在实现 Serializable 接口,除了加 serialVersionUID 之外,还要加 2 个方法:
- private void
writeObject(java.io.ObjectOutputStream out)
throws IOException{} - private void
readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
- private void
package com.atguigu.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data //不会给静态变量生成get/set
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable {
private static String company; //静态变量
private String name;
private transient double salary;//transient瞬时的,临时的
private static final long serialVersionUID = 1L;
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
Employee.company = company;
}
//除非对Employee对象的序列化和反序列化有必要的特殊需求,才会加下面的方法,否则不要加
/* private void writeObject(java.io.ObjectOutputStream out) throws IOException{
//定制序列化过程
out.writeUTF(name);
out.writeDouble(salary);
out.writeUTF(company);
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
//定制反序列化规则
//读的顺序、类型必须与写的顺序类型一致
name = in.readUTF();
salary = in.readDouble();
company = in.readUTF();
}*/
}
package com.atguigu.object;
import com.atguigu.bean.Employee;
import org.junit.jupiter.api.Test;
import java.io.*;
public class TestEmployeeReadAndWrite {
@Test
public void test1()throws IOException {
Employee.setCompany("尚硅谷");
Employee e = new Employee("李刚",20000);
FileOutputStream fos = new FileOutputStream("D:\\employee.aaa");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(e);
oos.close();
fos.close();
}
@Test
public void test2()throws IOException,ClassNotFoundException{
FileInputStream fis = new FileInputStream("d:\\employee.aaa");
ObjectInputStream ois = new ObjectInputStream(fis);
Object object = ois.readObject();
System.out.println("object = " + object);
System.out.println("公司名:" + Employee.getCompany());
ois.close();
fis.close();
}
}
2.4 IO 流小结
看图片或 xmind 文档。
三、认识异常和异常处理
3.1 认识异常
Java 程序运行过程中出现的问题都是异常,但是不包括语法错误和逻辑错误。
如果 Java 程序运行时遇到异常,没有合理的处理,那么程序会挂掉。这样的话,程序的健壮性就很差,所以为了提高程序的健壮性,必须引入异常处理机制。
Java 中使用对象来表示各种异常和错误。而对象是由 Java 类 new 出来的,所以下面要认识各种各样的异常和错误的类型:
1、Throwable 类
Throwable
类是 Java 语言中所有错误或异常的超类。
- 只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java
throw
语句抛出。 - 类似地,只有此类或其子类之一才可以是
catch(捕获)
子句中的参数类型。
异常对象可能是 JVM 抛出,或者程序员用 throw 语句抛出。一旦异常对象被抛出,没有被 catch 的话,程序就会挂掉。
两个子类的实例,[Error](../../java/lang/Error.html)
和 [Exception](../../java/lang/Exception.html)
,通常用于指示发生了异常情况。通常,这些实例是在异常情况的上下文中新近创建的,因此包含了相关的信息(比如堆栈跟踪数据)。
2、Error
Error
是 Throwable
的子类,用于指示合理的应用程序不应该试图捕获(catch)的严重问题。
例如:VirtualMachineError (虚拟机错误)的子类 StackOverflowError(栈内存溢出)和 OutOfMemoryError(堆内存溢出错误)
遇到这里错误,通常应该停下来:
- 升级硬件
- 升级软件架构或修复 BUG(例如无条件递归)
- JVM 调参数
- 等
3、Exception
Exception
类及其子类是 Throwable
的一种形式,它指出了合理的应用程序想要捕获的条件。
忠告:
(1)不要把所有的 Exception 都交给 try-catch 处理,否则会造成(1)程序的可读性差(2)程序性能低下。
(2)能通过条件判断、数据校验等操作避免的异常,就不要依赖 try-catch。例如:下标越界(下标应该在[0, 数组的长度-1])、空指针异常(引用数据类型的变量必须赋值正经的对象,不应该是 null 就去调用方法啥)、类型转换异常(向下转型之前用 instanceof 判断一下)等。
(3)实在避免不了的,要用 try-catch 处理。例如:FileNotFoundException 等
4、编译时异常和运行时异常
- 编译时异常(受检异常,编译器可以检测出来的风险):是指编译器可以提前
预警
的异常类型。此时这个异常不见得一定会发生。例如:FileNotFoundException 等 - 运行时异常(非受检异常,即不受编译器检查):是指编译器
不会给出预警
的异常类型,哪怕这个异常真的会发生,编译器也无视它。所有运行时异常都是 RuntimeException 及其子类。
最低要求:至少能写出 5 种以上的异常类型。建议大家写英文。
3.2 异常的处理
主要围绕 5 个关键字:try,catch,finally,throw,throws
3.2.1 try-catch
语法结构:
try{
可能发生异常的代码1;
可能发生异常的代码2;
可能发生异常的代码3;
}catch(异常的类型1 参数名){//参数名通常是e
异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型2 参数名){//参数名通常是e
异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型3 参数名){//参数名通常是e
异常处理的语句, 或 打印异常信息 ,或日志记录等
}
代码4;
1、情况 1:try 中三句代码都没有发生异常
- 所有 catch 都不执行
- 代码 4 正常执行
2、情况 2:try 中代码 2 发生异常
- 首先,try 中代码 3 不执行
- 其次,根据发生的异常的类型,来确定走哪个 catch。catch 是从上往下判断,如果上面的匹配了,下面的 catch 就不看了
- 代码 4 正常执行
3、情况 2:try 中代码 2 发生异常,但是所有 catch 都不匹配
- 首先,try 中代码 3 不执行
- catch 会判断,但是{}都不执行
- 当前方法就挂了,代码 4 就不执行了
package com.atguigu.exception;
import org.junit.jupiter.api.Test;
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestTryCatch {
@Test
public void test1(){
//需求:从键盘输入2个整数,求它们的商
Scanner input = new Scanner(System.in);
//选中可能发生异常的代码,然后按Ctrl + Alt + T,选中try-catch
int a = 0;
while (true) {
try {
System.out.print("请输入第1个整数:");
a = input.nextInt();
//java.util.InputMismatchException 输入不匹配,编译器没有提示,所以是运行时异常
break;
} catch (InputMismatchException e) {
// System.out.println(e);//把异常当普通信息打印
// System.out.println("输入的不是整数");//自己打印错误信息
// System.err.println(e);//以错误信息的方式打印e对象
System.err.println("输入的不是整数");//以错误信息的方式打印e对象
// e.printStackTrace();//打印e对象的详细信息,包括堆栈跟踪信息(初学时先用它,将来学了日志框架,再根据不同情况选择不同的等级信息打印)
input.nextLine();//把现在输入的不匹配的信息全部读取调用,才能重新读取新的数据
}
}
int b = 0;
while(true) {
try {
System.out.print("请输入第2个整数:");
b = input.nextInt();
if(b==0){
System.out.println("除数不能为0,请重新输入!");
}else{
break;
}
} catch (InputMismatchException e) {
System.err.println("输入的不是整数");
input.nextLine();//把现在输入的不匹配的信息全部读取调用,才能重新读取新的数据
}
}
double shang = (double)a/b;
System.out.println("shang = " + shang);
input.close();
}
}
3.2.2 finally 关键字
finally 是与 try-catch 结构搭配使用的。用于表示无论 try 中有没有异常,也不管 catch 能不能抓住异常,都要执行的代码块,哪怕 try 或 catch 中有 return 语句也挡不住 finally 执行。通常是资源释放代码,例如:IO 流的关闭、网络连接的断开等代码。
package com.atguigu.exception;
import org.junit.jupiter.api.Test;
public class TestFinally {
@Test
public void test1(){
try{
System.out.println(8/2);//执行,不发生异常
System.out.println("java");//执行
}catch (ArithmeticException e){
System.out.println("发生算术异常");//不执行
}finally {
System.out.println("finally");//执行
}
System.out.println("atguigu");//执行
}
@Test
public void test2(){
try{
System.out.println(8/0);//执行,发生异常
System.out.println("java");//不执行
}catch (ArithmeticException e){
System.out.println("发生算术异常");//执行
}finally {
System.out.println("finally");//执行
}
System.out.println("atguigu");//执行
}
@Test
public void test3(){
try{
System.out.println(8/0);//执行,发生异常 算术异常
System.out.println("java");//不执行
}catch (NullPointerException e){//异常类型不匹配
System.out.println("发生空指针异常异常");//不执行
}finally {
System.out.println("finally");//执行
}
System.out.println("atguigu");//不执行
}
@Test
public void test4(){
try{
System.out.println(8/2);//执行,不发生异常
System.out.println("java");//执行
return;//提前结束当前方法
}catch (ArithmeticException e){
System.out.println("发生算术异常");//不执行
}finally {
System.out.println("finally");//执行
}
System.out.println("atguigu");//不执行
}
@Test
public void test5(){
try{
System.out.println(8/2);//执行,不发生异常
System.out.println("java");//执行
System.exit(0);//退出虚拟机
}catch (ArithmeticException e){
System.out.println("发生算术异常");//不执行
}finally {
System.out.println("finally");//不执行
}
System.out.println("atguigu");//不执行
}
}
3.2.3 try-catch-with-resource
语法结构:
try(需要关闭的资源对象的声明){
可能发生异常的业务代码
}catch(异常的类型1 参数名){//参数名通常是e
异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型2 参数名){//参数名通常是e
异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型3 参数名){//参数名通常是e
异常处理的语句, 或 打印异常信息 ,或日志记录等
}finally{
除了try()中资源关闭之外代码。(如果finally中只需要写try()资源的关闭语句,那么可以省略finally。
因为try()中的资源对象会自动关闭。
}
try()
中的资源类必须实现 Closeable 接口 或 AutoCloseable 接口,才能被自动关闭。
try()
中的资源类对象默认是 final 的。
package com.atguigu.exception;
import com.atguigu.bean.Student;
import org.junit.jupiter.api.Test;
import java.io.*;
public class TestTryCatchWithResource {
@Test
public void test1(){
//把Student对象写到student.aaa文件中
Student s = new Student("张三",100);
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("d:\\尚硅谷\\student.aaa");
oos = new ObjectOutputStream(fos);
oos.writeObject(s);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test2(){
//把Student对象写到student.aaa文件中
Student s = new Student("张三",100);
//JDK7之后支持
try(FileOutputStream fos = new FileOutputStream("d:\\尚硅谷\\student.aaa");
ObjectOutputStream oos = new ObjectOutputStream(fos);) {
oos.writeObject(s);
} catch (IOException e) {
e.printStackTrace();
}
//凡是在try()中声明的资源对象,都不需要手动关闭,可以自动关闭了。
}
@Test
public void test3()throws IOException{
FileWriter fw = new FileWriter("d:\\1.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("hello");
fw.close();
bw.close();//报错,流已经关闭了
/*
数据的流向:
内存 -> bw -> fw -> "d:\\1.txt"文件
这里先关闭fw。
关闭流有先后顺序问题。遵循先new的后关。
*/
}
}
3.2.4 throws
throws 是用于声明当前方法中
- 可能发生某种类型的异常
- 当前方法并未处理这个可能发生的异常,要由调用者处理,即谁调用谁处理
3.2.5 throw
throw:主动抛出异常对象
在方法体中有 throw 语句,方法签名中一般都有 throws。
方法体中没有 throw 语句,只要当前方法可能发生异常,你有不处理的,也要加 throws。
对比:throw 和 throws
throw | throws | |
---|---|---|
编写的位置不同 | 方法体 { }里 | 【修饰符】 返回值类型 方法名(【形参列表】)throws 异常类型 |
后面跟的东西不同 | throw 异常对象; | throws 异常的类型们 |
含义不同 | 主动抛出异常对象,只要这句语句执行了,就代表异常确实发生了 | throws 只是表示可能发生 xx 类型的异常,需要调用者处理,并不代表这个异常真的发生。 |
package com.atguigu.exception;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestThrows {
@Test
public void test1() {
try {
copy("d:\\1.txt", "d:\\上谷歌\\3.txt");
System.out.println("复制成功");
} catch (IOException e) {
System.out.println("复制失败");
}
//这里想要实现一个效果,复制成功,打印复制成功,否则打印复制失败
}
//代表copy方法内部(1)可能发生IOException(2)没有处理IOException异常
//由调用者test1处理
public void copy(String srcFile, String destFile) throws IOException {
try(
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
) {
byte[] data = new byte[1024];
while (true) {
int len = fis.read(data);
if (len == -1) {
break;
}
fos.write(data, 0, len);
}
}catch (IOException e){
throw e;//继续抛出 e是异常对象
}
}
}
3.2.6 方法的重载与方法的重写的区别(完整版)
方法的重载(Overload) | 方法的重写(Override) | |
---|---|---|
位置 | 同一个类或父子类中 | 父子类 |
方法名 | 相同 | 相同 |
形参列表(个数、类型、顺序,与形参名无关) | 不同 | 相同 |
返回值类型 | 不看 | 总的来说:<= 细说:基本数据类型和 void 必须相同,引用数据类型必须是<= |
权限修饰符 | 不看 | >=,不能是 private |
其他修饰符 | 不看 | static,final 不能重写 |
throws 异常类型 | 不看 | 总的来说:<= |
总结:重写的要求口诀,两同两小一大,三不能
关于 throws 异常类型重写方法时的详细要求:
- 如果被重写方法的签名中没有 throws 编译时类型的异常,重写时不可以 throws 编译时类型的异常
- 如果被重写方法的签名中有 throws 编译时类型的异常,重写时可以继续 throws 编译时类型的异常,但是异常的类型必须满足<=的关系
package com.atguigu.exception;
import java.io.IOException;
public class Father {
public void m1(){
//...
}
public void m2()throws IOException {
//...
}
public void m3()throws IOException {
//...
}
public void m4()throws IOException {
//...
}
public Object m5(){
return new Object();
}
public String m6(){
return "hello";
}
void m7(){
//...
}
}
package com.atguigu.exception;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Son extends Father{
/* @Override
public void m1() throws IOException {
///..
}*/
//如果被重写方法的签名中没有throws 编译时类型的异常,重写时不可以throws编译时类型的异常
@Override
public void m2() throws IOException {
//,...
}
//FileNotFoundException < IOException
//因为FileNotFoundException extends IOException
@Override
public void m3() throws FileNotFoundException {
//...
}
//Exception > IOException
/* @Override
public void m4() throws Exception {//错误
///
}*/
@Override
public String m5() {
return "hello";
}
/* @Override
public Object m6() {
return new Object();
}*/
@Override
public void m7() {
///...
}
}
package com.atguigu.exception;
import java.io.IOException;
public class TestSon {
public static void main(String[] args) {
//多态引用,父类的变量指向子类的对象
Father f = new Son();
//编译时看左边,运行时看右边
//编译时看父类,按照父类的方法没有异常发生,实际执行Son类方法时,也不能发生异常,否则不安全
f.m1();
//编译时看父类,按照父类的异常进行try-catch,实际执行Son类方法时,发生的异常必须 <=,否则catch不住
try {
f.m2();
} catch (IOException e) {
e.printStackTrace();
}
try {
f.m3();
} catch (IOException e) {
e.printStackTrace();
}
try {
f.m4();
} catch (IOException e) {
e.printStackTrace();
}
//编译时看父类,按照父类中方法的返回值类型接收,实际Son类中返回的结果只能更小,否则接不住
Object obj = f.m5();
System.out.println("obj = " + obj);
String s = f.m6();
System.out.println("s = " + s);
f.m7();//父类编译能找到这个方法,运行时也得能找到这个方法
}
}
3.2.7 对象克隆(了解)
java.lang.Object 根父类中有一个方法:
protected Object clone() throws CloneNotSupportedException
父类声明的方法,子类会继承。子类重写这个方法时,必须同时实现 Cloneable 接口,否则会发生 CloneNotSupportedException 异常。
protected:本类(这里是 Object 本类中) + 本包(这里 java.lang 本包)+ 其他包的子类中。
package com.atguigu.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Circle implements Cloneable{
private double radius;
//权限修饰符可以往大了改
//返回值类型可以往小了改
@Override
public Circle clone() throws CloneNotSupportedException {
return (Circle) super.clone();//调用Object类的方法
}
}
package com.atguigu.clone;
import com.atguigu.bean.Circle;
import org.junit.jupiter.api.Test;
public class TestClone {
@Test
public void test1() throws CloneNotSupportedException {
Circle c1 = new Circle(2.5);
Circle c2 = c1.clone();//错误,因为Circle类是Object的子类,得在Circle类中才能直接调用clone方法
System.out.println(c1);
System.out.println(c2);
}
}