JAVA基础15
IDEA中的调试程序
为什么要Debug?
编写好的程序在执行过程中出现错误,复杂的代码中无法直接看出错误,则需要Debug查找错误。
Debug步骤
1.添加断点 2.启动调试 3.单步执行 4.观察变量和执行流程,找到并解决问题
行断点
step into会进入你自己写的方法。
而Force step into能够进入所有的方法,比如jdk的方法。
- 当我们有多个断点,并不需要一步一步的执行查错,我们可以点击左边的按钮让程序从上一个断点处跳转到下一个断点处
- 当我们点击这个Run to Cursor我们可以让程序从断点处跳转到我们光标所在的这一行
方法断点
当执行了这个方法就会暂停,可以看这个方法什么时候执行
可以查看那些地方有断点,下面打一斜杠的标识表示让所有的断点失效。
属性断点
右键这个属性断点
条件断点
右键断点,在Condition输入停止的条件
强制结束调试中的程序
当我们运行到断点处,不想再继续执行下去,我们应该右键方法,选择Force Return则可以强制跳出,并且后面的代码不再执行。
异常处理
异常概述
什么是异常
指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。
异常的抛出机制
Java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)然后程序员可以捕获(catch)到这个异常对象,并处理。
如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止。
如何对待异常
对于程序出现的异常,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种就是在程序员编写程序时,就充分考虑到各种可能发生的异常和错误,极力预防和避免。
实在无法避免的,编写相应的代码进行异常的检测,以及 '异常的处理',保证代码的健壮性。
异常的体系结构
java.lang.Throwable:异常体系的根父类
----java.lang.Error:错误。Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
例如:StackOverflowError、OutofMemoryError
----java.lang.Exception:异常。我们可以编写针对性的代码进行处理。
--编译时异常:(又称受检异常)在执行javac.exe命令时,出现的异常。
例如:ClassNotFoundException 未能找到指定类、FileNotFoundException 未能找到指定文件、IOException输入输出异常
---运行时异常:(非受检异常)在执行java.exe命令时,出现的异常。
例如:ArrayIndexOutOfBoundsException 数组角标越界
NullPointerException 空指针异常
ClassCastException 类型转换异常
NumberFormatException 数字格式化异常
InputMismatchException 输入不匹配异常
ArithmeticException 算数异常
捕获异常
方式一 try-catch-finally
过程一"抛":程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,生成对应异常类的对象,并将此对象抛出。一旦抛出,此程序就不执行其后的代码。
过程二"抓":针对于过程1中抛出的异常对象,进行捕获处理。此捕获处理的过程就称为抓。一旦将异常进行了处理,代码就可以继续执行。
基本结构:
使用细节:
> 将可能出现异常的代码声明在try语句中,一旦代码出现异常,就会自动生成一个对应异常类的对象,并将此对象抛出。
> 针对于try中抛出的异常类的对象,使用之后的catch语句进行匹配。一旦匹配上,就进入catch语句块进行处理。一旦处理结束,代码就继续向下执行。
> 如果声明了多个catch结构,不同的异常类型在不存在子父类关系的情况下,谁声明在上面,谁声明在下面都可以。如果多个异常类型满足子父类的关系,则必须将子类声明在父类结构的上面,否则报错。
> catch中异常处理的方式:
① 自己编写输出的语句
② printStackTrace( ):打印异常的详细信息。 (推荐)
③ getMessage( ):获取发生异常的原因。
> try中声明的变量,出了try结构之后,就不可以进行调用了
> try-catch结构是可以嵌套使用的
开发体会:
> 对于运行时异常:
开发中,通常就不进行显示的处理了。一旦在程序执行中,出现了运行时异常,那么就会根据异常的提示信息修改代码即可。
import java.util.InputMismatchException; import java.util.Scanner; public class Test { @Test public void test1(){ try { Scanner scan = new Scanner(System.in); int num = scan.nextInt(); System.out.println(num); }catch (NullPointerException e){ System.out.println("出现了NullPointerException异常"); }catch (InputMismatchException e){ System.out.println("出现了InputMismatchException异常"); }catch (RuntimeException e){ // 这个异常不能排在上两个异常的前面,因为这个异常范围更大,下面的异常就没有机会执行,会报错! System.out.println("出现了RuntimeException异常"); } System.out.println("异常处理结束,代码继续执行....."); } @Test public void test2(){ try { String str = "123"; str = "abc"; int i = Integer.parseInt(str); System.out.println(i); }catch (NumberFormatException e){ e.printStackTrace(); // 或者 System.out.println(e.getMessage()); } System.out.println("程序结束"); } }
> 对于编译时异常:
一定要处理(catch),否则编译不通过。
public class Test { @Test public void test1(){ try { File file = new File("D:\\Hello.txt"); FileInputStream fis = new FileInputStream(file); // 可能报FileNotFoundException int data = fis.read(); // 可能报IOException while (data != -1){ System.out.println((char)data); data = fis.read(); // 可能报IOException } fis.close(); // 可能报IOException }catch (FileNotFoundException e){ e.printStackTrace(); }catch (IOException e){ // 不能写在FileNotFoundException上面,有子父类关系 e.printStackTrace(); } System.out.println("读取数据结束"); } }
finally的理解
> 我么将一定要被执行的代码声明在finally结构中。
> 更深刻的理解:无论try中或者catch中是否存在仍未被处理的异常,无论try中或catch中是否存在return语句等,finally中声明的语句都一定会执行
> finally语句和catch语句是可选的,但是finally不能单独使用
例外:使用System.exit(0)来终止当前正在运行的JVM虚拟机,fainally语句就不会再执行了。
示例一:
public class FinallyTest { public static void main(String[] args) { int result = test("12"); System.out.println(result); } public static int test(String str){ try{ Integer.parseInt(str); // String类型转换为 基本数据类型: 调用包装类的静态方法 parseXxx() return 1; }catch (NumberFormatException e){ return -1; }finally { System.out.println("test结束"); } } }
解释:
输入的是String类型12,然后通过包装类的静态方法将12转换为Int类型。
这里可以成功转型,所以catch里的语句是不会执行的。
转型后接着的是return 1语句,但是finally里面的语句必须执行,所以会在return 1之前执行finally里面的输出语句,然后才返回1.
最后将result = 1输出。
实例二:
public class FinallyTest { public static void main(String[] args) { int result = test("a"); System.out.println(result); } public static int test(String str){ try{ Integer.parseInt(str); // String类型转换为 基本数据类型: 调用包装类的静态方法 parseXxx() return 1; }catch (NumberFormatException e){ return -1; }finally { System.out.println("test结束"); return 0; } } }
解释:
在这里和上面一样,在将"a"转换为int基本数据类型,显然是会报错,所以执行的是catch里面的语句return -1。
但是必须在执行return语句之前将finally内的语句执行,故先输出输出语句,然后执行return 0。然而已经执行了return有返回,return -1语句不能够再执行了,所以result得到的返回值实际上是0,然后将result输出。
示例三:
public class FinallyTest { public static void main(String[] args) { int result = test(10); System.out.println(result); } public static int test(int num){ try{ return num; }catch (NumberFormatException e){ return num --; }finally { System.out.println("test结束"); return ++num; } } }
解释:
这里和之前相同,不会执行catch里的语句,在return num之前,会执行finally里面的语句,首先输出语句,然后执行++num,并将值返回,不会有机会再执行return num语句了。result得到的返回值就是11,将其打印。
示例四:
public class FinallyTest { public static void main(String[] args) { int result = test(10); System.out.println(result); } public static int test(int num){ try{ return num; }catch (NumberFormatException e){ return num --; }finally { System.out.println("test结束"); ++num; } } }
解释:
局部变量表里面获得的num是10,在准备return num语句的时候,将需要return的数据放在操作数栈里面,此时存放的是10.
本身下一步应该直接将这个10return回,但是还有finally语句,所以执行先输出语句,然后将局部变量表的10改为11.
然后回到return num语句,但此时注意,返回的操作数栈里的数字从头到尾都没有动过,仍然是10,再将这个10返回出去。
什么样的语句必须声明在finally中?
> 我们在开发中,有一些资源(比如:输入流、输出流,数据库连接、Socket连接等资源),在使用完以后,必须显示的进行关闭操作,否则,GC不会自动的回收着一些资源,进而导致内存泄露。
为了保证这些资源在使用完之后,不管是否出现了未被处理的异常情况下,这些资源都能够被关闭,我们必须将这些操作声明在finally里面。
方式二 throws
格式: 在方法的声明处,使用"throws 异常类型1,异常类型2, ......"
throws是否真正的处理了异常?
> 从编译是否能够通过的角度来看,看成是 出了异常时候的解决方案:继续向上抛出(throws)
> 但是,此throws的方式,仅仅是将可能出现的异常抛给了此方法的调用者。此调用者仍然需要考虑如何处理相关异常。从这个角度来说,throws的方式并不算是真正意义上的处理了异常。
方法重写的要求:(只针对编译型异常)
子类重写的方法抛出的异常类型可以和父类被重写的方法抛出的异常类型相同或者是父类被重写的的方法抛出的异常类型的子类。
import java.io.FileNotFoundException; import java.io.IOException; public class OverrideTest { public static void main(String[] args) { Father f = new Son(); try{ f.method1(); }catch(IOException e){ // 这边编译的时候认为我调用的是父类的方法, e.printStackTrace(); } } } class Father{ public void method1() throws IOException { } } class Son extends Father{ public void method1() throws FileNotFoundException { //这里的异常只能抛父类相同的异常或者是父类异常的子类 } }
这种形式并不针对运行时异常!
开发中,如何选择异常处理的两种方法
注意:这里的异常主要指的是编译时异常
> 代码程序中,涉及到资源的调用(流、数据库连接、网络连接等):try-catch-finally 保证不存在内存泄漏
> 父类被重写的方法没有throws异常,则子类重写的方法中如果出现异常:try-catch-finally 不能用throws,否则违背了规则
> 开发中,方法a中依次调用了方法b,c,d等方法,方法b,c,d之间是递进关系。此时,如果方法b,c,d中有异常,我们通常选择使用throws,而方法a中通常选择使用try-catch-finally
手动抛出异常对象
在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的异常对象。
在方法内部,在满足指定条件的情况下,使用“throw 异常类的对象”的方式抛出
注意:throw后面写的语句都不会执行
手动抛出:
public class Circle { private double radius; //半径 public Circle(){ } public Circle (double radius){this.radius = radius;} public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } }
public interface CompareObject { // 若返回值是0,代表相等;若是正数,代表当前对象大;负数则代表当前对象小 public int compareTo(Object o) throws Exception; // 因为子类ComparableCircle继承了这个接口,所以必须也要抛出异常 }
public class ComparableCircle extends Circle implements CompareObject{ public ComparableCircle() { } public ComparableCircle(double radius) { super(radius); } @Override public int compareTo(Object o) throws Exception{ // 子类抛出异常,父类必须也要抛出,否则报错 if(this == o){ return 0; } if (o instanceof ComparableCircle){ ComparableCircle c = (ComparableCircle) o; return Double.compare(this.getRadius(), c.getRadius()); }else{ // throw new RuntimeException("输入类型不匹配!"); // 如果抛出的是运行时异常,后面不处理也可以; 但是如果抛出的是编译异常,后面必须要try-catch-finally处理 throw new Exception("输入类型不匹配!"); // 通过throw手动抛出异常 后面要是还写了语句也不会执行 } } }
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(); } } }
如何理解“自动" vs "手动" 抛出异常对象
过程1:"抛"
"自动抛":程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,自动生成对应异常类的对象,并将此对象抛出
"手动抛":程序在执行的过程当中,不满足指定条件的情况下,我们主动的使用"throw + 异常类的对象"方式抛出异常对象
过程2:"抓"
狭义上讲,try-catch的方式捕获异常,并处理。
广义上来讲,把"抓"理解为处理。则此时对应着异常处理的两种方法:① try-catch-finally ② throws
面试题:throw和throws的区别
throws使用在方法的声明处,后面接的异常类型,将产生的异常对象继续向上一层抛出,属于异常处理的方式。
throw使用在方法内部,后面接异常对象,表示手动的抛出一个指定异常类的对象。
更重要的是,这两个的使用场景完全不同,解决的问题完全不同,throw用于产生异常对象,throws用于对产生的异常对象如何去处理。
自定义异常
如何自定义异常类
① 继承于现有的异常体系。通常继承于RuntimeException \ Exception
② 通常提供几个重载的构造器
③ 提供一个全局常量,声明为:
static final long serialVersionUID = XXXXXXXXXXXXX;
public class BelowZeroException extends Exception{ static final long serialVersionUID = -3387516999948L; // 提供一个异常类的时候都需要加上这个ID,并且每个不一样 public BelowZeroException() { } public BelowZeroException(String name) { super(name); } public BelowZeroException(String message,Throwable cause) { super(message,cause); } }
如何使用自定义异常类
> 在具体的代码中,满足指定条件的情况下,需要手动的使用"throw + 自定义异常类的对象"方式,将异常对象抛出。
> 如果自定义异常类是非运行时异常,则必须考虑如何处理此异常类的对象。(具体的:① try-catch-finally ② throws)
public class ThrowTest { public static void main(String[] args) { Student s1 = new Student(); try{ s1.regist(10); 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{ throw new BelowZeroException("此输入非法!"); //使用自己自定义的异常类 } } }
为什么需要自定义异常类
我们希望能够通过异常的名称就能够直接判断此异常出现的原因,所以有必要在开发场景中。
不满足我们指定的条件时,指明我们自己特有的异常类。通过此异常类的名称,就能够判断出具体出现的问题。
练习
public class BelowZeroException extends Exception{ static final long serialVersionUID = -3387516999948L; // 提供一个异常类的时候都需要加上这个ID,并且每个不一样 public BelowZeroException() { } public BelowZeroException(String name) { super(name); } public BelowZeroException(String message,Throwable cause) { super(message,cause); } }
public class DivisionDemo { public static void main(String[] args) { int a = Integer.parseInt(args[0]); int b = Integer.parseInt(args[1]); try{ int result = divide(a,b); }catch (BelowZeroException e){ System.out.println(e.getMessage()); }catch (NumberFormatException e){ System.out.println("数据类型不一致"); }catch (ArrayIndexOutOfBoundsException e){ System.out.println("缺少命令行参数"); }catch (ArithmeticException e){ System.out.println("除0"); } } public static int divide(int a,int b) throws BelowZeroException{ if(a<0 || b < 0){ throw new BelowZeroException("输入不能够小于0!"); // 注意,一定要先手动抛出才行 } return a/b; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具