java全端课--异常处理

178 阅读11分钟

一、异常

1.1 什么是异常?

异常是指在Java程序中,发生不正常的情况,使得程序无法正常运行。

1.2 异常的分类(掌握分类)

1、编译时异常:编译器“预感”到这个程序“可能”存在问题,就提醒你要注意,提前做好预案,即如果这个异常真发生了,你要怎么处理,否则编译就不通过。处理有两种态度:(1)当前方法不管它,直接抛出去,扔给调用者处理,如果是main,扔出去的话,相当于直接挂掉(2)积极处理,try-catch。

2、运行时异常:编译器“检测”不到,直到程序运行时才发生异常。

结论:

如果编译器在编译阶段就给出“预警”的异常类型,就是编译时异常,否则就是运行时异常。

1.3 异常的体系结构

Java中一切皆对象。同样,Java中的异常和错误也用对象表示。Java中所有异常和错误的根类型是java.lang.Throwable类型。

在JDK的API文档中说明了核心类库JRE中提供给我们程序员用的类、接口及其公共方法的说明。

Throwable又分为两大类:

  • Error:用于指示合理的应用程序不应该试图捕获的严重问题。例如:VirtualMachineError下的OutOfMemoryError(堆内存溢出错误), StackOverflowError(栈内存溢出错误,曾经在递归中见过)
  • Exception:指出了合理的应用程序想要捕获的条件。对于Exception的态度,建议大家(1)能避免的尽量避免(通过基础知识的把握以及条件判断来避免)(2)不能避免的,再用try-catch等机制来解决。
    • Exception又分为编译时和运行时异常(见1.2小节)。所有运行时异常都是RuntimeException及其子类。

1.4 异常的处理(掌握5个关键字)

1.4.1 try-catch

try:尝试执行xx代码。

catch:尝试捕获xx异常对象。

快捷键:选中需要{}的代码,Ctrl + Alt + T

try{
    可能发生异常的代码
}catch(异常的类型1 参数名){//参数名习惯上用e表示,当然也可以是别的
    编写打印异常的代码(前期是打印到控制台,后期是记录到日志当中) 以及 处理异常的代码
}catch(异常的类型2 参数名){//参数名习惯上用e表示,当然也可以是别的
    编写打印异常的代码(前期是打印到控制台,后期是记录到日志当中) 以及 处理异常的代码
}catch(异常的类型3 参数名){//参数名习惯上用e表示,当然也可以是别的
    编写打印异常的代码(前期是打印到控制台,后期是记录到日志当中) 以及 处理异常的代码
    
    //打印异常
    //方式一:
    //System.out.println(e);//普通信息的打印
    //方式二:
    // System.err.println(e);//错误信息的打印,默认用红色打印错误信息
    //方式三:
    // e.printStackTrace();//异常自带的标准的打印方式(前期一般用它)
}

多个catch分支,遵循从上往下依次判断的顺序,如果上面的类型匹配了,下面catch就不看了。如果多个catch的异常类型有父子类关系的话,那么子在上父在下。如果它们没有父子类关系,那么顺序可以随意。

1.4.2 try-catch-finally

finally块是用于编写无论如何都要执行的代码。解释无论如何:

  • 无论try中是否会发生异常
  • 也不管catch能不能捕获异常
  • 哪怕try-catch中有return语句

唯一能让它不执行的是System.exit(0);退出JVM

1.4.3 关键字:throws

throws的作用:用于声明当前方法中可能发生xx类型的异常,而且当前方法未处理,交给调用者处理。谁调用谁处理。

调用者最好要用try-catch处理,如果所有方法都不处理,都选择throws,那么一旦发生异常,程序就挂了,程序很脆弱。

【修饰符】 class 类名{
    【①修饰符】 ②返回值类型 ③方法名(④【形参列表】)【⑤throws 异常列表】{
        ⑥方法体语句;
    }
}

说明:throws后面写异常的类型,而且可以是多个类型,用逗号分隔。

重载重写
位置同一个类或父子类父子类中(父类或父接口)
权限修饰符不看>=,不能是private
其他修饰符不看不能是static,final
返回值类型不看(1)基本数据类型和void:完全相同
(2)引用数据类型:<=
方法名完全相同完全相同
形参列表(个数、类型、顺序)
与名字无关
完全不同完全相同
throws 异常类型列表不看总:<=
小于等于的原则是针对编译时异常类型,关于运行时异常其实编译器是检测不到的

重写的要求:两同两小一大

1.4.4 关键字:throw

throwsthrow
出现的位置出现在方法的()后面出现构造器/成员方法的方法体{ } 里面
作用告知调用者当前方法可能发生xx类型的异常手动抛出一个异常对象,只要这个语句执行了,异常就发生了

1.5 Object类的方法

1.5.1 Object类的clone方法(了解)

Object类的方法,所有类都会继承。所以,我们需要了解Object类的所有方法。

clone方法用于克隆对象的,就是复制一个一模一样的对象。

子类如果需要使用克隆功能,需要实现Cloneable接口,然后重写clone方法。

1.5.2 Object类finalize方法

finalize方法现在已经过时了,不推荐我们使用了。

面试题:final、finally、finalize有什么区别?

final:修饰类不能被继承,修饰方法不能被重写,修饰变量值不能被修改。

finally:结合try-catch使用,无论如何都要执行的代码放finally块,一般写资源关闭代码,后面学习的IO流,网络连接等资源对象的关闭。

​ 应该避免在finally类中计算的代码,避免写return语句。

finalize:Object类的一个方法,早期的时候是用于GC(垃圾回收器)在回收对象之前,做清理工作。

二、工具

2.1 JUnit单元测试工具

JUnit是第三方的工具,所以用它的时候,需要单独下载它的jar包(库)。

1、如何下载

2、如何使用

package com.mytest.junit;

import org.junit.Test;

public class TestJUnit {
    @Test
    public void test1(){
        System.out.println("hello");
    }

    @Test
    public void test2(){
        System.out.println("hello2");
    }
}

要求:

  • 这个类本身必须是public
  • 这个类只能是有唯一的public无参构造
  • 只能在public,void,()的方法上,加@Test注解

3、键盘输入的设置

特殊:需要单独开启JUnit的键盘输入功能。

-Deditable.java.test.console=true
@Test
    public void test3(){
        Scanner input = new Scanner(System.in);

        System.out.print("请输入一个整数:");
        int num = input.nextInt();
        System.out.println("num = " + num);

        input.close();
    }

2.2 lombok

它也不是JDK官方的,也是第三方,也要引入jar包。

IDEA 开启注解:

  • @Data //自动生成get/set,equals和hashCode,toString等方法
  • @NoArgsConstructor //生成无参构造
  • @AllArgsConstructor //生成全参构造,为所有实例变量初始化的构造器
package com.mytest.lombok;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data //自动生成get/set,equals和hashCode,toString等方法
@NoArgsConstructor //生成无参构造
@AllArgsConstructor //生成全参构造,为所有实例变量初始化的构造器
public class Employee {
    private String name;
    private double salary;
}

三、常用类的API

3.1 包装类

3.1.1 什么是包装类

Java是面向对象的编程语言,但是它不纯。因为它包含8种基本数据类型和void,这些类型都不是引用数据类型。但是,Java后面的很多API,或新特性都是只为对象服务的,那么8种基本数据类型的数据就无法使用那些新的API或新特性,例如:泛型、集合等。

为了解决这个问题,Java为8种基本数据类型分别设置了包装类,使得基本数据类型和对象之间可以自由切换。

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

3.1.2 自动装箱与拆箱

JDK5之前需要手动装箱与拆箱,比较麻烦。JDK5之后才支持自动装箱与拆箱。

  • 装箱:基本数据类型 -> 包装类对象
  • 拆箱:包装类对象 -> 基本数据类型的数据

自动装箱与拆箱,只支持对应类型之间,对应关系看上面的表格。

3.1.3 装箱和拆箱的应用

  • 当一个包装类对象与一个基本数据类型 比较 == 和 != ,或大小比较等,都会把包装类对象拆箱
  • 如果是两个包装类对象之间 == 和 != 比较,不拆箱
  • 如果是两个包装类对象之间的>, <等大小比较,也会自动拆箱。(对象之间是无法 >< 比较的)
package com.mytest.wrap;

import org.junit.Test;

public class TestBoxing {
    @Test
    public void test1(){
        Integer i = 1;//自动装箱,左边是引用数据类型,右边是基本数据类型
        int j = i;//自动拆箱,i是对象,j是基本数据类型
    }

    @Test
    public void test2(){
        Integer i = 200;
        int j = 200;
        System.out.println(i == j);//把i自动拆箱,变为2个int值比较
    }

    @Test
    public void test3(){
        Integer i = 200;
        Integer j = 200;
        System.out.println(i == j);//false  比较两个对象的地址值
    }

    @Test
    public void test4(){
        Integer i = 200;
        Double j = 200.0;
        //System.out.println(i == j);//报错, 两个对象的类型不一致,它们之间也没有父子类关系,是无法比较地址是否相等

    }

    @Test
    public void test5(){
        Integer i = 200;
        double j = 200.0;
        System.out.println(i == j);//i会自动拆箱
    }

    @Test
    public void test6(){
        Integer i = 200;
        Double j = 200.0;
        System.out.println(i > j);//两个都拆箱会基本数据类型
    }

    @Test
    public void test7(){
       // Double d = 1; //无法自动装箱
       // int a = d;//无法自动拆箱
        //Double 对应的类型 double
        //int 对应的类型是Integer
        //int 与 Double不是对应类型

        double d = 1;//基本数据类型的自动提升
        Double d2 = 1.0;
        Double d3 = 1D; //D代表double类型
    }
}

3.1.4 特殊2点

1、对象不可变
2、部分包装类对象可以共享

共享的好处:可以大量的减少对象的数量。

共享的前提:对象不可变。

基本数据类型包装类缓存对象/共享对象包装类和基本数据的数据范围
byteByte[-128, 127][-128, 127]
shortShort[-128, 127][-32768,32767]
intInteger[-128, 127]很大
longLong[-128, 127]很大
floatFloat很大
doubleDouble很大
charCharacter[0,127][0,65535]
booleanBooleantrue和falsetrue和false

3.1.5 常用的常量和方法

  • 包装类.MAX_VALUE
  • 包装类.MIN_VALUE
  • 包装类.compare(参数1,参数2)
  • 包装类.parseXxx(字符串)
    • Integer.parseInt(字符串)
    • Double.parseDouble(字符串)
package com.mytest.wrap;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data //自动生成get/set,equals和hashCode,toString等方法
@NoArgsConstructor //生成无参构造
@AllArgsConstructor //生成全参构造,为所有实例变量初始化的构造器
public class Employee implements  Comparable{
    private int id;
    private String name;
    private double salary;
    private int age;

    @Override
    public int compareTo(Object o) {
        return this.id - ((Employee)o).id;
    }
}
package com.mytest.wrap;

import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;

public class TestAPI {
    @Test
    public void test1(){
        System.out.println("int整数的最大值:" + Integer.MAX_VALUE);
        System.out.println("int整数的最小值:" + Integer.MIN_VALUE);
        /*
        int整数的最大值:2147483647
        int整数的最小值:-2147483648
         */
    }

    @Test
    public void test2(){
        Employee[] arr = new Employee[4];
        arr[0] = new Employee(2,"张三",15000,23);
        arr[1] = new Employee(1,"李四",16000,26);
        arr[2] = new Employee(3,"王五",12000,27);
        arr[3] = new Employee(4,"赵六",19000,21);

        System.out.println("按照id升序排序:");
        //直接使用java.util.Arrays工具类
        Arrays.sort(arr);//会按照元素的compareTo方法来比较两个对象的大小

        //增强for
        for (Employee e : arr) {
            System.out.println(e);
        }

        System.out.println("按照年龄升序排序:");
        //按照年龄排序,从小到大
        Comparator c = new Comparator(){
            @Override
            public int compare(Object o1, Object o2) {
                Employee e1 = (Employee) o1;
                Employee e2 = (Employee) o2;
//                return e1.getAge() - e2.getAge();
                return Integer.compare(e1.getAge(),e2.getAge());//作用与上面相减的相同
            }
        };
        Arrays.sort(arr, c);//让它用c对象的compare方法来比较两个员工对象的大小
        //再次遍历
        for (Employee e : arr) {
            System.out.println(e);
        }

        System.out.println("按照薪资升序排序:");
        Comparator c2 = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Employee e1 = (Employee) o1;
                Employee e2 = (Employee) o2;
                /*if(e1.getSalary() > e2.getSalary()){
                    return 1;
                }else if(e1.getSalary() < e2.getSalary()){
                    return -1;
                }
                return 0;*/
                return Double.compare(e1.getSalary(),e2.getSalary());
            }
        };
        Arrays.sort(arr, c2);//让它用c2对象的compare方法来比较两个员工对象的大小
        //再次遍历
        for (Employee e : arr) {
            System.out.println(e);
        }
    }

    @Test
    public void test4(){
        //整数 -> String
        int num = 1;
        String str = num + "";
        String str2 = String.valueOf(num);

        //String -> int
        String str3 = "123";
        String str4 = "456";
        System.out.println(str3 + str4);//拼接 123456
        int a = Integer.parseInt(str3);
        int b = Integer.parseInt(str4);
        System.out.println(a + b);//求和
    }

    @Test
    public void test5(){
        String s1 = "3.14";
        String s2 = "6.36";
        double d1 = Double.parseDouble(s1);
        double d2 = Double.parseDouble(s2);
        System.out.println(d1 + d2);
    }
}