一、手动抛出异常对象:throw
(1)两种方式
之前说的几个步骤:
①此时执行几行代码,在执行的过程中,可能出现一些问题,比如将字符串“abc”转换成Integer类型的,肯定是没有办法转化。这时候它会在相应代码位置自动生成一个NumberFormatException
的对象,这个对象就被抛出去了。
②针对抛出的异常对象,考虑catch处理(捕获);若是搞不定,还可以throws。(异常处理的两种方式
)
现在针对“生成异常对象”的环节,除了系统自动抛之外,可能还会出现手动抛出异常对象的场景。
手动抛出异常对象,也是要处理的。只要是抛出了异常对象,不去处理的话,程序就会停止。
☕Java 中异常对象的生成有两种方式:
- 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,那么针对当前代码,就会在后台自动创建一个对应异常类的实例对象并抛出。
- 由开发人员手动创建:
new 异常类型([实参列表]);
,如果创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样,但是一旦throw抛出,就会对程序运行产生影响了。
🎲为什么要主动抛出异常呢?
在程序执行的时候,有时候会出现不满足要求的场景。比如将字符串“abc”转换为Integer类型的,这就是转换不了的,对于这种场景,程序给我们预知道这些问题,帮我们把这些问题给操作了,意思就是,若是出现这种不合理的现象,它就会自动抛出一个异常对象。
但是还是会有一些实际问题,系统没办法给我们抛,就需要自己来。
比如现在有一个Student,他有一个id属性,要给id赋值,这个id最起码要是一个正数,一旦给这个id赋值了负数,就不太合理。以前我们是写一个输出语句,现在可以拋一个异常。在Java语言层面,没有说id不能是负数,这是我们实际情况需要的一个要求。
所以,如果在实际开发当中,有一些具体的要求,这些要求在实际操作中不满足的时候,我们就可以主动去抛出一个异常对象。
在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的异常对象。
(2)使用格式
🎲如何实现手动抛出异常?
在方法内部,满足指定条件的情况下,使用"throw 异常类的对象
"的方式抛出。
注意这里是throw
,后面是异常的对象,既然是对象,就需要new
一个,后面再跟上想抛出类型的异常。
如下:
throw new 异常类名(参数);
注意点:throw后的代码不能被执行,编译不通过。
throw语句抛出的异常对象,和JVM自动创建和抛出的异常对象一样。
- 如果是编译时异常类型的对象,同样需要使用throws或者try…catch处理,否则编译不通过。
- 如果是运行时异常类型的对象,编译器不提示。
- 可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
throw new String("want to throw");
(3)举例
1、手动抛运行时异常
现在有一个Student
,他有一个id
属性,要给id赋值,这个id最起码要是一个正数,一旦给这个id赋值了负数,就不太合理。
以前我们是写一个输出语句,现在可以拋一个异常。在Java语言层面,没有说id不能是负数,这是我们实际情况需要的一个要求。
以往我们只能这样表示id输入非法(输入的是负数),如下:
public class ThrowTest {
}
class Student{
int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
System.out.println("输入的id非法");
}
}
}
现在我们可以手动抛出异常类的对象,一旦输入的id不行,就不让程序继续往下执行,如下:
class Student{
int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
//System.out.println("输入的id非法");
//手动抛出异常类的对象
throw new RuntimeException("输入的id非法");
}
}
}
这里抛的是一个运行时异常,抛出了一个异常对象,下一步就需要考虑处理了,运行时异常可以考虑不处理。
所以现在不需要try-catch或者throws。
调用一下:
🌱情况1
package yuyi02;
public class ThrowTest {
public static void main(String[] args) {
Student s1=new Student();
s1.regist(10);
System.out.println(s1);
}
}
class Student{
int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
//System.out.println("输入的id非法");
//手动抛出异常类的对象
throw new RuntimeException("输入的id非法");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
输出结果:
这是一个正常情况。
🌱情况2
package yuyi02;
public class ThrowTest {
public static void main(String[] args) {
Student s1=new Student();
s1.regist(-10);
System.out.println(s1);
}
}
class Student{
int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
//System.out.println("输入的id非法");
//手动抛出异常类的对象
throw new RuntimeException("输入的id非法");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
此时传入regist
中的是-10,会进入else逻辑中,会抛出异常对象。
这个异常对象抛出来之后也没有进行处理,也没有try-catch-finally,也没有throws。(运行时异常不做处理)
所以,这个时候出现的异常对象,导致程序终止,main方法后面的输出语句就无法执行了。
看一下输出结果:
可以看到,它里面是一个RuntimeException
,因为我们造的就是一个RuntimeException
的对象,里面的信息就是"输入的id非法"。我们在这里写的信息其实就是Message
里面的内容。
☕补充
若是现在处理这个“运行时异常”,可以这样try-catch
一下:
package yuyi02;
public class ThrowTest {
public static void main(String[] args) {
Student s1=new Student();
try {
s1.regist(-10);
System.out.println(s1);
}catch (RuntimeException e){
e.printStackTrace();
}
}
}
class Student{
int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
//手动抛出异常类的对象
throw new RuntimeException("输入的id非法");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
输出结果和没有处理的时候一样,所以说一般不做处理,如下:
这里的“输入的id非法”其实就是getMessage
,它里面就是构造器传进来的数据。
public class ThrowTest {
public static void main(String[] args) {
Student s1=new Student();
try {
s1.regist(-10);
System.out.println(s1);
}catch (RuntimeException e){
System.out.println(e.getMessage());
}
}
}
class Student{
int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
//System.out.println("输入的id非法");
//手动抛出异常类的对象
throw new RuntimeException("输入的id非法");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
现在打印的就是这个内容,如下:
2、手动抛普通的异常
比如这样:
package yuyi02;
class Student{
int id;
public void regist(int id){
if(id>0){
this.id=id;
}else{
//2、手动抛出异常类的对象--普通异常
throw new Exception("输入的id非法");
}
}
}
这个时候就要小心一点了,因为它不是运行时异常了,这种运行方式就需要处理了。如下:
出了异常就要处理了,可以try-catch
或者throws
。
假设现在不在regist
方法里面try-catch,那我们就throws一下。
因为下面thow
的是一个Exception,那么上面throws
的也要是Exception。如下:
现在不是运行时异常,就需要处理了。
现在是一个普通的Exception,而且将这个问题抛到main方法那边了。main
方法就需要处理这个异常了,这时候必须要try-catch
(catch里面的异常就是针对throw过来的那个,现在throw过来了Exception,所以catch里面要写Exception),如下:
public class ThrowTest {
public static void main(String[] args) {
Student s1=new Student();
//2、手动抛出异常类的对象--普通异常 (非运行时异常)
try {
s1.regist(-10);
System.out.println(s1);
}catch(Exception e){
e.printStackTrace();
}
}
}
🌱整体代码
package yuyi02;
public class ThrowTest {
public static void main(String[] args) {
Student s1=new Student();
//2、手动抛出异常类的对象--普通异常 (非运行时异常)
try {
s1.regist(-10);
System.out.println(s1);
}catch(Exception e){
e.printStackTrace();
}
}
}
class Student{
int id;
public void regist(int id) throws Exception{
if(id>0){
this.id=id;
}else{
//2、手动抛出异常类的对象--普通异常 (非运行时异常)
throw new Exception("输入的id非法");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
注意点:throw后的代码不能被执行,编译不通过。比如:
🍺输出如下
3、代码
上面两种的代码,整体如下:
🌱代码
package yuyi02;
/**
* ClassName: ThrowTest
* Package: yuyi02
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/1/16 0016 11:39
*/
public class ThrowTest {
public static void main(String[] args) {
Student s1=new Student();
//1、手动抛出异常类的对象--运行时异常
/*
try {
s1.regist(-10);
System.out.println(s1);
}catch (RuntimeException e){
//e.printStackTrace();
System.out.println(e.getMessage());
}*/
//2、手动抛出异常类的对象--普通异常 (非运行时异常)
try {
s1.regist(-10);
System.out.println(s1);
}catch(Exception e){
e.printStackTrace();
}
}
}
class Student{
int id;
public void regist(int id) throws Exception{
if(id>0){
this.id=id;
}else{
//System.out.println("输入的id非法");
//1、手动抛出异常类的对象--运行时异常
//throw new RuntimeException("输入的id非法");
//2、手动抛出异常类的对象--普通异常 (非运行时异常)
throw new Exception("输入的id非法");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
(4)使用注意点
无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有被try…catch合理的处理,都会导致程序崩溃。
throw语句会导致程序执行流程被改变,throw语句是明确抛出一个异常对象,因此它下面的代码将不会执行
。
如果当前方法没有try…catch处理这个异常对象,throw语句就会代替return语句
提前终止当前方法的执行,并返回一个异常对象给调用者。
package com.atguigu.keyword;
public class TestThrow {
public static void main(String[] args) {
try {
System.out.println(max(4,2,31,1));
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println(max(4));
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println(max());
} catch (Exception e) {
e.printStackTrace();
}
}
public static int max(int... nums){
if(nums == null || nums.length==0){
throw new IllegalArgumentException("没有传入任何整数,无法获取最大值");
}
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
if(nums[i] > max){
max = nums[i];
}
}
return max;
}
}
(5)自动VS手动
🎲如何理解"自动 vs 手动"抛出异常对象?
①过程1:“抛
”(产生异常对象)
- “自动抛” : 程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,自动生成对应异常类的对象,并将此对象抛出。一旦抛出,此程序就不执行其后的代码了。
- “手动抛” :程序在执行的过程当中,不满足指定条件的情况下,我们主动的使用"
throw + 异常类的对象
"方式抛出异常对象。
②过程2:“抓
” (处理异常对象)
- 狭义上讲:
try-catch
的方式捕获异常,并处理。 - 广义上讲:把“抓”理解为“处理”。则此时对应着异常处理的两种方式:①
try-catch-finally
②throws
☕注意
1、其实,所谓的自动抛,不过不是我们自己写的而已。在源码中,它其实也是用“throw+异常对象”来做的。这里只是理解为“自动抛”。
2、throws
和throw
是合作关系,throws是产生异常对象的过程,throw是处理异常对象的过程。
二、练习
(1)练习1
🌋题目描述
修改chapter08_oop3中接口部分的exer2,在ComparableCircle接口compareTo()中抛出异常。
在接口这一篇的练习2中有详细说明,链接:https://blog.csdn.net/m0_55746113/article/details/134687578?spm=1001.2014.3001.5502
【Circle.java】
package yuyi02.exer.exer1;
/**
* ClassName: Circle
* Package: yuyi03
* Description:
* 定义一个Circle类,声明radius属性,提供getter和setter方法
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 14:52
*/
public class Circle { //两个Circle对象不能够比较大小
public double radius; //半径
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public Circle() {
}
public Circle(double radius) {
this.radius = radius;
}
//toString方法
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
【ComparableCircle.java】
package yuyi02.exer.exer1;
/**
* ClassName: ComparableCircle
* Package: yuyi03
* Description:
* 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
* 在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 14:56
*/
public class ComparableCircle extends Circle implements CompareObject {
//根据对象半径的大小,比较对象的大小(和之前说的equals很像)
@Override
public int compareTo(Object o) {
if(this==o){ //判断当前对象与o是不是指向同一个
return 0; //若地址一样,则半径肯定一致,直接返回0
}
if(o instanceof ComparableCircle){ //判断是否是当前类的对象
ComparableCircle c=(ComparableCircle) o; //若是当前类对象,先强转一下 (从父类对象强转成子类才能调用子类特有的结构)
return Double.compare(this.getRadius(),c.getRadius()); //API里面有一个类就叫Double,里面有一个方法叫compare(),里面传入两个double类型的值,就会自动比较它们的大小,返回的就是一个int类型的值,直接return即可
}else{ //当这个对象不是当前实例
//return 2; 如果输入类型不匹配,则返回2
throw new RuntimeException("输入类型不匹配");
}
}
public ComparableCircle() {
}
public ComparableCircle(double radius) {
super(radius);
}
}
【CompareObject.java】
package yuyi02.exer.exer1;
/**
* ClassName: CompareObject
* Package: yuyi03
* Description:
* 定义一个接口用来实现两个对象的比较。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 10:09
*/
public interface CompareObject { //自定义一个接口来比较对象大小
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
public int compareTo(Object o); //这个是抽象方法(省略了abstract),虽然它没有方法体,但是这个方法是做什么的,形参是什么意思,返回值类型是什么都完全确定了,只是细节没有确定
}
【InterfaceTest.java】
package yuyi02.exer.exer1;
/**
* ClassName: InterfaceTest
* Package: yuyi03
* Description:
* 定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 15:43
*/
public class InterfaceTest {
public static void main(String[] args) {
ComparableCircle c1=new ComparableCircle(2.3);
ComparableCircle c2=new ComparableCircle(5.3);
int compareValue=c1.compareTo(c2);
if(compareValue>0){
System.out.println("c1对象大");
} else if (compareValue<0) {
System.out.println("c2对象大");
}else {
System.out.println("c1与c2一样大");
}
}
}
🍰分析
接口CompareObject里面有一个方法compareTo,用来比较对象大小。
Circle本身不能比较大小,它的子类ComparableCircle继承于Circle的同时又实现了接口CompareObject,它就具备了比较大小的规范。
在ComparableCircle中将方法compareTo重写了。
若比较的两者一致就是0。
不一致的话,若是ComparableCircle类型的,先强转然后比较;若不是ComparableCircle类型的,当时没有学到异常,只能用return 2
将就一下,现在来看的话,不是ComparableCircle类型的就直接抛一个异常即可。
如下:
这时候,抛一个异常最为合适,因为返回正数、负数、0都不太合适。
现在抛的是运行时异常RuntimeException
,抛了一个对象而已,此时并没有处理。
一般抛出异常之后,都考虑要进行处理,只不过此时抛的是运行时异常,就不用进行处理了。
若此时用的是一般异常,就需要进行处理了。比如现在throw了一个Exception
,如下:
现在就要处理了,要不然程序过不去。
怎么处理?要么throws
,要么try-catch
。
比如现在往上抛,针对当前抛的类型,也应该是Exception
,如下:
这时候怎么还报错呢?
看一下错误信息:
这是因为接口里面没有抛异常,如下:
实现方法(重写方法)里面不能抛比接口里面更大的,所以此时接口里面也需要做处理:
以后也会碰见这样的场景,在接口里面,明明是一个抽象方法,它也会抛出一个异常,这就是为了配合在具体实现的时候可能会抛出的异常。
现在还有一个地方报错,如下:
这是因为刚才处理异常的时候,我们采用了第二种方式,就是throws往上抛。
所以在main方法里面,就必须要对异常进行处理了。
可以使用try-catch
进行处理,如下:
🌱代码
【Circle.java】
package yuyi02.exer.exer1;
/**
* ClassName: Circle
* Package: yuyi03
* Description:
* 定义一个Circle类,声明radius属性,提供getter和setter方法
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 14:52
*/
public class Circle { //两个Circle对象不能够比较大小
public double radius; //半径
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public Circle() {
}
public Circle(double radius) {
this.radius = radius;
}
//toString方法
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
【CompareObject.java】
package yuyi02.exer.exer1;
/**
* ClassName: CompareObject
* Package: yuyi03
* Description:
* 定义一个接口用来实现两个对象的比较。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 10:09
*/
public interface CompareObject { //自定义一个接口来比较对象大小
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
public int compareTo(Object o) throws Exception; //这个是抽象方法(省略了abstract),虽然它没有方法体,但是这个方法是做什么的,形参是什么意思,返回值类型是什么都完全确定了,只是细节没有确定
}
【ComparableCircle.java】
package yuyi02.exer.exer1;
/**
* ClassName: ComparableCircle
* Package: yuyi03
* Description:
* 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
* 在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 14:56
*/
public class ComparableCircle extends Circle implements CompareObject {
//根据对象半径的大小,比较对象的大小(和之前说的equals很像)
@Override
public int compareTo(Object o) throws Exception{
if(this==o){ //判断当前对象与o是不是指向同一个
return 0; //若地址一样,则半径肯定一致,直接返回0
}
if(o instanceof ComparableCircle){ //判断是否是当前类的对象
ComparableCircle c=(ComparableCircle) o; //若是当前类对象,先强转一下 (从父类对象强转成子类才能调用子类特有的结构)
return Double.compare(this.getRadius(),c.getRadius()); //API里面有一个类就叫Double,里面有一个方法叫compare(),里面传入两个double类型的值,就会自动比较它们的大小,返回的就是一个int类型的值,直接return即可
}else{ //当这个对象不是当前实例
//throw new RuntimeException("输入类型不匹配");
throw new Exception("输入的类型不匹配");
}
}
public ComparableCircle() {
}
public ComparableCircle(double radius) {
super(radius);
}
}
【InterfaceTest.java】
package yuyi02.exer.exer1;
/**
* ClassName: InterfaceTest
* Package: yuyi03
* Description:
* 定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 15:43
*/
public class InterfaceTest {
public static void main(String[] args) {
ComparableCircle c1=new ComparableCircle(2.3);
ComparableCircle c2=new ComparableCircle(5.3);
try {
int compareValue=c1.compareTo(c2);
if(compareValue>0){
System.out.println("c1对象大");
} else if (compareValue<0) {
System.out.println("c2对象大");
}else {
System.out.println("c1与c2一样大");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
🍺输出结果
(2)面试题
🎲面试题:throw 和 throws 的区别?
答:“上游排污,下游治污”,污-异常对象;排-throw;治-throws。
throws
从使用上来讲,是使用在方法的声明上,后面跟异常类型,指明将异常向上一层抛出,属于异常处理的方式。
throw
是使用在方法内部,后面跟的是异常类的对象,表示手动抛出一个指定异常类的对象。
throw
用来产生异常对象(第一个环节),throws
是针对产生的异常对象,如何进行处理(第二个环节)。