java中的异常处理机制
异常在java中以类和对象的形式存在,那么异常的继承结构是怎样的?我们可以使用UML图来描述以下继承结构
画UML图的工具:Rational Rose、starUML等
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常。(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常)
RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管)
编译时异常和运行时异常,都是发生在运行阶段,编译阶段异常是不会发生的。编译时异常因为什么而得名?
因为编译时异常必须在编译(编写 )阶段预先处理,如果不处理编译器报错,因此得名。 所有异常都是在运行时发生的。因为只有程序运行阶段才可以new对象。
因为异常的发生就是new对象
编译时异常和运行时异常有什么区别?
编译时异常一般发生的概率较高。如:
你看到外面下雨了,倾盆大雨。出门前会预料:如果不打伞,很可能会生病(生病是一种异常)。而且这个异常发生的概率很高,所以我们出门前要那一把伞。“拿一把伞”就是对“生病异常”发生之前的一种处理方式。
对于一些发生概率较高的异常,需要再运行之前对其进行预处理。
运行时异常一般发生的概率较低。如:
小明走在大街上,可能会被天上的飞机轮子砸到。被飞机轮子砸到也算是一种异常。但是这种异常发生概率较低。再出门值卡吗你没必要提前对这种发生概率较低的异常进行预处理。如果预处理这种异常,将“活的很累”。
假设你在出门之前,把能够发生的异常都预先处理,你这个人会更加的安全,但是你这个人会活得很累。
假设java中没有对异常进行划分,没有分为编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果?
首先,如果这样,程序肯定是绝对的安全的。但是程序员编写程序太累,到处都是处理异常的代码。
编译时异常还有其他名字:
运行时异常还有其他名字:
再次强调:所有异常都是发生在运行阶段的。
java语言中对异常的处理包括两种方式:
第一种方式:在方法生命的位置上,使用throws关键字,抛给上一级。
谁调用我,就抛给谁。抛给上一级。
第二种方式:使用try…catch语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我抓住了
如:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,”损失1000元“这就可以看作是一个异常发生了。我有两种处理方式。
第一种方式:我把这件事告诉我的领导【异常上抛】
第二种方式:我自己掏腰包把这个钱补上了。【异常的捕捉】
张三 - - - -> 李四 - - - - > 王五 - - - - > CEO
思考:
异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
注意:java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果:终止java程序的执行。
什么是UML?有什么用?
UML是一种统一建模语言。一种图标式语言(画图的)。
UML不是只有java中使用。只要是面向对象的编程语言,都有UML。一般画UML图的都是软件架构师或者是系统分析师,这些级别的人使用的。软件设计人员使用UML。
在UML图中可以描述类和类之间的关系、程序执行的流程、对象的状态等。
盖大楼和软件开发一样,一个道理。
盖大楼前,建筑师先画图纸。图纸上的一个一个符号都是标准符号,这个图纸画完,只要是搞建筑的都能看懂,因为这个图纸上标注的这些符号都是一种“标准语言”
在java软件开发中,软件分析师/设计师负责设计类,java软件开发人员必须要能看懂。
什么是异常,java提供异常处理机制有什么用?
/*
1、什么是异常,java提供异常处理机制有什么用?
以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常。
java是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常的情况,java把该异常信息供程序员参考。
程序员看到异常信息之后,可以对程序进行修改,让程序更加的健壮。
什么是异常:程序执行过程中的不正常情况
异常的作用:增强程序的健壮性。
2、以下程序执行控制台出现了:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionText01.main(ExceptionText01.java:11)
这个信息别我们成为:异常信息,这个信息是JVM打印的。
*/
public class ExceptionText01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a / b; //实际上JVM在执行到此处的时候,会new对象:new ArithmeticException("/ by zero");
//并且JVM将new的异常对象抛出,打印输出信息到控制台了
System.out.println(a+"/"+b+"="+c);
//此处运行也会创建一个:ArithmeticException类型的异常对象
//System.out.println(100/0);
//观察到异常信息之后,对程序进行修改,更加健壮。
/* int a = 10;
int b = 2;
if(b == 0){
System.out.println("除数不能为0");
return;
}
//程序执行到此处表示除数一定不是0
int c = a / b;
System.out.println(a+"/"+b+"="+c);*/
}
}
java中异常是以什么形式存在的?
/*
java语言中异常是以什么形式存在的呢?
1、异常在java中以类的形式存在,每一个异常类都可以创建异常对象。
2、异常在对应的现实生活这是怎样的?
火灾(异常类):
2008年8月8日 小明家着火了(异常对象)
2008年8月9日 小刚家着火了(异常对象)
2008年9月8日 小红家着火了(异常对象)
类是: 模板
对象是:实际存在的个体
钱包丢了(异常类):
2008年1月1日,小明的钱包丢了(异常对象)
2008年1月9日,小芳的钱包丢了(异常对象)
......
*/
public class ExceptionText02 {
public static void main(String[] args) {
//通过异常类“实例化”异常对象
NumberFormatException nfe = new NumberFormatException("数字格式化异常");
System.out.println(nfe);//java.lang.NumberFormatException: 数字格式化异常
//通过异常类“实例化”异常对象
NullPointerException npe = new NullPointerException("空指针发生了");
System.out.println(npe.toString());//java.lang.NullPointerException: 空指针发生了
}
}
运行时异常编写程序时可以不处理
//运行时异常编写程序时可以不处理
public class ExceptionText03 {
public static void main(String[] args) {
/*
程序执行到此处发生了ArithmeticException异常,底层new了一个ArithmeticException异常对象,然后抛出了
由于是main方法调用了100/0,所以这个异常ArithmeticException抛给了main方法,main方法没有处理
将这个异常自动抛给了JVM。JVM最终终止程序的执行。
ArithmeticException 继承 RuntimeException ,属于运行时异常。在编写阶段不需要对这种异常进行预先的处理。
*/
System.out.println(100/0);
//这里的hello world 没有输出,没有执行。
System.out.println("hello world");
}
}
编译时异常必须处理
/*
以下代码报错的原因是什么?
因为doSome()方法声明位置上使用了:throws ClassNotFoundException
而ClassNotFoundException是编译时异常。必须编写代码时处理,如不处理,编译报错
*/
public class ExceptionText04 {
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!!!!");
}
}
异常的处理方式有两种
public class ExceptionText05 {
//第一种处理方式:在方法声明的位置上继续使用:throws,来完成异常的继续上抛。抛给调用者
//上抛类似于推卸责任。(继续把异常传递给调用者)
/* public static void main(String[] args) throws ClassNotFoundException {
doSome();
}*/
//第二种处理方式:try..catch进行捕捉
//捕捉等于把异常拦下了,异常真正解决了!(调用者是不知道的)
public static void main(String[] args) {
try{
doSome();
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!!");
}
}
注意事项
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
处理异常的第一种方式:在方法声明的位置上使用throws关键字抛出,谁调用这个方法,我就抛给谁。抛给调用者来处理。
在方法声明的位置上使用throws关键字抛出,谁调用我这个方法,我就抛给谁,抛给调用者来处理.
这种处理异常的态度:上报.
处理异常的第二种方式:
使用try..catch语句对异常进行捕捉,这个异常不会上报,自己把这个事情解决了。异常抛出到此为止,不再上抛了。
注意:
只要异常没有捕捉,采用上抛的方式,此方法的后续代码不会执行
另外需要注意:try语句块中的某一行出现异常,后面的代码不会执行
try..catch捕捉异常之后,后续的代码可以执行.
在以后的开发中,处理编译时异常,应该上抛还是应该捕捉呢?
如果希望调用者来处理,选择throws上抛。
其他情况使用捕捉的方式
*/
public class ExceptionText06 {
/*
一般不建议在main方法上使用throws,因为这个异常如果真的发生了,一定会抛给JVM,JVM只有终止
异常处理机制的作用是增强程序的健壮性。怎么能做到,异常发生了也不会影响程序的执行。
所以一般main方法中的异常建议使用try..catch进行捕捉。main就不要继续上抛了。
*/
// public static void main(String[] args) throws FileNotFoundException {
public static void main(String[] args) {
//100/0是算术异常,这个异常是运行时异常,你在编译阶段,可以处理,也可以不处理。编译器不管
//System.out.println(100/0);//不处理编译器也不管
//处理以下也可以
/* try{
System.out.println(100/0);
}catch(ArithmeticException e){
System.out.println("算术异常了!!!");
}*/
System.out.println("main begin");
try {
//try 尝试
m1();
//以上代码出现异常,直接进入catch语句进行捕捉
System.out.println("helloworld");
} catch (FileNotFoundException e) {//catch后面的好像一个形参
//这个分支中可以使用e引用,e引用保存的的内存地址是那个new出的异常对象的内存地址.
//catch是捕捉异常之后走的分支。
//在catch分支中干什么?处理异常
System.out.println("文件不存在,可能路径错误,也可能该文件被删除了");
System.out.println(e.toString());
}
//try..catch把异常抓住之后,这里的代码会继续执行
System.out.println("main over");
}
public static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
//以上代码出现异常,这里也不会执行.
System.out.println("m1 over");
}
// private static void m2() throws ClassCastException {
// 抛别的不行,抛ClassCastException说明你还是没有对FileNotFoundException进行处理
// private static void m2() throws IOException {
//抛FIleNotFoundException的父对象IOException,这样是可以的,因为IOException包括FileNotFoundException
// private static void m2() throws Exception {
//这样也可以,因为Exception包括所有的异常.
// private static void m2() throws ClassCastException,FileNotFoundException {
//throws也可以写多个异常,可以使用逗号隔开
private static void m2() throws FileNotFoundException {
System.out.println("m2 begin");
//编译报错的原因是:m3()方法声明位置上有:throws FileNotFoundException
//我们在这里调用m3()方法没有对异常进行预处理,所以编译报错
m3();
//以上如果出现异常,下面的代码是不会执行的
System.out.println("m2 over");
}
private static void m3() throws FileNotFoundException {
//调用SUN jdk中的某个类的构造方法
//这个类还没接触过,后期IO流的时候就知道了。我们只是借助这个类学习一下异常处理机制。
//创建一个输入流对象,该流指向一个文件。
/*
编译报错的原因是什么?
第一:这里调用了一个构造方法:FileInputStream(String name)
第二:这个构造方法的生命位置上有:throws FileNotFoundException
第三:通过类的继承结构可以看到:FileNotFoundException父类是IOException,IOException的父类是Exception
最终得知:FileNotFoundException是编译时异常。
错误原因:编译时异常要求程序员编写程序阶段必须对他进行处理,不处理编译器就报错
*/
//new FileInputStream("D:\\360Downloads\\Software\\漏洞补丁目录\\dd.txt");
//我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。
//一个方法当中的代码出现异常之后,如果上报的话,此方法结束.
new FileInputStream("D:\\360Downloads\\Softwaress\\漏洞补丁目录\\dd.txt");
System.out.println("如果new FIleInputStream出现异常,则这里的代码也不会执行");
}
}
深入try…catch
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
深入try..catch
1、catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型
2、catch可以写多个。建议catch的时候,精确的一个一个处理,这样有利于程序的调试。
3、catch写多个的时候,从上到小,必须遵守从小到大。
*/
public class ExceptionText07 {
public static void main(String[] args) {
/* try {
FileInputStream fis = new FileInputStream("D:\\360Downloads\\Software\\漏洞补丁目录\\ddd.txt");
System.out.println("以上出现异常,此处无法执行");
} catch (FileNotFoundException e) {
System.out.println("文件不存在");
}
System.out.println("helloworld");*/
/* try {
FileInputStream fis = new FileInputStream("D:\\360Downloads\\Software\\漏洞补丁目录\\ddd.txt");
} catch (IOException e) {//多态:IOException e = new FileNotException();
System.out.println("文件不存在");
}*/
//不够精确
/* try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\360Downloads\\Software\\漏洞补丁目录\\ddd.txt");
//读文件
fis.read();
} catch (Exception e) {//所有的异常都走这个分支.
System.out.println("文件不存在");
}*/
/* try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\360Downloads\\Software\\漏洞补丁目录\\ddd.txt");
//读文件
fis.read();
} catch (FileNotFoundException e) {
System.out.println("文件不存在");
}catch (IOException e){
System.out.println("读文件报错了");
}*/
//编译报错:Exception 'java.io.FileNotFoundException' has already been caught
// 已经被捕捉过了,因为IOException是FIleNotFoundException的父类型。
/* try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\360Downloads\\Software\\漏洞补丁目录\\ddd.txt");
//读文件
fis.read();
}catch (IOException e){
System.out.println("读文件报错了");
} catch (FileNotFoundException e) {
System.out.println("文件不存在");
}*/
/*
JDK8的新特性:
catch中的异常类型中间可以采用“或(|)”的方式
*/
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\360Downloads\\Software\\漏洞补丁目录\\ddd.txt");
//进行数学运算
System.out.println(100/0);//这个异常是运行时异常,编写程序时可以处理,也可以不处理
}catch (FileNotFoundException | ArithmeticException | NullPointerException e) {
System.out.println("文件不存在?算术异常?空指针异常?都有可能!");
}
}
}
异常对象的两个非常重要的方法
/*
异常对象有两个非常重要的方法:
获取异常简单的描述信息:
String msg = exception.getMessage();
打印异常追踪的堆栈信息:
exception.printStackTrace();
*/
public class ExceptionText08 {
public static void main(String[] args) {
//这里只是为了测试getMessage()方法和printStackTrace()方法
//这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的java对象。
NullPointerException npe = new NullPointerException("空指针异常对象npe");
//获取异常简单描述信息:这个信息实际上就是构造方法上的String参数。
String msg = npe.getMessage();
System.out.println(msg);
//打印异常堆栈信息。java后台打印异常堆栈追踪信息处理信息的时候,采用了异步线程的方式打印的
npe.printStackTrace();
for (int i = 0; i <500 ; i++) {
System.out.println(i);
}
System.out.println("helloworld");
}
}
如何查看异常的追踪信息
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/*
异常对象的两个方法:
String msg = e.getMessage();
e.printStackTrace (一般使用这个)
我们以后查看异常的追踪信息,应该怎么看,可以快速的调试程序呢?
异常信息追踪信息,从上往下一行一行看。但是要注意的是:SUN写的代码就不用看了(看包名就知道是自己写的还是SUN写的)
主要问题出现在自己编写的代码上面。
*/
public class ExceptionText09 {
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
//获取异常的简单描述信息
System.out.println(e.getMessage());
//打印异常堆栈追踪信息!!!
//在实际的开发中,建议使用这个,养成好习惯。这行代码要写上,不然出问题了都不知道
e.printStackTrace();
/*
java.io.FileNotFoundException: D:\360驱动大师目录\下载保存目录\SeachDownload\eee.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.(FileInputStream.java:138)
at java.io.FileInputStream.(FileInputStream.java:93)
at ExceptionText09.m3(ExceptionText09.java:47)
at ExceptionText09.m2(ExceptionText09.java:43)
at ExceptionText09.m1(ExceptionText09.java:39)
at ExceptionText09.main(ExceptionText09.java:16)
因为47行出问题导致43行出问题,43行出问题导致39行出问题,49行出问题导致16行出问题,应该先看47行,47行是代码错误根源
*/
//这里的程序不耽误执行,很健壮(服务器不会因为遇到异常而宕机)
System.out.println("helloworld");
}
}
private static void m1() throws FileNotFoundException {
m2();
}
private static void m2() throws FileNotFoundException {
m3();
}
private static void m3() throws FileNotFoundException {
new FileInputStream("D:\\360驱动大师目录\\下载保存目录\\SeachDownload\\eee.txt");
}
}
关于try…catch的finally子句
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Parameter;
/*
关于try..catch中的finally子句:
1、在finally子句中的代码是最后执行的,并且是一定会执行的,即使try..catch语句块中的代码出现了异常
finally子句必须和try一起出现,不能单独编写
2、finally语句通常使用在哪些情况下?
通常在finally语句块中完成资源的释放/关闭,因为finally中的代码有保障。
即使try语句块中的代码出现异常,finally中的代码也会执行。
*/
public class ExceptionText10 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
//创建输入流对象
fis = new FileInputStream("D:\\360驱动大师目录\\下载保存目录\\SeachDownload\\ee.txt");
//开始读文件....
String s = null;
//这里一定会出现空指针异常
s.toString();
System.out.println("helloworld");
//流使用完需要关闭,因为流是占用资源的
//即使以上程序出现异常,流也必须关闭。放在这里可能关闭不了
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
} finally {
System.out.println("hello paoke");
//流的关闭放在这里比较保险,finally中的代码是一定会执行的。即使try中的代码出现了异常,也可以执行。
//当fis为空时不执行
if(fis != null) {
try {
//close()方法有异常,采用捕捉的方式
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("hello kity");
}
}
try和finally,可以没有catch。
以及try中如果有return,finally怎么执行
//finally语句:放在finally语句块中的代码是一定会执行的。
public class ExceptionText11 {
public static void main(String[] args) {
/*
try和finally,没有catch可以吗?可以
try不能单独使用
try finally可以联合使用
以下代码的执行顺序:
先执行try..
在执行finally...
最后执行return(return语句只要执行,方法必然结束)
*/
try{
System.out.println("try....");
return;
}finally {
//fianlly中的语句会执行,能执行到
System.out.println("finally");
}
//Unreachable statement。这里不能写语句,因为这个代码是无法执行到的
//System.out.println("helloworld");
}
}
使用System.exit(0);退出JVM时,finally…
//finally
public class ExceptionText12 {
public static void main(String[] args) {
try{
System.out.println("try....");
//退出JVM
System.exit(0);//退出JVM之后,finally语句中的代码就不会执行了。
}finally {
System.out.println("finally");
}
}
}
finally面试题
//finally面试题
public class ExceptionText13 {
public static void main(String[] args) {
int result = m();
System.out.println(result);//100
}
/*
java语法规则(有一些规则是不能破坏的,一旦这么说了,必须这么做):
java中有一条这样的规则:
方法体中的代码必须遵循自上而下的顺序因此逐行执行(亘古不变的 语法)
java中还有一条这样的语法规则:
return语句一旦执行,整个方法必须结束。(更古不变的语法)
*/
private static int m() {
int i =100;
try{
//这行代码出现在int i = 100; 的下面,所以最终结果必须返回100
//return语句还必须保证是最后执行的,一旦执行,整个方法结束。
return i;
}finally {
i++;
}
}
}
/*
反编译之后的结果
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
*/
final、finally、finalize有什么区别?
/*final 、finally 、finalize有什么区别?
final 关键字
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值
finally 关键字
finally与try一起联合使用
finally语句块中的代码是一定会执行的
finalize 标识符
是一个Object类中的方法名
这个方法是垃圾回收器GC负责调用的。
*/
public class ExceptionText14 {
public static void main(String[] args) {
//final是一个关键字
final int i = 100;
//finally也是一个关键字,和try联合使用,使用在异常处理机制中,在finally语句块中的代码是一定会执行的
try{
}finally{
System.out.println("finally");
}
//finalize()是Object类中的一个方法,作为方法名出线。所以finalize是标识符
}
}
如何自己定义异常?
异常类
/*
1、SUN提供的JDK内置的异常肯定是不够用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的
和业务挂钩的,这些异常类我们可以自己去定义
2、java中怎么自定义异常呢?
两步:
第一步:编写一个类继承Exception或RuntimeException。
第二步:提供两个构造方法,一个无参的,一个带有String参数的。
死记硬背
*/
public class MyException extends Exception{//编译时异常:发生概率比较高的时候定义为编译时异常
public MyException(){
}
public MyException(String s){
super(s);
}
}
//public class MyExceptio exception RuntimeException{ //运行时异常:发生概率比较低的定义为运行时异常比较好
//
//}
创建异常对象
public class ExceptionText15 {
public static void main(String[] args) {
//创建异常对象(只是new对象,并没有手动抛出)
MyException e = new MyException("用户名不能为空");
//打印异常堆栈信息
e.printStackTrace();
//获取异常简单描述信息
String msg = e.getMessage();
System.out.println(msg);
}
}
对之前的数组模拟栈存储进行改进,使用异常进行错误的输出
改进后的栈类
public class MyStack {
private Object[] elements ;
private int index = -1;
public MyStack() {
this.elements = new Object[10];
}
public MyStack(int i) {
this.elements = new Object[i];
}
public Object[] getElements() {
return elements;
}
public void setElements(Object[] elements) {
this.elements = elements;
}
//压栈
public void push(Object object) throws MyStackOperationException {
if(this.index >= this.elements.length-1){
//学了异常之后对这个进行改进,不再使用输出语句,太low了
/*System.out.println("栈已满,压栈失败");
return;*/
/*//改进:
//创建异常对象
MyStackOperationException e = new MyStackOperationException("栈已满,压栈失败");
//手动将异常抛出去
throw e; //这里不能使用try捕捉,自己new了一个异常,自己捕捉,没有意义。栈已满的消息需要传递出去*/
//合并
throw new MyStackOperationException("栈已满,压栈失败");
}
elements[++index] = object;
System.out.println("元素 "+object+" 压栈成功,栈帧此时指向"+index);
}
//弹栈
public void pop() throws MyStackOperationException {
if(index <= -1){
/*System.out.println("栈已空,弹栈失败");
return;*/
throw new MyStackOperationException("栈已空,弹栈失败");
}
System.out.println("元素 " + elements[index] +" 弹栈成功,此时栈帧指向:"+ --index);
}
}
自定义的内存类
/**
* 栈操作异常:自定义异常
*/
public class MyStackOperationException extends Exception{//编译时异常
public MyStackOperationException(){
}
public MyStackOperationException(String s){
super(s);
}
}
测试类
//对改良之后的栈进行测试
//注意:最后这个例子,是异常最重要的案例,必须掌握,自定义异常在实际开发中的作用
public class ExceptionText16 {
public static void main(String[] args) {
//创建栈对象
MyStack stack = new MyStack();
//压栈、这里不能再上抛了,因为JVM发现异常之后,就会终止程序。
try {
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
//这里栈满了
stack.push(new Object());
} catch (MyStackOperationException e) {
//输出异常的简单信息
System.out.println(e.getMessage());
//打印异常信息
//e.printStackTrace();
}
//弹栈
try {
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
//这里栈空了
stack.pop();
} catch (MyStackOperationException e) {
//输出异常的简单信息
System.out.println(e.getMessage());
//打印异常信息
//e.printStackTrace();
}
}
}
方法重写时,重写后的方法不能比之前的方法抛出更多的异常
/*
之前在讲方法覆盖时,遗留的问题:
重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。
*/
public class ExceptionText17 {
public static void main(String[] args) {
}
}
class Animal{
public void doSome(){
}
public void doOther()throws Exception{
}
}
class Cat extends Animal{
//编译报错
//public void doSome() throws Exception{}
//编译通过
//public void doOther(){}
//编译通过
//public void doOther() throws Exception{}
//编译通过
public void doOther() throws NullPointerException{
}
}
/*
总结异常中的关键字:
异常捕捉:
try
catch
finally
throws 在方法声明位置上使用,表示上报异常信息给调用者
throw 手动抛出异常
*/
相关作业
编写程序模拟用户注册:
1、程序开始执行时,提示用户输入“用户名”和“密码”信息。
2、输入信息之后,后台java程序模拟用户注册。
3、注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。
注意:
完成注册的方法放到一个单独的类中。
异常类自定义即可。
class UserService {
public void register(String username,String password){
//这个方法中完成注册!
}
}
编写main方法,在main方法中接收用户输入的信息,在main方法
中调用UserService的register方法完成注册。
用户业务类
package homework;
//用户业务类:处理用户相关的业务:例如登录、注册等功能
public class UserService {
/**
* 用户注册
* @param username 用户名
* @param password 密码
* @throws IllegalNameException 当用户名为null,或者用户名长度<6,或者长度>14时,出现该异常
*/
public void register(String username , String password) throws IllegalNameException {
//引用等于null的这个判断最好放到所有条件的最前面
//if(username == null || username.length() < 6 || username.length() >14 ){}
//再分享一个经验: 写成null ==username 比 username == null 要好。有时候 少一个=就成了:username = null,这种与我们想的不一样,但是这样写不会报错,采用前一种方法就比较保险
// 写成"abc".equals(username) 比 username.equals("abc") 要好
//if( null == username || username.length() < 6 || username.length() >14 ){}
if( null == username || username.length() < 6 || username.length() >14 ){
throw new IllegalNameException("用户名长度必须在[6-14]之间");
}
//程序能执行到这里,说明用户名没有问题
System.out.println("欢迎["+username+"]");
}
}
异常类
package homework;
public class IllegalNameException extends Exception{
public IllegalNameException(){
}
public IllegalNameException(String s){
super(s);
}
}
测试类
package homework;
public class Text {
public static void main(String[] args) {
UserService us = new UserService();
try {
us.register("abcdef","123");
} catch (IllegalNameException e) {
e.printStackTrace();
}
}
}
作业2
开放型题目,随意发挥:
写一个类Army,代表一支军队,这个类有一个属性Weapon数组w(用来存储该军队所拥有的所有武器),
该类还提供一个构造方法,在构造方法里通过传一个int类型的参数来限定该类所能拥有的最大武器数量,
并用这一大小来初始化数组w。
该类还提供一个方法addWeapon(Weapon wa),表示把参数wa所代表的武器加入到数组w中。
在这个类中还定义两个方法attackAll()让w数组中的所有武器攻击;
以及moveAll()让w数组中的所有可移动的武器移动。
写一个主方法去测试以上程序。
提示:
Weapon是一个父类。应该有很多子武器。
这些子武器应该有一些是可移动的,有一些
是可攻击的。
可移动接口
package arrayarmy;
//可移动的接口
public interface Moveable {
//移动行为
void move();
}
可攻击接口
package arrayarmy;
//可射击的接口
public interface Shootable {
//射击行为
void shoot();
}
武器类
package arrayarmy;
//所有武器的父类
public class Weapon {
//武器的名字
String name ;
public Weapon(String name){
this.name = name;
}
//重写toString方法
@Override
public String toString() {
return name;
}
}
坦克类
package arrayarmy;
/**
* 坦克是一个武器,可移动,可攻击
*/
public class TanK extends Weapon implements Moveable,Shootable{
public TanK(String name) {
super(name);
}
@Override
public void move() {
System.out.println("坦克移动");
}
@Override
public void shoot() {
System.out.println("坦克攻击");
}
}
高射炮类
package arrayarmy;
//高射炮是一个武器,但是不能移动,只能射击。
public class GaoShePao extends Weapon implements Shootable{
public GaoShePao(String name) {
super(name);
}
@Override
public void shoot() {
System.out.println("高射炮开炮");
}
}
战斗机类
package arrayarmy;
//战斗机:是武器,可移动、可攻击。
public class Fighter extends Weapon implements Shootable,Moveable{
public Fighter(String name) {
super(name);
}
@Override
public void move() {
System.out.println("战斗机起飞");
}
@Override
public void shoot() {
System.out.println("战斗机开炮");
}
}
物资飞机类
package arrayarmy;
//物资飞机:只能运输物资
public class WuZiFeiJi extends Weapon implements Moveable{
public WuZiFeiJi(String name) {
super(name);
}
@Override
public void move() {
System.out.println("物资飞机运输物资");
}
}
添加武器异常类
package arrayarmy;
public class AddWeaponException extends Exception{
public AddWeaponException(){
}
public AddWeaponException(String s){
super(s);
}
}
军队类
package arrayarmy;
/*
开放型题目,随意发挥:
写一个类Army,代表一支军队,这个类有一个属性Weapon数组w(用来存储该军队所拥有的所有武器),
该类还提供一个构造方法,在构造方法里通过传一个int类型的参数来限定该类所能拥有的最大武器数量,
并用这一大小来初始化数组w。
该类还提供一个方法addWeapon(Weapon wa),表示把参数wa所代表的武器加入到数组w中。
在这个类中还定义两个方法attackAll()让w数组中的所有武器攻击;
以及moveAll()让w数组中的所有可移动的武器移动。
写一个主方法去测试以上程序。
提示:
Weapon是一个父类。应该有很多子武器。
这些子武器应该有一些是可移动的,有一些
是可攻击的。
*/
//军队
//类在强制类型转换过程中,如果是类转换成接口类型。那么类和接口之间不需要存在继承关系,也可以转换,java语法中允许
public class Army {
//武器数组
Weapon[] weapons;
/**
* 创建军队的构造方法
* @param count 武器数量
*/
public Army(int count){
//动态初始化数组中的每一个元素默认值都是null,武器数组是有了,但是武器数组中没有放武器
this.weapons = new Weapon[count];
}
/**
* 将武器加入数组weapons中
* @param weapon 要放入数组的武器
*/
public void addWeapon(Weapon weapon) throws AddWeaponException {
for(int i = 0 ; i < weapons.length ; i++){
if(null == weapons[i]){
weapons[i] = weapon;
System.out.println(weapon+"入队");
return;
}
}
//程序如果执行到这里,说明武器没有添加成功。
throw new AddWeaponException("武器库已满,无法放入武器");
}
/**
* 所有可攻击的武器攻击
*/
public void attackAll(){
//遍历数组
for(int i = 0 ; i < weapons.length ; i++){
//Weapon是所有武器的父类,但是需要调用的是子类武器的方法,所以这里使用向下转型
//看weapons[i]是否具有可攻击的方法,如果有,转型----> 调用该方法
if ( weapons[i] instanceof Shootable){
Shootable shootable = (Shootable)weapons[i];
shootable.shoot();
}
}
}
/**
* 所有可移动的武器移动
*/
public void moveAll(){
//遍历数组
for(int i = 0 ; i < weapons.length ; i++){
if( weapons[i] instanceof Moveable){
Moveable moveable =(Moveable)weapons[i];
moveable.move();
}
}
}
}
测试类
package arrayarmy;
public class Text {
public static void main(String[] args) {
//构建一个军队army
Army army = new Army(4); //这个军队的武器库里只能存放四个武器
//创建武器对象
Fighter fighter = new Fighter("战斗机");
GaoShePao gaoShePao = new GaoShePao("高射炮");
TanK tanK = new TanK("坦克");
WuZiFeiJi wuZiFeiJi = new WuZiFeiJi("物资飞机");
WuZiFeiJi wuZiFeiJi2 = new WuZiFeiJi("物资飞机2");
//添加武器
try{
army.addWeapon(fighter);
army.addWeapon(gaoShePao);
army.addWeapon(tanK);
army.addWeapon(wuZiFeiJi);
army.addWeapon(wuZiFeiJi2);
}catch(AddWeaponException e){
System.out.println(e.getMessage());
}
//让所有可移动的移动
army.moveAll();
//让所有可攻击的攻击
army.attackAll();
}
}