异常
什么是异常?
- Java 中的错误大致可以分为两类:一类是编译时错误,一般指语法错误;另一类是运行时错误。
- 编译时的错误在程序编译时就会暴露出来,会导致程序编译失败。IDE 集成开发环境都会对这种错误进行提示,我们在编写代码时就能看到错误。
- 运行时错误在我们编写代码的过程中以及程序编译期间难以发现,甚至可以正常编译通过,但一旦运行就会报错,这类错误一般不容易发现。
- Java 中有一组类专门来描述各种不同的运行时错误,叫做异常类,Java 结合异常类提供了处理错误的机制,具体步骤就是当程序出现错误时,会创建一个包含错误信息的异常类的实例化对象,并将该对象提交给系统,由系统装交给能处理该异常类的代码进行处理。
- 异常分为两类,包括 Error 和 Exception。Error 指系统错误,由 Java 虚拟机生成,我们编写的程序无法处理。Exception 指程序运行期间出现的错误 ,我们编写的程序可以对其进行处理。
异常的使用
异常的使用需要用到两个关键字 try 和 catch ,并且这两个关键字需要结合起来使用,用 try 来监听可能会抛出异常的代码,一旦捕获到异常,生成异常对象并交给 catch 来处理,基本语法如下:
try{
//可能抛出的异常
}catch(异常对象){
//处理异常
}
比如:
public class Test{
public static void main(String[] args){
try{
int num = 10/0;
}catch(Exception e){
e.printStackTrace();
}
}
}
这段代码就会在运行时报错,因为除数为 0;所以程序在执行时会产生错误并自动生成一个 Exception 对象,在 catch 代码块中捕获 Exception 对象并进行处理,将错误信息打印出来。
- 通常除了使用 try 和 catch 关键字,我们还需要用到 finally 关键字,finally 里面的代码无论程序是否抛出异常都会执行。基本语法如下:
try{
//可能抛出异常
}catch(异常对象){
//处理异常
}finally{
//必须执行的代码
}
示例:
public class Test{
public static void main(String[] args){
System.out.println(test());
}
pulbic static int test(){
try{
System.out.println("try...");
return 0;
}catch(Exception e){
}finally{
System.out.println("finally...");
return 20;
}
}
}
运行结果:
try…
finally…
20
通过结果可以看到,虽然 try 代码块中执行了 return 操作,但是 finally 代码块中的程序已然会执行,并且会覆盖 try 中 return 的结果,返回给外部调用者,正是因为 finally 的这个特性,一般在 finally 中进行释放资源的操作
异常类
Java 将运行时出现的错误全部封装成类,并且不是一个类,而是一组类。这些类之间是有层级关系的,有树状结构一层层向下分级,处在最顶端的类是 Throwable ,是所有异常类的根结点。Throwable 有两个直接子类:Error 和 Exception。Throwable、Error、Exception 都存放在 java.lang 包中。
Error 常见的子类有 VirtualMachineError、AWTError、IOError。更加具体的自己去了解。
Exception 常见的子类主要有 IOException 和 RuntimeException 。 IOException 存放在 java.io 包中,RuntimeException 存放在 java.lang 包中。Exception 类要重点关注,因为这部分异常是需要我们在编写代码的过程中手动进行处理的
一下这些类全部存放在 java.lang 包中
- RuntimeException 的常用子类如下:
- ArithmeticException :表示数字运算异常;
- ClassNotFoundException : 表示类未定义异常;
- IllegalArgumentException : 表示参数格式错误异常;
- ArrayIndexOutOfBoundsException : 表示数组下标越界异常;
- NullPointerException : 表示空指针异常;
- NoSuchMethodError : 表示方法未定义异常;
- NumberFormatException : 表示将其他数据类型转为数值类型时的不匹配异常。
throw 和 throws
throw 和 throws 是 Java 在处理异常时使用的两个关键字,都用来抛出异常,但是使用方法以及表示的含义完全不同。
Java 中抛出异常有 3 种方式:
- 第 1 种是我们之前介绍过的使用 try-catch 代码块捕获异常。这种方式其实是一种防范机制,即代码中有可能会抛出异常。如果抛出,则捕获并进行处理;如果不抛出,则程序继续向后执行 。
- 使用 throw 是开发者主动抛出异常,即读到 throw 代码就一定会抛出异常,基本语法:
throw new Exception()
,这是一种基于代码的逻辑判断而主动抛出的异常的方式。
示例:
public class Test{
public static void main(String[] args){
String str = "Java";
if(str.equals("Java")){
throw new NumberFormatException();
}else{
int num = Integer.parseInt(str);
}
}
}
运行结果:
分析:上述代码中,我们主动对 str 进行判断,如果 str 的值为 Java,则直接抛出 NumberFormatException 异常。所以 try-catch 是捕获可能抛出的异常,throw 是确定会抛出异常,这是二者的区别
- throws 是作用于方法,用来描述该方法可能会抛出的异常
示例:
public class Test{
public static void main(String[] args){
try{
test();
}catch(NumberFormatException e){
e.printStackTrace();
}
}
public static void test() throws NumberFormatException{
String str = "Java";
int num = Integer.parseInt(str);
}
}
分析: test() 方法在定义时通过 throws 关键字声明了该方法可能会抛出 NumberFormatException 异常,所以我们在调用该方法时,需要手动使用 try-catch 进行捕获。同时 catch 代码块中可以捕获 NumberFormatException,也可以捕获 Exception。着两种方法都是没有问题的,这就是 Java 的多态机制,Exception 是所有异常的父类,NumberFormatException 也可以理解成 Exception 的另一种表现形式。在调用 test() 方法时,可以使用 try-cathc 主动捕获,也可以不添加 try-catch,直接交给 Java 虚拟机来处理异常。所以可以不加 try-catch ,但是建议添加。既然我们在 catch 中可以使用多态来捕获异常,那么在定义方法时也可以使用多态来描述可能会发生的异常,把 NumberFormatException 换成 Exception 就可以了
但是在这种情况下,main 方法调用 test 方法时就必须手动进行捕获。这里需要注意,如果方法抛出 RunntimeException 异常或者其子类异常,在外部调用方法时可以不进行 try-catch 捕获。如果方法抛出的是 Exception 异常或者其子类,则外部调用时必须进行 try-catch 捕获,否则会报错;如果不添加 try-catch ,也可以通过让 main 方法抛出该异常的方式来解决这个错误
public class Test{
public static void main(String args) throws Exception{
test();
}
public static void test() throws Exception{
String str = "Java";
int num = Integer.parseInt(str);
}
}
分析:test() 方法声明时会抛出 Exception ,主方法中的代码在调用 test() 方法时需要对异常进行处理,这里选择将异常抛出,抛出异常之后是交给 Java 虚拟机来处理了(不建议这种做法)
自定义异常类
在实际开发中,我们除了使用 Java 提供的异常类之外,也可以根据需求来自定义异常类,比如定义一个方法,对传入的参数进行 ++ 操作并返回,同时要求参数必须是整型,如果传入的参数不是整数类型则抛出自定义异常,具体实现代码如下:
class MyNumberException extends Exception {
public MyNumberException(String error) {
super(error);
}
}
public class TestMyException {
public int add(Object object) throws MyNumberException {
//instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
if(!(object instanceof Integer)) {
String error = "传入的参数不是整数类型";
throw new MyNumberException(error);
} else {
int num = (int) object;
return num++;
}
}
public static void main(String[] args) {
TestMyException test = new TestMyException();
try {
int num = test.add("hello");
}catch (MyNumberException e) {
e.printStackTrace();
}
}
}
运行结果:
分析:add() 方法定义时声明了可能会抛出 MyNumberException 异常,是 Exception 的子类,所以在 main 方法中 调用 add() 方法时需要手动处理。我们也可以让 main 方法抛出异常,推荐使用 try-catch 的方式
Java 中有些异常在 throw 之后,还需要在方法定义出添加 throws 声明,有些异常则不需要,直接 throw 即可。这是因为 Exception 的异常分 checked exception 和 runtime exception ,checked exception 表示需要强制去处理的异常,即 throw 异常之后需要立即处理,要么自己 try-catch,要么抛给上一层去处理,否则会报错;而 runtime exception 没有这个限制,throw 之后可以不处理。直接继承自 Exception 的类就是 checked exception ,继承自 RuntimeException 的类就是 runtime exception。我们自己定义的 MyNumberException 是直接继承 Exception 的,所以需要在 add() 方法定处声明 throws MyNumberException。
综合练习
使用面向对象章节所学的知识点,重点包括封装、继承、多态、抽象、接口来完成一个汽车查询系统。
需求描述:共有 3 中类型的汽车:小轿车、大巴车、卡车,期中小轿车座位数为 4 座,大巴车座位数为 53 座,卡车座位数为2 座,要求使用封装、继承、抽象来完成车辆的定义。
可以对车辆信息作出修改,卡车可以运货三十载重量不能超过 12 吨,使用自定义异常来处理错误,小轿车和大巴车没有此功能,要求使用接口来实现。
示例:
public interface Container {
public int getweight();
}
public class CarException extends Exception {
public CarException(String message) {
super(message);
}
}
//定义一个抽象类,车类
public abstract class Car {
private String name;
private String color;
public String getName() {
return name;
}
public String getColor() {
return color;
}
public Car(String name, String color) {
this.name = name;
this.color = color;
}
public abstract String seatNum();
}
//小轿车
class Sedan extends Car{
public Sedan(String name, String color) {
super(name,color);
}
@Override
public String seatNum() {
return "4座";
}
}
//大巴车
class Bus extends Car{
public Bus(String name, String color) {
super(name, color);
}
@Override
public String seatNum() {
return "53座";
}
}
//卡车
class Truck extends Car implements Container{
private int weight;
public Truck(String name, String color, int weight) {
super(name, color);
this.weight = weight;
}
@Override
public String seatNum() {
return"2座";
}
public int getweight() {
return this.weight;
}
}
public class CarTest {
private static Scanner scanner;
private static Sedan sedan;
private static Bus bus;
private static Truck truck;
private static Car[] cars;
static {
scanner = new Scanner(System.in);
sedan = new Sedan("小轿车","黑色");
bus = new Bus("大巴车","绿色");
truck = new Truck("卡车","红色",2);
cars = new Car[3];
cars[0] = sedan;
cars[1] = bus;
cars[2] = truck;
}
public void showCars() throws CarException {
System.out.println("车辆名称\t\t车辆颜色\t\t座位\t\t载重量");
for(Car car : cars) {
if(car instanceof Sedan) {
Sedan sedan = (Sedan) car;
System.out.println(sedan.getName()+"\t\t"+sedan.getColor()+"\t\t"+sedan.seatNum());
}
if(car instanceof Bus) {
Bus bus = (Bus) car;
System.out.println(bus.getName()+"\t\t"+bus.getColor()+"\t\t"+bus.seatNum());
}
if(car instanceof Truck) {
Truck truck = (Truck) car;
System.out.println(truck.getName()+"\t\t"+truck.getColor()+"\t\t"
+truck.seatNum()+"\t\t"+truck.getweight());
}
}
System.out.println("1.小轿车\t2.大巴车\t3.卡车");
System.out.print("请选择要修改的车辆:");
int num = scanner.nextInt();
switch(num) {
case 1:
update("sedan");
break;
case 2:
update("bus");
break;
case 3:
update("truck");
break;
default:
System.out.println("车辆不存在");
}
}
public void update(String type) throws CarException {
String name = null;
String color = null;
if(type.equals("sedan")) {
System.out.print("请输入车辆的名称:");
name = scanner.next();
System.out.print("请输入车辆的颜色:");
color = scanner.next();
Sedan sedan = new Sedan(name,color);
cars[0] = sedan;
}
if(type.equals("bus")) {
System.out.print("请输入车辆的名称:");
name = scanner.next();
System.out.print("请输入车辆的颜色:");
color = scanner.next();
Bus bus = new Bus(name,color);
cars[1] = bus;
}
if(type.equals("truck")) {
System.out.print("请输入车辆的名称:");
name = scanner.next();
System.out.print("请输入车辆的颜色:");
color = scanner.next();
System.out.print("请输入载重量:");
int weight = scanner.nextInt();
if(weight > 12) {
try {
throw new CarException("卡车的最大载重量为 12吨");
}catch(CarException e) {
e.printStackTrace();
return;
}
}
Truck truck = new Truck(name,color,weight);
cars[2] = truck;
}
showCars();
}
public static void main(String[] args) {
CarTest carTest = new CarTest();
try {
carTest.showCars();
} catch (CarException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果: