四、java异常处理与常用类
一、java异常处理
1 、异常:就是不正常,是指程序在运行时出现的问题,错误
2 、异常才处理机制:java中为了分离错误处理代码与源代码,采用了面向对象的方式来处理异常,异常也被看做是对象,而且和一般的对象无区别,但是异常必须是Throwable类及其子类所产生的对象。
3 、抛出异常,指正在运行的方法或是java虚拟机生成的异常类对象的产生并提交给jre的过程称为抛出一个异常。
4 、捕获异常,jre在得到一个异常对象时,会寻找相应的处理方法,沿着生成异常的方法回溯调用栈,知道找到包含相应异常处理的方法为止,然后jre将当前异常对象交给这个方法处理,这一过程叫做捕获一个异常。
5 、异常体系 Throwable
Error:指JVM出现重大问题,如运行的类不存在或是内存溢出等,不需要编写代码做出对应处理,程序无法处理,出现则程序中断。
Exception:在运行时运行出现的一些情况,可以通过try、catch、finally处理。
6 、异常分类与结构。
编译时被检查的异常,Checked异常,在程序中必须使用try…catch处理。
编译时不被检测的异常,Runtime异常,可以不用try…catch处理,一旦出现就由JVM处理。
Runtime异常:指程序设计或实现方法不当在程序运行期间产生的异常,所以说是一种可以完全避免的异常。而且这种异常编译器不会检测,即使不处理也没关系,但是一旦出现,程序就会异常终止。若是有异常处理代码,就会以异常抓抛模型进行处理。
Checked异常:除了RuntimeException之外其他的Exception及其子类都是受检查的异常,Java编译器会去检查他们,必须通过try…catch或是throw语句声明抛异常,否则编译不通过,这种异常,要求程序必须处理。
7 、异常的处理方法
1、try…catch捕获处理异常:
try{可能会发生异常的代码}且try中一个语句发生了异常,则try中该 语句之后的语句不会在执行。
catch{异常类对象} … 可以多个catch块,但是范围小的异常类型居前
finally{可选代码块,一定会执行的代码块,除非在catch中有 System.exit(1)语句}
Exception.java (1测试return和finally的先后顺序,2异常捕获的范围问题(原因,java中不能到达的代码是一个错误,若父类异常范围在前,则之后的子类异常catch代码块永远不会到达),3一个catch捕获多个异常类的实现)
package blog4;
/**
* 本例程主要测试下列三点:
* 1 测试return和finally的先后顺序
* 2 异常捕获的范围问题
* 3 一个catch捕获多个异常类的实现 (以数组下标越界和空指针以及数学异常为例)
*
*/
public class Exception {
public static void test1() {
String friends[] = { "kelly", "sandy", "Mike", "Chery" };
try {// 不用try…catch捕捉也能编译通过,因为是运行时异常
for (int i = 0; i <= 4; i++) {
// 数组下标越界异常
System.out.println(friends[i]);
}
// 数学异常
System.out.println("the next is math exception"); // 未执行
@SuppressWarnings("unused")
int num = friends.length / 0;
// 实际上上列try代码块在发生数组越界异常后就不执行数学异常的语句了,因为程序先捕获了该异常
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
/*
public static void test2() {
String friends[] = { "kelly", "sandy", "Mike", "Chery" };
try {// 不用try…catch捕捉也能编译通过,因为是运行时异常
for (int i = 0; i <= 4; i++) {
// 数组下标越界异常
System.out.println(friends[i]);
}
}catch (java.lang.Exception e){
e.printStackTrace();
//此处编译错误,因为Exception作为ArrayIndexOutOfBoundsException的父类
//却优先捕获,则第二个catch块永远不会到达,这在java中是错误的(不能达到的代码)
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
}*/
public static int test3() {
int a = 0;
try {
//程序先执行这一句,但是函数却没跳出,但是记住了需要返回的值是0
return a;
} catch (java.lang.Exception e) {
a++;
e.printStackTrace();
} finally{
//此处在return a;之后执行,虽然改变a值,但是不改变返回值
a++;
//return a,若在finally中添加return,则下面的return -1不可达到,会编译错误
//此处return a;会返回1,而覆盖了try中记录的返回值0.
}
//此处没有执行到
return -1;
}
public static void main(String[] args) {
Exception.test1();
System.out.println(Exception.test3());
}
}
2、try嵌套,多异常处理:
为了实现上文所说的当try中一个语句发生异常后,try中该句后面的语句不能执行,所以,需要使用try语句嵌套解决这个问题。异常前后关系会被推入一个堆栈。
TryNest.java(try嵌套的实现)
package blog4;
/**
* try语句嵌套: 前文Exception.java可见,try中有多个语句时,前面的语句发生了异常,后续语句就不执行了
* 解决:1、后续语句放在finally中,2、嵌套try语句(若后续语句可能发生异常)
*
*/
public class TryNest {
@SuppressWarnings("unused")
public static void test() {
String friends[] = { "kelly", "sandy", "Mike", "Chery" };
try {
try {
int num = friends.length / 0;
} catch (ArithmeticException e) {
e.printStackTrace();
}
// 即使发生了ArithmeticException此处for语句块还是会执行
for (int i = 0; i <= 4; i++) {
// 数组下标越界异常
System.out.println(friends[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TryNest.test();
}
}
3、throws声明异常:
有时不用在方法中处理异常,需要将异常向上传递,由调用它的方法来处理这些异常,这种传递可以逐层向上传递,知道main方法,此时需要throws字句声明。格式为:
returnType methodName(paramList) throws ExceptionList(逗号隔开,为非运行时异常,运行时异常可以不声明)
一个方法,调用了一个本来就已经用throws声明的方法,那么它要么try…catch处理,要么也向上throws抛出。
重写方法不能抛出比被重写方法范围更大的异常类型
4、throw关键字
自行抛出一个异常对象
ThrowCompare.java(对比throw和throws)
package blog4;
/**
* throw与throw的对比: throws用在方法声明上,后面跟的是异常类,可以多个异常类,逗号连接
* throw用于方法内,后面跟的是唯一一个异常类的对象
*
*/
public class ThrowCompare {
public static void test1() throws java.lang.Exception {
int i = 10;
System.out.println(i / 0);
}
public static void test2() throws java.lang.Exception {
int a = 10;
if (a > 0) {
// 通过throw抛出异常后,同样需要在方法中用throws声明会抛出异常,或者在当前方法中
// try catch处理
throw new java.lang.Exception("test2 a>0 exception...");
}
}
public static void main(String[] args) {
try {
ThrowCompare.test1();
// 实际上,test1发生异常,所以test2执行不到
ThrowCompare.test2();
} catch (java.lang.Exception e) {
e.printStackTrace();
}
}
}
5、finally关键字
异常的统一出口,不管try块是否异常,也不管哪个catch执行,finally块总会被执行,除非在try或是到达的catch块中调用了退出JVM系统的的system.exit(1)语句。
finally不能单独出现,而且不要再finally中使用return或是throw语句,否则会导致try或是catch中的return或是throw失效。
6、throw和catch同时使用实例
异常出现在当前方法中,程序只对异常进行部分处理,剩下的处理在调用者中完成,此时应再次抛出异常,让调用者捕捉到,使用catch中throw的方法。
ThrowInCatch.java
package blog4;
/**
* 抛出异常后,本方法中执行一部分,调用者中做剩下的处理 此时用这种分开处理的方法,在Catch中再抛出异常的方法
*
*/
public class ThrowInCatch {
public static void buy(String price) throws java.lang.Exception {
try {
if (price != null)
Double.parseDouble(price);
} catch (java.lang.Exception e) {
e.printStackTrace();
throw new java.lang.Exception("价格不能只能是数字组成");
}
}
public static void main(String[] args) {
try {
ThrowInCatch.buy("abc");
} catch (java.lang.Exception e) {
e.printStackTrace();
}
//执行结果,打印两个异常
//1、buy中的打印 2、main中的打印
//其实对应的都是一个异常,但是它出现的第一次是程序自动产生的异常对象,在buy中捕获,第二次是
//人工抛出的,在main中捕获
}
}
7、自定义异常
自定义异常必须继承Exception或者其子类,可扩充自己的成员变量或是方法,反映更加丰富的异常信息以及对异常对象的处理功能。
UserException.java(自定义异常的实现)
package blog4;
public class UserException {
public static void getScore(int x) throws TooHight,TooLow{
if(x>100){
throw new TooHight("score>100",x);
}else if(x<0){
throw new TooLow("score<0",x);
}else{
System.out.println("score is " + x);
}
}
public static void main(String[] args) {
try {
UserException.getScore(10);
UserException.getScore(120);
UserException.getScore(-5); //未执行
} catch (TooHight e) {
e.printStackTrace();
System.out.println(e.getMessage()+" score is "+e.score);
} catch (TooLow e) {
e.printStackTrace();
System.out.println(e.getMessage()+" score is "+e.score);
}
}
}
@SuppressWarnings("serial")
class TooHight extends java.lang.Exception{
int score; //在异常对象中保存异常的数据
public TooHight(String mess,int score){
super(mess);
this.score = score;
}
}
@SuppressWarnings("serial")
class TooLow extends java.lang.Exception{
int score;
public TooLow(String mess,int score){
super(mess);
this.score = score;
}
}
二 、java常用类
1、String 类
String是不可变的字符序列,一旦创建内容不可变,直到对象销毁。
String类和StringBuffer类都是final声明的,没有子类。
注:常量池
JVM中一块独立的区域存放字符串常量和基本类型常量(public static final)。String使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中储存的字符串内容,就是因为如此,才说String类型是不可变的。
单独使用“”引号创建的字符串都是常量,编译期就已经确定存储到常量池中,而使用new 创建的String对象,是运行期新创建的,会储存到堆内存中。使用只包含常量的字符串连接符如“aa”+“bb”创建的也是常量,编译期就能确定,已经确定存储到常量池中,使用包含变量的字符串连接符如“aa”+ s1创建的对象时运行期才创建的,储存在堆内存中。
使用“”引号创建字符串的过程:会现在缓冲池中查找相同的字符串,若有,则将其引用赋给字符串变量,若无,创建新的字符串,放到缓冲池中。
StringTest.java(equals和==的区别,+号的使用,缓冲池的理解,equals比较的是对象的内容,而==比较的是对象(引用型变量)的地址)
package blog4;
/**
* 本例知识点:
* 1 equals和==的区别,equals比较的是对象的内容,而==比较的是对象(引用型变量)的地址
* 2 +号的使用
* 3 缓冲池的理解
*
*/
public class StringTest {
public static void main(String[] args) {
String s1 = "aabb";
String s2 = "aabb";
String s3 = new String("aabb");
System.out.println(s1==s2); //true,s2和s1都是指向常量池中同一个string对象
System.out.println(s2==s3); //false,==比较的是对象是否相同,此处不同
System.out.println(s2.equals(s3)); //true,equals比较的是内容,s2和s3相等
System.out.println("**********分隔符***********");
String s4 = "aabbcc";
String s5 = "aabb" + "cc";//常量间用+号,还是在常量池中先搜索已存在的String
String s6 = s1 + "cc"; //变量存在的+号,直接在堆中建立新的String对象
System.out.println(s4==s5); //true
System.out.println(s4==s6); //false
System.out.println(s4.equals(s6)); // true
}
}
String常用方法:
各种构造方法,length、charAt(int where)、getChars()、getBytes()、toCharArray()、equals(是否相等)、compareTo(大小对比)、subString(取子串)、startWith()、endWith()、indexOf()、lastIndexOf()、replace()、valueOf(将基本数据类型转成String)、大小写转换toLowCase\toUpperCase()、忽略前后空白trim()、以指定分隔标记拆分字符串split().
注:字符串为空有两种情况,1、null,2、“”;
2、StringBuffer和StringBuilder
StringBuffer和StringBuilder对象是可变的字符串,这样可以避免每次重新创建对象,提高了效率。
StringBuffer提供了字符串的动态添加、插入和替换操作。
StringBuffer是线程安全的,效率低,可变内容。StringBuilder是线程不安全的,性能高,推荐使用StringBuilder。
BufferBuilder.java(这两个类的用法,以及常用方法append、insert、delete、deleteCharAt、reverse(翻转字符串内容),实现将整数数组元素变成逗号连接的字符串)
package blog4;
/**
* 本例测试StringBuffer与StringBuilder相关用法
*/
public class BufferBuilder {
//1 将”ABCDE“变成”A,B,C,D“
public static void test1(){
/* 1 插入实现 利用insert方法
StringBuffer sb = new StringBuffer("ABCDE");
sb.deleteCharAt(sb.length()-1);
System.out.println(sb);
for(int i=1;i<sb.length();i+=2){
sb.insert(i, ',');
}
System.out.println(sb);
*/
// 2 追加方法,转成字符数组逐个添加形成新的字符串
StringBuffer sb = new StringBuffer("ABCDE");
char [] charArray = sb.toString().toCharArray();
StringBuffer sb2 = new StringBuffer();
for(int i=0;i<charArray.length-1;i++){
sb2.append(charArray[i]).append(',');
}
sb2.deleteCharAt(sb2.length()-1);
System.out.println(sb2);
}
// 2 将字符串前后翻转
public static void test2(){
StringBuilder sb = new StringBuilder("abcde");
System.out.println(sb);
sb.reverse();
System.out.println(sb);
}
// 3 将一个整数数组的元素用逗号相连,形成一个字符串
public static void test3(){
StringBuilder sb = new StringBuilder();
int [] intArray = {2,4,6,8,10};
for(int a:intArray){
sb.append(a).append(',');
}
sb.deleteCharAt(sb.length()-1);
System.out.println(sb);
}
// 4 replace方法与append连缀
public static void test4(){
StringBuilder sBuilder = new StringBuilder();
//若用new StringBuilder(5)此时参数5代表sBuilder容量,
//但是若append追加字符串使它超出了原有容量,会继续扩大容量,不会溢出字符
sBuilder.append("<hello>").append("<goodbye>").append("<cat>");
System.out.println(sBuilder);
// 若是1,2,则会把h替换成laolao
sBuilder.replace(1, 1, "laolao");
// 可见replace方法用“laolao”字符串替代了原字符串中一个字符位置,是插入操作
System.out.println(sBuilder);
}
public static void main(String[] args) {
BufferBuilder.test1();
BufferBuilder.test2();
BufferBuilder.test3();
BufferBuilder.test4();
}
}
3、Math和Random以及UUID
Math类:abs(绝对值)、max(最大值)、min(最小值)、PI(静态常量)、rint(返回最接近参数的整数值)、random(返回大于0.0小于1.0的double)、round(返回只入不舍的最近整数值)。
Random类:生成伪随机数。Next(下一个随机数)、nextInt(int n)产生一个0到n之间的随机数,java中的区间都是前开后闭的。
注:不指定种子的构造函数时系统根据当前时间生成种子,每个种子对应一个数列,相同的种子会得到相同数列,而不是数值。所以如果在构造函数中指定种子,会得到同一个数列。
UUID:UUID表示一个128位的值,经过特殊算法保证全网唯一,叫全局唯一标识符。它保证同一时空中所有机器都是唯一独立的。用到了以太网卡地址、纳秒级时间、芯片ID和随机数。
OtherClass.java(以上三个类的使用方法)
package blog4;
import java.util.Random;
import java.util.UUID;
/**
* Math和Random以及UUID的使用
*
*/
public class OtherClass {
public static void main(String[] args) {
//产生一个1—50的随机数,abs绝对值,Math也提供了random方法。
int randomNumber = Math.abs(new Random().nextInt()%50)+1;
int randomNumber2 = Math.abs(new Random().nextInt(51));
// 上例可见nextInt与nextInt(int range)的区别
int randomNumber4 = Math.round((float)Math.random()*100)%50+1;
// round是取最接近整形(只入不舍)
System.out.println(randomNumber + "..." + randomNumber2 + "..." + randomNumber4);
UUID uuid = UUID.randomUUID();
System.out.println(uuid.toString()); //全网唯一的随机数
}
}
4、Date、Calendar、DateFormat和SimpleDateFormat类
Date类:逐渐已被Calendar取代。
Calendar类:抽象类,没有共有的构造方法,不能外部new得到对象,调用其静态方法getInstance获取Calendar对象。
常用方法:int get(int field)返回字段指定的值,年月日等、
DateFormat和SimpleDateFormat类:前者是对时间日期格式化的抽象类,不能new对象,只能通过工厂类方法返回DateFormat实例,后者是前者的子类。
Parse方法与format方法的使用是关键。
DataFormatTest.java(时间类及其格式化的综合运用)
package blog4;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* 时间的综合运用
* data类、Calendar类、DateFormat类、SimpleDateFormat类
*/
public class DateFormatTest {
// Data与Calendar类的运用
public static void test1(){
Date date = new Date();
System.out.println(date);
System.out.println("1970-1-1 到现在的毫秒数为:" + date.getTime());
// 抽象类,静态方法获取实例
Calendar cDate = Calendar.getInstance();
// 注:Calendar中的month从0开始,到11。
System.out.println(cDate.get(Calendar.YEAR) + "-" + cDate.get(Calendar.MONTH));
}
// DateFormat类的使用
public static void test2(){
// 抽象类,只能用过工厂类方法返回实例
// 获取日期格式实例
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT,Locale.CHINA);
// 获取时间格式实例
DateFormat dTime = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG);
Date date = new Date();
// 以两种格式输出当前时间
String s1 = df.format(date);
String s2 = dTime.format(date);
System.out.println(s1);
System.out.println(s2);
System.out.println(date);
// 逆操作,将当前格式的字符串转成一个date类型对象
try {
// 将相应格式的字符串转成date对象
Date dd = df.parse("1991-6-21");
// 将date对象以相应的DateFormat格式输出
System.out.println(dTime.format(dd));
} catch (ParseException e) {
e.printStackTrace();
System.out.println("不能转换");
}
}
// SimpleDateFormat类的使用
public static void test3(){
// 指定一种格式,注意格式中的大小写问题
SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy.MM.dd - HH:mm:ss");
try {
// 将该格式的字符串转成date对象
Date date = sDateFormat.parse("1992.8.3 - 12:23:20");
System.out.println(date); // 以date默认格式输出
System.out.println(sDateFormat.format(date)); // 以指定格式输出
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
DateFormatTest.test1();
System.out.println();
DateFormatTest.test2();
System.out.println();
DateFormatTest.test3();
}
}
5、System类与Runtime类
System类包含一些与系统有关的类字段和方法,不能被实例化,类中所有方法都是static的,可以直接被System调用,其三个静态属性:in、out、error,对应标准输入流和标准输出流及标准错误流。
常用方法:arraycopy()快速的复制数组、exit(int status)终止虚拟机、currentTimeMillis(返回当前时间,毫秒单位,通常用来记录程序各部分的运行时间)、gc(运行垃圾回收器)、get\setProgerties(获取\设置系统属性)
Runtime类:封装了java运行时环境,每一个java应用程序都有一个Runtime类实例,使程序与其运行时的环境相连接,通常不实例化该对象,都是通过调用静态方法getRunTime获取当前RunTime对象的引用,然后调用方法对java虚拟机状态进行控制。
常用方法:gc(运行垃圾回收器)。totalMemory、freeMemory查看堆内存空间的大小及剩余情况。