一、异常
1.1 什么是异常
异常就是指不正常。是指代码在运行过程中可能发生错误,导致程序无法正常运行。
package com.atguigu.exception;
public class TestException {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
System.out.println("数组下标为[10]的元素:" + arr[10]);
//运行时发生 ArrayIndexOutOfBoundsException数组下标越界异常
System.out.println("数组的长度:" + arr.length);//不执行
}
}
1.2 异常的类型(怎么划分分类是重点)
Java是面向对象的编程语言,以“对象”为中心。所以,Java也把异常用对象表示。对象是由某种类new出来的,那么每一种异常的情况都会有一个异常的类来描述它。例如:数组下标越界异常 就用 java.lang.ArrayIndexOutOfBoundsException 类来描述它。
因为异常的类型非常多,所以我们有必要学习它们的继承关系图:
作为异常的根类型是java.lang.Throwable类型(当然如果类的角度来说,根类型仍然是Object)。
Throwable有两大子类:
-
Error:是
Throwable
的子类,用于指示合理的应用程序不应该试图捕获(catch)的严重问题。例如:VirtualMachineError(虚拟机错误)下的子类StackOverflowError(栈内存溢出错误),它在我们无条件递归调用时发生过。这种错误必须停下来,修正我们的程序,或升级硬件,或软件的架构。 -
Exception:
Exception
类及其子类是Throwable
的一种形式,它指出了合理的应用程序想要捕获的条件。-
对于Exception系列的异常来说,特别是RuntimeException系列能避免的尽量避免(依赖于程序员的经验或素质)。不能避免的,就要通过try-catch来处理。
-
Exception下面又可以分为两大类:
-
RuntimeException及其子类:运行时异常,又称为非受检异常。非受检异常是指编译器不会对当前代码做该系列的异常类型检查。例如:ArrayIndexOutOfBoundsException(数组下标越界异常) 或 NullPointerException(空指针异常)等,编译器不会提示你,你的代码可能发生这种异常,直到程序运行时,发生异常,我们才知道。简单的说,编译器检查不出来这个系列的异常。
-
ArrayIndexOutOfBoundsException(数组下标越界异常)
-
NullPointerException(空指针异常)
-
ClassCastException(类型转换异常):向下转型可能发生
-
ArithmeticException(算术异常): a / 0 会发生这个异常
-
-
RuntimeException系列以外的,包括Exception本身:编译时异常,又称为受检异常。受检异常是指编译器会对当前代码做该系列的异常类型检查,不管这个异常是不是发生,会不会发生,编译器只要认为有可能发生,就会报编译错误,即编译不通过。必须等程序员写好对该异常的处理代码后,才会编译通过。
-
总结:无论是编译时异常,还是运行时异常,当运行的时候,异常真的发生了,都会导致程序崩溃。它们的区别只是看编译器是否提醒你而已。
package com.atguigu.exception;
public class TestRuntimeException {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
System.out.println("数组下标为[10]的元素:" + arr[10]);
//这句代码编译器压根不检查,是否下标越界。无论下标是否越界,只要数组名对了,下标是int值,就会编译通过
//但是编译通过,不代表运行正常
}
}
package com.atguigu.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class TestCheckException {
public static void main(String[] args) throws FileNotFoundException {
//FileInputStream:File(文件)Input(输入)Stream(流),这个流是用来读取文件的内容
//下面这句代码,表示想要从 d:\1.txt文件读取文件内容
FileInputStream fis =new FileInputStream("d:\\1.txt");
//此时d:\1.txt文件存不存在,编译器都会提醒你,这段代码可能发生FileNotFoundException
//编译报错,不是说这个文件一定不存在,只是说可能不存在
//必须要求程序员说明(通过代码说明)如果文件不存在,怎么办,编译才会通过。
//例如:我告诉编译器,如果文件不存在,发生了FileNotFoundException,那么main方法不管,程序要挂就挂吧
}
}
1.3 try-catch异常的处理(重点,围绕5个关键字)
try{
可能发生异常的业务代码语句1;
可能发生异常的业务代码语句2;
可能发生异常的业务代码语句3;
可能发生异常的业务代码语句4;
可能发生异常的业务代码语句5;
}catch(异常的类型1 参数名){ //参数名一般都是写e
//(1)打印异常信息的代码 ,要么打印到控制台,要么记录到日志, 打印它的目的是便于程序员后期跟踪,查看发生问题的地方,发生问题的原因,便于后期维护代码
//(2)对异常的处理代码,有的时候引起异常的问题不处理,下面的代码是无法正常运行。
}catch(异常的类型2 参数名){
//
}catch(异常的类型3 参数名){
//
}
执行特点:
(1)try{}没有发生异常,那么所有的catch都不会执行。
(2)try{}中发生了异常,例如:“可能发生异常的业务代码语句3;”发生了异常,try中语句4和语句5不执行。
-
如果语句3发生的是 异常的类型1的问题,那么就从try“语句3”跳到了 第一个catch 分支执行,下面的两个catch不会执行。
-
如果语句3发生的是 异常的类型2的问题,那么就从try“语句3”跳到了 第二个catch 分支执行,上面的与下面的两个catch不会执行。
-
如果语句3发生的是 异常的类型3的问题,那么就从try“语句3”跳到了 第三个catch 分支执行,上面的两个catch不会执行。
-
如果语句3发生的是 异常的类型4的问题,3个catch都不执行了,就会导致 当前方法 异常结束,即当前方法就挂了。
-
如果当前方法main方法,main方法是JVM调用的,那么程序就挂了。
-
如果当前方法其他方法,那么就会“带着这个异常对象”回到调用这个方法的位置,等着调用者处理这个异常。
-
示例一
没有处理异常的代码:有潜在风险
package com.atguigu.exception;
import java.util.Scanner;
public class TestTryCatch {
public static void main(String[] args) {
//需求:从键盘输入2个字符串类型的整数值,然后把它们转为int值,求它们的商
//本来直接输入int类型的整数,用int类型的变量接收即可,这里为了说明问题,故意把问题复杂化
Scanner input = new Scanner(System.in);
System.out.print("请输入第一个整数:");
String str1 = input.next();//123
System.out.print("请输入第二个整数:");
String str2 = input.next();
int a = Integer.parseInt(str1);
int b = Integer.parseInt(str2);
int result = a / b;
System.out.println("商: " + result);
input.close();
//上面的代码,编译器没有发现潜在的问题
//说明一会发生的异常,都是运行时异常
}
}
加上处理异常的代码:更健壮
-
如果能通过条件判断避免的,就不用try-catch,例如:除数为0的情况。
-
如果不能通过条件判断的避免的,就需要用try-catch处理,来增强程序的健壮性。
-
如果有潜在的异常问题,不判断避免,也不用try-catch处理,程序就会很脆弱,一旦发生问题,程序就挂了。以后大家是写服务器端程序,服务器不能随便就挂了。
package com.atguigu.exception;
import java.util.Scanner;
public class TestTryCatch {
public static void main(String[] args) {
//需求:从键盘输入2个字符串类型的整数值,然后把它们转为int值,求它们的商
//本来直接输入int类型的整数,用int类型的变量接收即可,这里为了说明问题,故意把问题复杂化
Scanner input = new Scanner(System.in);
int a = 0;
while (true) {
try {
//选中要用try包围的代码,按快捷键Ctrl + Alt +T
System.out.print("请输入第一个整数:");
String str1 = input.next();//123
//如果输入张三,Integer.parseInt(str1);代码会报错 java.lang.NumberFormatException
//NumberFormatException数字格式化异常, “张三" 转换格式,转换为int失败了
//(1)想要避免这个异常,也能做到,但是需要学习后面的知识,比如正则,判断str1中是不是纯数字(现在还未学习)
//(2)也可以使用try-catch
a = Integer.parseInt(str1);
break;//如果Integer.parseInt(str1)没有发生异常,break就会执行
//如果Integer.parseInt(str1)发生异常,break不执行,而且跳到catch执行
} catch (NumberFormatException e) {
e.printStackTrace();//打印异常的信息到控制台
}
}
int b;//声明改到while上面,为了提升b变量的作用域
while(true) {
try {
System.out.print("请输入第二个整数:");
String str2 = input.next();
b = Integer.parseInt(str2);
if(b != 0 ){
break;
}else{
System.out.println("除数不能为0!");
}
} catch (NumberFormatException e) {
e.printStackTrace();//打印异常的信息到控制台
}
}
int result = a / b;
System.out.println("商: " + result);
input.close();
}
}
try-catch和循环的嵌套关系对比
示例二:
没有处理异常的代码:有潜在风险
package com.atguigu.exception;
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestTryCatch3 {
public static void main(String[] args) {
//需求:从键盘输入2个整数值,求它们的商
Scanner input = new Scanner(System.in);
System.out.print("请输入第一个整数:");
int a = input.nextInt();
System.out.print("请输入第二个整数:");
int b = input.nextInt();
int result = a / b;
System.out.println("商: " + result);
input.close();
//上面的代码,编译器没有发现潜在的问题
//说明一会发生的异常,都是运行时异常
}
}
加上处理异常的代码:更健壮
package com.atguigu.exception;
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestTryCatch3 {
public static void main(String[] args) {
//需求:从键盘输入2个整数值,求它们的商
Scanner input = new Scanner(System.in);
int a = 0;
while (true) {
try {
System.out.print("请输入第一个整数:");
a = input.nextInt();
break;
//如果a = input.nextInt();发生异常,break不执行,跳到catch执行
//如果a = input.nextInt();没有发生异常,break执行,结束循环,catch不会执行
} catch (InputMismatchException e) {//输入不匹配异常,本来想要让用户输入int值,结果它输入其他的
e.printStackTrace();//在控制台打印异常信息
/*
a = input.nextInt();代码发生异常,因为我们输入的是张三回车,
而nextInt()只能从输入通道中读取int值,它发现通道里面只有张三回车,
nextInt()不读取张三,就报异常了。
为了把张三回车给读取掉,需要加一句代码。 input.nextLine()这样才可以把张三以及张三后面的回车符一起读取掉,
这样呢,用户才能有机会重新输入
*/
input.nextLine();
}
}
int b;
while (true) {
try {
System.out.print("请输入第二个整数:");
b = input.nextInt();
if (b != 0) {
break;
} else {
System.out.println("除数不能为0!");
}
} catch (Exception e) {
e.printStackTrace();
input.nextLine();
}
}
int result = a / b;
System.out.println("商: " + result);
input.close();
}
}
1.4 try-catch-finally
try{
可能发生异常的业务代码语句1;
可能发生异常的业务代码语句2;
可能发生异常的业务代码语句3;
可能发生异常的业务代码语句4;
可能发生异常的业务代码语句5;
}catch(异常的类型1 参数名){ //参数名一般都是写e
//(1)打印异常信息的代码 ,要么打印到控制台,要么记录到日志, 打印它的目的是便于程序员后期跟踪,查看发生问题的地方,发生问题的原因,便于后期维护代码
//(2)对异常的处理代码,有的时候引起异常的问题不处理,下面的代码是无法正常运行。
}catch(异常的类型2 参数名){
//
}catch(异常的类型3 参数名){
//
}finally{
//无论上面的try是否发生了异常,
//也不管catch是否可以捕获异常,
//就算try或catch有return语句,finally块都会执行
//一般是资源关闭代码写到这里面
//如果finally里面写了return语句,try,catch中的return语句就失效了
}
示例一
异常没有处理:
package com.atguigu.exception;
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestTryCatchFinally1 {
public static void main(String[] args) {
//需求:输入一个整数,然后打印它
Scanner input = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = input.nextInt();
System.out.println("num = " + num);//(1)
input.close();//(2)
System.out.println("下面的代码:我爱尚硅谷!");//(3)
//没有异常处理代码,一旦input.nextInt()发生异常,下面所有代码(1)(2)(3)都不执行了,程序就挂了
}
}
有正确捕获异常:
package com.atguigu.exception;
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestTryCatchFinally2 {
public static void main(String[] args) {
//需求:输入一个整数,然后打印它
Scanner input = new Scanner(System.in);
try {
System.out.print("请输入一个整数:");
int num = input.nextInt();
System.out.println("num = " + num);//(1)
} catch (InputMismatchException e) {
e.printStackTrace();//打印异常
}
input.close();//(2)
System.out.println("下面的代码:我爱尚硅谷!");//(3)
//上面异常正确捕获,
//input.nextInt()发生异常,下面的代码(1)不执行,(2)(3)正常执行
}
}
没有正确捕获异常
package com.atguigu.exception;
import java.util.Scanner;
public class TestTryCatchFinally3 {
public static void main(String[] args) {
//需求:输入一个整数,然后打印它
Scanner input = new Scanner(System.in);
try {
System.out.print("请输入一个整数:");
int num = input.nextInt();
System.out.println("num = " + num);//(1)
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();//打印异常
}
input.close();//(2)
System.out.println("下面的代码:我爱尚硅谷!");//(3)
//上面异常没有正确捕获,
//input.nextInt()发生异常,下面的代码(1)(2)(3)都不执行
}
}
加finally
package com.atguigu.exception;
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestTryCatchFinally4 {
public static void main(String[] args) {
//需求:输入一个整数,然后打印它
Scanner input = new Scanner(System.in);
try {
System.out.print("请输入一个整数:");
int num = input.nextInt();
System.out.println("num = " + num);
} catch (InputMismatchException e) {
e.printStackTrace();//打印异常
} finally {
input.close();
System.out.println("下面的代码:我爱尚硅谷!");
}
//加finally,无论异常是否正确捕获
//input.nextInt()发生异常,下面的代码(1)不执行,(2)(3)执行
}
}
示例2:加return
执行finally和try的return
package com.atguigu.exception;
public class TestTryCatchFinally5 {
public static void main(String[] args) {
int a = getANumber();
System.out.println(a);//结果是1
}
public static int getANumber(){
try {
return 1;
}catch(Exception e){
return 2;
}finally {
System.out.println("finally");
}
}
}
执行finally和catch的return
package com.atguigu.exception;
public class TestTryCatchFinally6 {
public static void main(String[] args) {
int a = getANumber();
System.out.println(a);//返回2
}
public static int getANumber(){
try {
System.out.println(1/0);
return 1;
}catch(Exception e){
return 2;
}finally {
System.out.println("finally");
}
}
}
执行finally和finally的return
package com.atguigu.exception;
public class TestTryCatchFinally7 {
public static void main(String[] args) {
int a = getANumber();
System.out.println(a);//返回3
}
public static int getANumber(){
try {
System.out.println(1/0);
return 1;
}catch(Exception e){
return 2;
}finally {
return 3;
}
}
}
1.5 throws
1.5.1 throws的用法
这个关键字,用于在方法的(形参列表)后面,方法体{}的前面,写明该方法可能发生xx类型的异常,在该方法中并未“处理”,需要调用者来处理这些异常。
注意:如果调用者是main方法,那么main方法就应该选择try-catch,否则就等价于异常没有处理,一旦发生,程序就挂了。
如果调用者不是main方法,还可以继续throws,让调用 调用者的地方来处理。
【修饰符】 class 类名{
【修饰符】 返回值类型 方法名(【形参列表】) throws 异常类型列表{
}
}
【修饰符】 class 类名{
【修饰符】 返回值类型 main(【形参列表】) {
try{
b(实参列表);
}catch(异常类型 e){
//....
}
//其他代码(正常执行)
}
修饰符】 返回值类型 b(【形参列表】) throws 异常类型列表{
c(实参列表);//语句1
其他语句2 ;
//如果c方法中的语句B发生异常,等价于语句1 这句代码发生异常,其他语句2不会执行,b方法就会挂掉,并且带着异常对象回到main方法
}
修饰符】 返回值类型 c(【形参列表】) throws 异常类型列表{
语句A;
语句B;//可能发生异常的代码
语句C;
//如果语句B发生异常,语句C不会执行,c方法就会挂掉,并且带着异常对象回到b方法。
}
}
【修饰符】 class 类名{
【修饰符】 返回值类型 main(【形参列表】) {
b(实参列表);
}
修饰符】 返回值类型 b(【形参列表】) {
try{
c(实参列表);//语句1
}catch(异常类型 e){
//....
}
其他语句2 ;
//如果c方法中的语句B发生异常,等价于语句1 这句代码发生异常,因为这里有try-catch把异常捕获了,
//那么其他语句2会执行
}
修饰符】 返回值类型 c(【形参列表】) throws 异常类型列表{
语句A;
语句B;//可能发生异常的代码
语句C;
//如果语句B发生异常,语句C不会执行,c方法就会挂掉,并且带着异常对象回到b方法。
}
}
1.5.2 方法重写的要求
方法的重载(Overload) | 方法的重写(Override) | |
---|---|---|
声明的位置 | 同一个类中 或 父子类中 | 父子类中 |
权限修饰符 | 不看 | 应该 > 或 = 被重写方法的权限修饰符,并且被重写方法不能是private |
其他修饰符 | 不看 | 不能是static,final |
返回值类型 | 不看 | 基本数据类型和void:必须相同 引用数据类型:应该 < 或 = 被重写方法的返回值类型 |
方法名 | 必须相同 | 必须相同 |
(形参列表) | 必须不同(类型、个数、顺序不同,和形参名无关) | 必须相同(类型、个数、顺序相同,和形参名无关) |
throws 异常类型 | 不看 | 如果被重写方法没有加throws 编译时异常类型列表,那么重写时,不可以再加throws 编译时异常类型列表。 如果被重写方法加throws 编译时异常类型列表,那么重写时,throws后面的编译时类型异常 必须满足 < 或 = 的关系。 和运行时类型的异常无关。 |
父类Father示例代码
package com.atguigu.exception;
import java.io.FileNotFoundException;
public class Father {
public void m1()throws Exception{
//....
}
public void m2()throws Exception{
//....
}
public void m3()throws FileNotFoundException {
//....
}
public void m4(){
//....
}
public void m5(){
}
}
子类Son示例代码
package com.atguigu.exception;
import java.io.FileNotFoundException;
public class Son extends Father{
@Override
public void m1() throws Exception {//throws的异常类型与父类相同
///
}
//FileNotFoundException < Exception
@Override
public void m2() throws FileNotFoundException {
//....
}
/* //Exception > FileNotFoundException
@Override
public void m3() throws Exception {//错误
//...
}*/
/* @Override
public void m4() throws FileNotFoundException {//错误,因为父类被重写方法没有throws编译时异常类型
//....
}*/
//运行时异常,编译器根本不检查
public void m5()throws ArithmeticException{
}
}
1.5.3 java.lang.Cloneable接口
Cloneable接口:克隆接口,克隆就是复制的意思。
在java.lang.Object类中有一个clone方法:
protected native Object clone() throws CloneNotSupportedException;
权限修饰符:protected(受保护),表示这个方法只能在本类、本包、其他包的子类本身中调用。
说明:重写clone方法的类,还必须实现java.lang.Cloneable接口。
-
如果子类不重写clone方法,不能在子类以外的地方,调用子类对象的clone方法。例如:Student类没有重写clone方法,就不能在测试类TestCloneable中调用学生对象的clone方法
-
如果子类不实现Cloneable接口,那么就算子类重写clone方法,也会发生CloneNotSupportedException异常。
-
调用clone方法时,编译器会提醒你需要处理CloneNotSupportedException异常,因为CloneNotSupportedException属于编译时受检异常。
Student类
package com.atguigu.exception;
public class Student implements Cloneable{
private String name;
private int score;
public Student() {
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
//重写时把方法的返回值类型从Object修改为Student,这是可以的 <=
//重写时权限修饰符可以从protected修改为public,这是可以的 >=
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
测试类TestCloneable
package com.atguigu.exception;
public class TestCloneable {
public static void main(String[] args) {
Student s = new Student("张三", 100);
//复制一个s对象
/* Student s2 = s;//不是复制,是两个变量s,s2都指向同一个对象
s.setName("张四");
System.out.println(s);
System.out.println(s2);*/
try {
Student s2 = s.clone();//克隆一个对象,复制一个属性值一模一样的对象
//s对象在调用clone()方法,s对象是Student类型,
//只能在Student类中调用这个方法
//解决办法:让Student类重写clone方法
s2.setName("张四");
System.out.println(s);
System.out.println(s2);
} catch (CloneNotSupportedException e) {//编译器提醒我们要try-catch处理,说明CloneNotSupportedException是编译时(受检)异常
e.printStackTrace();
}
}
}
1.6 练习题讲解
练习题1
package com.atguigu.exer2;
public class Test2 {
public static void main(String[] args) {
int test = test(3,5);
System.out.println(test);//8
}
public static int test(int x, int y){
int result = x;
try{
if(x<0 || y<0){//条件不成立
return 0;//不执行
}
result = x + y;//执行 result = 8
return result; //执行它
/*
return有两个作用:
(1)返回结果给调用者 先把result变量的值8返回给调用者
(2)结束当前方法 本来应该结束当前方法的执行,但是因为有finally,要先去执行finally
//执行完finally,结束当前方法的执行
*/
}finally{
result = x - y;//修改result ,result = -2
}
}
}
练习题2
package com.atguigu.exer3;
public class Test3 {
static int i = 0;
public static void main(String[] args) {
System.out.println(test());//2
}
public static int test(){
try{
return ++i;
/*
return有两个作用:
(1)返回结果给调用者 计算++i的值,i=1,返回i的值1的给调用者
(2)结束当前方法 本来应该结束当前方法的执行,但是因为有finally,要先去执行finally
*/
}finally{
return ++i;
/*
return有两个作用:
(1)返回结果给调用者 计算++i的值,i=2,返回i的值2的给调用者
(2)结束当前方法 结束当前方法的执行
*/
}
}
}
1.7 自定义异常
1、为什么要自定义?
虽然核心类库中已经给我们预备了很多的异常类型了,但是我们有时候仍然需要自定义异常类型。因为异常的类型名最好能够清晰的描述我们发生异常的问题,即异常类型名最好能见名知意。系统预定义的这些异常类型,可能并不能准确的描述你的业务层面的一些异常问题,所以就最好自定义。
2、如何自定义异常类型
-
必须继承Throwable或其子类。通常会继承Exception(继承它,你的自定义异常就是受检异常)或RuntimeException(继承它,你的自定义异常就是非受检异常)
-
因为只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java
throw
语句抛出。 -
类似地,只有此类或其子类之一才可以是
catch
子句中的参数类型。
-
-
建议自定义异常时,提供无参构造以及能为从父类继承的message属性赋值的有参构造
3、注意
自定义异常需要通过throw语句抛出,同样可以throws告诉调用者处理,最后仍然使用try-catch。
自定义异常类型:钱不能为负数的异常
package com.atguigu.exception;
//钱不能为负数的异常
public class MoneyCannotNegativeException extends Exception{
public MoneyCannotNegativeException() {
}
public MoneyCannotNegativeException(String message) {
super(message);
}
}
自定义异常类型:钱不够异常
package com.atguigu.exception;
//钱不够异常
public class MoneyNotEnoughException extends Exception{
public MoneyNotEnoughException() {
}
public MoneyNotEnoughException(String message) {
super(message);
}
}
银行账号类Account
package com.atguigu.exception;
public class Account {
private String id;//账号
private double balance;//余额
public Account() {
}
public Account(String id, double balance) {
this.id = id;
this.balance = balance;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public void withdraw(double money) throws MoneyCannotNegativeException, MoneyNotEnoughException {
if(money < 0){
// System.out.println("取款不能为负数");
// throw new MoneyCannotNegativeException();
throw new MoneyCannotNegativeException("取款不能为负数!");
}else if(money > balance){
// System.out.println("余额不足!");
// throw new MoneyNotEnoughException();
throw new MoneyNotEnoughException("余额不足!");
}else{
balance -= money;
}
}
public void save(double money) throws MoneyCannotNegativeException {//存款
if(money < 0){
// System.out.println("存款不能为负数");
// throw new MoneyCannotNegativeException();
throw new MoneyCannotNegativeException("存款不能为负数!");
}else{
balance += money;
}
}
@Override
public String toString() {
return "Account{" +
"id='" + id + '\'' +
", balance=" + balance +
'}';
}
}
测试类
package com.atguigu.exception;
public class TestAccount {
public static void main(String[] args) {
Account a = new Account("111111", 5000);
try {
a.save(500);
System.out.println("存500后:"+a);
} catch (MoneyCannotNegativeException e) {
e.printStackTrace();
}
try {
a.save(-500);
System.out.println("存-500后:"+a);
} catch (MoneyCannotNegativeException e) {
e.printStackTrace();
}
try {
a.withdraw(500);
System.out.println("取500后:"+a);
} catch (MoneyCannotNegativeException e) {
e.printStackTrace();
} catch (MoneyNotEnoughException e) {
e.printStackTrace();
}
try {
a.withdraw(6000);
System.out.println("取6000后:"+a);
} catch (MoneyCannotNegativeException e) {
e.printStackTrace();
} catch (MoneyNotEnoughException e) {
e.printStackTrace();
}
}
}
1.8 思考题:throw与throws有什么区别
throw | throws | |
---|---|---|
位置 | 方法体里面 | (形参列表)的后面,方法体的前面 |
作用 | 抛出一个异常的对象 | 告诉调用者该方法可能发生xx类型的异常 |
后面接的东西不同 | throw 后面跟一个异常对象 | throws后面跟一个或多个的异常的类型名 |
使用形式 | throw new 异常类型(【实参列表】); | 【修饰符】 返回值类型 方法名(【形参列表】) throws 异常类型1,异常类型2{ } |
二、根父类
类 Object
是类层次结构的根类。每个类都使用 Object
作为超类。所有对象(包括数组)都继承这个类的方法。
public String toString():
默认返回对象的运行时类型(new对象的类型)+@+对象的hashCode值是十六进制形式。类似于虚拟地址。
建议所有子类都重写此方法。重写的快捷键:Alt + Insert 或 Ctrl + O。
打印对象时,会自动调用toString()。
protected Object clone()throws CloneNotSupportedException
如果子类对象要调用clone方法,要求子类重写clone方法。
并且子类要实现java.lang.Cloneable接口,否则会报CloneNotSupportedException。
public final Class<?> getClass():返回此对象的运行时类(new对象的类型)。
protected void finalize()throws Throwable
这个方法已经被废弃了。
比喻:它用来留临终遗言
实际:当一个类的对象在被GC(垃圾回收器)回收之前(即彻底要在JVM中消失了),会自动调用这个对象的finalize()。通常在这个方法中,编写释放该对象占用的一些系统资源。
有些公司的题库比较老,有一个这样的面试题:finally,final,finalize的区别?
finally是与try-catch一起使用的关键字。
final是用来修改类、方法、变量的关键字。
finalize是Object中一个方法,用于释放系统资源的方法,现在已经废弃了。
public boolean equals(Object obj):指示其他某个对象是否与此对象“相等”。
如果用模板生成,就没有问题,但是如果自己手动重写,需要遵守如下要求:强烈建议用快捷键生成,不要手动重写。
(1)自反性:自己和自己比较返回true
(2)传递性:x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也一定返回true
(3)对称性:x.equals(y)返回true,那么y.equals(x)也一定返回true
(4)一致性:x..equals(y)前面返回true,x和y的属性都没有修改,下面再次调用x..equals(y)也要返回true
(5)一个非空对象.equals(null)一定返回false
public int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
只有把对象放到哈希结构的容器中,这个方法才有意义,否则没有意义。(等后面再学习它)
但是,记住,equals和hashCode方法一定要一起重写。
如果用模板生成,就没有问题,但是如果自己手动重写,需要遵守如下要求:强烈建议用快捷键生成,不要手动重写。
(1)如果两个对象equals方法返回true,两个对象的hashCode()的结果必须相同。
(2)如果两个对象的hashCode()不同,那么这个两个对象equals方法返回false。
(3)如果两个对象的hashCode()相同,那么这两个对象的equals方法可能是true,可能是false。
示例代码1:getClass()
package com.atguigu.object;
import com.atguigu.exception.Father;
import com.atguigu.exception.Son;
public class TestObjectAPI {
public static void main(String[] args) {
Object obj = "hello";//多态引用
System.out.println(obj.getClass());//class java.lang.String
/*
obj的编译时类型是Object,看左边
obj的运行时类型是String,看右边
*/
Object obj2 = 1;
System.out.println(obj2.getClass());//class java.lang.Integer
//Object是引用数据类型,不能赋值基本数据类型的值。如果给它基本数据类型的值,会自动装箱为包装类的对象。
Father f = new Son();
System.out.println(f.getClass());//class com.atguigu.exception.Son
String str = "hello";
System.out.println(str.getClass());//class java.lang.String
}
}
示例代码2:finalize(了解)
package com.atguigu.object;
public class Demo {
@Override
protected void finalize() throws Throwable {
System.out.println("我轻轻的走了,不带走一段代码....");
}
}
package com.atguigu.object;
public class TestDemo {
public static void main(String[] args) {
Demo d = new Demo();
d = null;//d变量不引用上面的对象了,那么上面的对象就成垃圾了
System.gc();//主动呼叫GC来回收内存垃圾
try {
Thread.sleep(5000);//停秒,别那么着急结束main
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
示例代码3:equals
Person类
package com.atguigu.object;
public class TestDemo {
public static void main(String[] args) {
Demo d = new Demo();
d = null;//d变量不引用上面的对象了,那么上面的对象就成垃圾了
System.gc();//主动呼叫GC来回收内存垃圾
try {
Thread.sleep(5000);//停秒,别那么着急结束main
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类
package com.atguigu.object;
import java.util.Scanner;
public class TestPerson {
public static void main(String[] args) {
Person p1 = new Person("张三",23);
Person p2 = new Person("张三",23);
System.out.println(p1 == p2);//false
//==比较的是对象的首地址,new了两次,两个对象的首地址一定是不同的
System.out.println(p1.equals(p2));
/*
(1)如果Person类没有重写equals方法,从Object继承的equals也是比较对象的首地址。
(2)如果想要Person对象调用equals方法时,比较对象的属性值,那么就应该重写equals方法。
重写equals方法的快捷键:Alt + Insert
*/
// System.out.println(p1.equals(p1));//比较地址值就可以了
System.out.println(p1.equals(null));
//p1如果为null,就发生空指针异常了,不会进入equals方法
//p1如果不为null,一个非空对象与一个null一定是不相等,就返回false
System.out.println(p1.equals("张三"));
//p1是Person类型的,“张三"是String类型,类型不同,返回false
System.out.println("=======================");
Integer i = 1;
Integer j = 1;
System.out.println(i == j);//true
Integer a = 200;
Integer b = 200;
System.out.println(a==b);//false
System.out.println(a.equals(b));//true
//Integer重写了equals方法,比较两个包装类对象的数据值
System.out.println("===================");
Scanner input = new Scanner(System.in);
System.out.print("请输入姓名:");
String name = input.next();
//判断输入的姓名是否是张三
System.out.println(name == "张三");
System.out.println(name.equals("张三"));
//String类重写了equals方法
//结论:以后凡是引用数据类型,包括字符串,包装类,你自己写的类的对象比较是否相等
//都不要用==,而是用equals方法
input.close();
}
}
示例代码4:hashCode
package com.atguigu.object;
public class TestHashCode {
public static void main(String[] args) {
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112
System.out.println("Aa".equals("BB"));//false
Person p1 = new Person("张三",23);
Person p2 = new Person("张三",23);
System.out.println(p1.hashCode());//24022543
System.out.println(p2.hashCode());//24022543
System.out.println(p1.equals(p2));//true
}
}
三、native(了解)
在Object类中,我没看到了
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;
native:本地的,原生的。
在Java中,表示这个方法的方法体不是用Java语言实现的,而是调用底层的C或C++的代码。所以,在Java层面看不到它的方法体。
但是,在Java中你可以当成普通的Java方法一样调用,一样重写。