一、什么是异常
什么是异常,java提供异常处理机制有什么用?
程序在执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常。
无论是错误还是异常都是可抛出的,只要错误发生,java程序只有一个结果那就是终止程序的执行,退出jvm。
错误是不能够处理的
java语言是很完善的语言,提供了异常的处理方式,程序在执行过程中出现了不正常情况java把该异常信息打印输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加的健壮。
总结:
什么是异常:程序执行过程中的不正常情况。
异常的作用:增强程序的健壮性。
异常中的关键字:try、catch、finally、throws、throw
try、catch、finally:异常捕捉
throws:在方法声明位置上使用,表示上报异常信息给调用者。
throw:手动抛出异常。
二、异常的存在形式
在java语言中异常是以什么形式存在的呢?
public class Exception {
public static void main(String[] args) {
//通过异常“实例化”异常对象
NumberFormatException nfe = new NumberFormatException("数字格式化异常!");
// java.lang.NumberFormatException:数字格式化异常发生了!
System.out.println(nfe);
//通过异常类创建异常对象
NullPointerException npe = new NullPointerException("空指针异常发生了!");
// java.lang.NullPointerException:空指针异常发生了!
System.out.println(npe);
}
}
通过以上代码我们了解到了:
1、异常在java中以类的形式存在,每一个异常都可以创建异常对象。
2、在JVM观察到异常信息后,会new异常对象。
异常分为编译时异常(ExceptionSubClass)与运行时异常 (RuntimeException)
编译时异常(ExceptionSubClass):所有Exception的直接子类,都叫做编译时异常。编译时异常是在编译阶段发生的吗?不是。编译时异常是表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器报错。(发生概率较低)
运行时异常 (RuntimeException):所有的RuntimeException及子类都属于运行时异常。运行时异常在编写程序阶段,你可以选择处理,也可以不处理。(发生概率较高)
编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
编译时异常因为什么而得名 ?
因为编译时异常必须在编译(编写) 阶段预先处理,如果不处理编译器报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。
因为异常的发生就是new异常对象。
三、异常处理的两种方式
一、在方法声明的位置上,使用throws关键字,抛给上一级
(谁调用我,我就抛给谁。抛给上一级。)
二、使用try..catch语句进行异常的捕捉。
(这件事发生了,谁也不知道,因为我给抓住了)
思考:
异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种方式。
注意:
java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。
关于编译时处理异常示例:
public class Exception {
public static void main(String[] args) {
// main万法中调用doSome()方法
// 因为doSome()方法声明位置上有:throws ClassNotFoundException我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
// 如果不处理,编译器就报错。
// 编译器报错信息: Unhandled exception: java.Lang.CLassNotFoundException
doSome();
}
/**
*doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
* 这个代码表示doSome()方法在执行过程中,有可能会出现CLassNotFoundException异常。
* 叫做类没找到异常。这个异常直接父类是:Exception,所以CLassNotFoundException属于编译时异常
* @throws ClassNotFoundException
*/
public static void doSome () throws ClassNotFoundException{
System.out.println("doSome!!!!");
}
}
处理方法1、在方法声明的位置上继续使用: throws,来完成异常的继续上抛。抛给调用者。
public class Exception {
public static void main(String[] args) throws ClassNotFoundException{
doSome();
}
public static void doSome () throws ClassNotFoundException{
System.out.println("doSome!!!!");
}
}
处理方法2、try...catch进行捕捉。
public class Exception {
public static void main(String[] args) {
try {
doSome();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static void doSome () throws ClassNotFoundException{
System.out.println("doSome!!!!");
}
}
注意:只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行。try..catch捕捉异常之后,后续代码可以执行。
一个方法体中的代码出现异常之后,如果继续上报的话,此方法结束
四、try...catch
1、catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型
2、catch 可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
3、catch写多个的时候,从上到下,必须遵守从小到大。
public class Exception {
public static void main(String[] args) {
try{
FileInputStream fis = new FileInputStream("MySQL");
} catch (IOException e){
System.out.println("文件报错了");
} catch (FileNotFoundException e){
System.out.println("文件不存在");
//这里会报错,因为IOException的范围比FileNotFoundException的范围大,但异常的书写顺序必须遵守从小到大。
}
}
}
五、try...catch中的finally子语句
在finally子句中的代码是最后执行的,并且是一定会执行的,即使try 语句块中的代码出现了异常。 finally 子句必须和try一起出现,不能单独编写。
import java.io.FileInputStream;
import java.io.IOException;
public class Exception {
public static void main(String[] args) {
FileInputStream fis = null;
try{
//创建输入流对象
fis = new FileInputStream("D:\\Java笔记\\java中的随机数.md");
String s = null;
//这里出现空指针异常
s.toString();
System.out.println("Hellow! World");
} catch (IOException | NullPointerException e){
e.printStackTrace();
System.out.println("这里");
} finally {
//finally代码是一定会执行的,即使try语句块出现了异常
if (fis != null){
System.out.println("未访问成功");
try{
//流使用完要关闭,因为流会占用资源
fis.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
}
}
六、final、finally、 finalize之间的区别
final关键字:
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值
finally关键字:
和try一起联合使用
finally语句块中的代码是必须执行的
finalize标识符
是一个object类中的方法名
这个方法是由垃圾回收器GC负者调用的
七、如何自定义异常
1、编写一个类继承Exception或RuntimeException。
2、提供两个构造方法,一个无参数的,一个带有String参数的。
//编写一个类继承Exception
public class MyException extends Exception{
//提供两个构造方法,一个无参数的,一个带有String参数的
public MyException() {
}
public MyException(String message) {
super(message);
}
}
//编写一个类继承RuntimeException
public class MyRuntimeException extends RuntimeException{
//提供两个构造方法,一个无参数的,一个带有String参数的
public MyRuntimeException() {
}
public MyRuntimeException(String message) {
super(message);
}
}
public class ExceptionTest {
public static void main(String[] args) {
//只new了异常对象没有手动抛出
MyException ME = new MyException("这是一个编译时异常");
MyRuntimeException MRE = new MyRuntimeException("这是一个运行时异常");
//打印异常堆信息
MRE.printStackTrace();
ME.printStackTrace();
}
}
八、异常作业练习
01、程序开始执行时,提示用户输入“用户名”和“密码"信息。
注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。
完成注册的方法放到一个单独的类中。
异常类自定义即可。
public void register(String username, String password){ //这个方法中完成注册 }
编写main方法,在main方法中接收用户输入的信息,在main方法中调用UserService的register方法完成注册。
解答:
UserService类
public class UserService {
String name;
String password;
/**
*
* @param username 用户名
* @param password 密码
* @throws MyException 当用户名为null,或者用户名长度小于6,或者长度大于14,都会出现该异常!
*/
public void register(String username, String password) throws MyException{
//判断用户名是否合法,长度必须在6~14之间
if(null == username || username.length() < 6 || username.length() > 12){
throw new MyException("对不起您输入的用户名不合法");
} else {
//程序能到这说明用户名合法
System.out.println("恭喜您注册成功");
}
}
}
MyException类
/**
* 自定义异常
*/
public class MyException extends Exception{
public MyException(){
}
public MyException(String message){
super(message);
}
}
RegisterTest类
import java.util.Scanner;
public class RegisterTest {
public static void main(String[] args) {
//接受用户键盘输入
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的用户名");
String admin = sc.next();
System.out.println("请输入您的密码");
String password = sc.next();
//创建UserService对象
UserService rt = new UserService();
try {
rt.register(admin, password);
} catch (MyException e) {
//获取异常的信息
System.out.println(e.getMessage());
}
}
}
02、写一个类Army,代表一支军队,这个类有一个属性weapon数组w(用来存储该军队所拥有的所有武器)
该类还提供一个构造方法,在构造方法里通过传一个int类型的参数来限定该类所能拥有的最大武器数量,
该类还提供一个方法addweapon(Weapon wa),表示把参数wa所代表的武器加入到数组w中。
在这个类中还定义两个方法attackAll()让w数组中的所有武器攻击;
以及moveAll( )让w数组中的所有可移动的武器移动。
写一个主方法去测试以上程序。
提示:
Weapon是一个父类。应该有很多子武器。
这些子武器应该有一些是可移动的,有一些是可攻击的。
解答:
Army类
public class Army {
Weapon[] w;
/**
* 编写有参构造方法来动态初始化w数组的长度
* @param num 表示编写的数组的长度
*/
public Army(int num){
this.w = new Weapon[num];
}
/**
* 编写方法来添加武器(将武器添加到w数组中)
* @param wa 添加的武器的引用
* @throws StackFull 这是一个添加达到最大值的异常,如果达到最大值则触发异常
*/
public void addWeapon(Weapon wa) throws StackFull{
for(int i = 0; i <= w.length - 1; i++){
//if语句如果进行遍历查看后有空位的话,就将新武器放入到数组中
if(null == w[i]){
w[i] = wa;
return;
}
}
//如果程序执行到此处,代表武器没有添加成功。此时发生异常。
throw new StackFull("对不起武器库已满");
}
/**
* 移动武器的方法
*/
public void moveAll(){
for (int i = 0; i < w.length; i++){
//用instanceof关键字判断Moveable是否为w数组的子类如果是则为true
if (w[i] instanceof Moveable){
//向下强转型
Moveable move = (Moveable) w[i];
move.Move();
}
}
}
/**
* 武器的攻击方法
*/
public void attackAll(){
for (int i = 0; i < w.length; i++){
//用instanceof关键字判断Shootable是否为w数组的子类如果是则为true
if (w[i] instanceof Shootable){
//向下强转
Shootable shoot = (Shootable) w[i];
shoot.Shoot();
}
}
}
}
自定义异常StackFull类
public class StackFull extends Exception{
public StackFull(){}
public StackFull(String message){
super(message);
}
}
武器类Tank
public class Tank extends Weapon implements Moveable, Shootable{
@Override
public void Move() {
System.out.println("坦克移动了");
}
@Override
public void Shoot() {
System.out.println("坦克射击了");
}
}
武器类TripleA
public class TripleA extends Weapon implements Shootable{
@Override
public void Shoot() {
System.out.println("高射炮开始射击!");
}
}
武器类SST
public class SST extends Weapon implements Moveable{
@Override
public void Move() {
System.out.println("运输机在移动");
}
}
接口Moveable
public interface Moveable {
void Move();
}
接口Shootable
public interface Shootable {
void Shoot();
}
武器库类Weapon
public class Weapon {
}
测试类Test
public class Test {
public static void main(String[] args) {
//创建一个长度为3的武器库
Army army = new Army(3);
//初始化所有的武器对象
Tank tank = new Tank();
TripleA tripleA = new TripleA();
SST sst = new SST();
SST sst1 = new SST();
try {
//将武器放入武器库中
army.addWeapon(tank);
army.addWeapon(tripleA);
army.addWeapon(sst);
army.addWeapon(sst1);
} catch (StackFull e) {
//打印出异常信息
System.out.println(e.getMessage());
}
//调用方法使武器移动与攻击
army.moveAll();
army.attackAll();
}
}