10. 注解(Annotation)

10.1 注解概述

10.1.1 什么是注解

注解(Annotation)是从JDK5.0开始引入,以“@注解名”在代码中存在。例如:

@Override
@Deprecated
@SuppressWarnings(value=”unchecked”)

Annotation 可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。还可以添加一些参数值,这些信息被保存在 Annotation 的 “name=value” 对中。

注解可以在类编译、运行时进行加载,体现不同的功能。

10.1.2 注解与注释

注解也可以看做是一种注释,通过使用 Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。但是,注解,不同于单行注释和多行注释。

10.1.3 注解的重要性

在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码XML配置等。

未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,Struts2有一部分也是基于注解的了。注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式

介绍

注解不会直接影响代码的执行,但它们可以在编译时、类加载时或运行时被读取,并用于生成代码、编译检查、处理代码等。Java 5引入了注解,并且随着Java的发展,注解的使用变得越来越广泛。

以下是一些Java注解的基本特性和用途:

  1. 元数据:注解可以为类、方法、变量等添加额外的信息。
  2. 编译时处理:一些注解在编译时被处理,例如@Override,它检查被注解的方法是否确实覆盖了父类中的方法。
  3. 运行时处理:一些注解在运行时被处理,例如@Transactional,它用于声明事务边界。
  4. 类文件处理:注解信息可以被保留在编译后的类文件中,供反射使用。
  5. 自定义注解:除了Java自带的注解,开发者可以创建自定义注解来满足特定的需求。

Java中一些常用的注解包括:

注解的基本语法如下:

public @interface MyAnnotation {
    String value();
}

使用注解:

@MyAnnotation(value = "SomeValue")
public class MyClass {
    // ...
}

注解可以有参数,这些参数在声明注解时必须指定。注解也可以没有参数

10.2 常见的Annotation作用

示例1:生成文档相关的注解

@author 标明开发该类模块的作者多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明如果没有参数就不能写
@return 对方法返回值的说明如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明如果方法没有用throws显式抛出的异常就不能写
package com.annotation.javadoc;
/**
 * @author 尚硅谷-宋红康
 * @version 1.0
 * @see Math.java
 */
public class JavadocTest {
    /**
     * 程序的主方法,程序的入口
     * @param args String[] 命令行参数
     */
    public static void main(String[] args) {
    }
    
    /**
     * 求圆面积的方法
     * @param radius double 半径值
     * @return double 圆的面积
     */
    public static double getArea(double radius){
        return Math.PI * radius * radius;
    }
}

示例2:在编译时进行格式检查(JDK内置的三个基本注解)

@Override: 限定重写父类方法,该注解只能用于方法

@Deprecated: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择

@SuppressWarnings: 抑制编译器警告

package com.annotation.javadoc;
 
public class AnnotationTest{
 
    public static void main(String[] args) {
        @SuppressWarnings("unused")
        int a = 10;
    }
    @Deprecated
    public void print(){
        System.out.println("过时的方法");
    }
 
    @Override
    public String toString() {
        return "重写的toString方法()";
    }
}

示例3:跟踪代码依赖性,实现替代配置文件功能

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) { }
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        doGet(request, response);
    }  
}
 <servlet>
    <servlet-name>LoginServlet</servlet-name>

    <servlet-class>com.servlet.LoginServlet</servlet-class>

  </servlet>

  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>

    <url-pattern>/login</url-pattern>

  </servlet-mapping>
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
public void buyBook(String username, String isbn) {
    //1.查询书的单价
    int price = bookShopDao.findBookPriceByIsbn(isbn);
    //2. 更新库存
    bookShopDao.updateBookStock(isbn);	
    //3. 更新用户的余额
    bookShopDao.updateUserAccount(username, price);
}
<!-- 配置事务属性 -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
       <tx:attributes>
       <!-- 配置每个方法使用的事务属性 -->
       <tx:method name="buyBook" propagation="REQUIRES_NEW" 
     isolation="READ_COMMITTED"  read-only="false"  timeout="3" />
       </tx:attributes>

</tx:advice>

10.3 三个最基本的注解

10.3.1 @OverRide

10.3.2 @deprecated

10.3.3 @SuppressWarnings

示例代码:

package com.atguigu.annotation;

import java.util.ArrayList;

public class TestAnnotation {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        int i;

        ArrayList list = new ArrayList();
        list.add("hello");
        list.add(123);
        list.add("world");

        Father f = new Son();
        f.show();
        f.methodOl();
    }
}

class Father{
    @Deprecated
    void show() {
        System.out.println("Father.show");
    }
    void methodOl() {
        System.out.println("Father Method");
    }
}

class Son extends Father{
/*	@Override
    void method01() {
        System.out.println("Son Method");
    }*/
}

10.4 元注解

JDK1.5在java.lang.annotation包定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

(1)**@target:**用于描述注解的使用范围

(2)**@retention:**用于描述注解的生命周期

(3)@documented:表明这个注解应该被 javadoc工具记录。

(4)**@inherited:**允许子类继承父类中的注解

示例代码:

package java.lang;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

拓展:元数据

String name = "Tom";

10.5 自定义注解的使用

一个完整的注解应该包含三个部分:
(1)声明
(2)使用
(3)读取

10.5.1 声明自定义注解

元注解】
【修饰符】 @interface 注解名{
    【成员列表】
}
package com.atguigu.annotation;

import java.lang.annotation.*;

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}
package com.atguigu.annotation;

import java.lang.annotation.*;

@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String columnName();
    String columnType();
}

10.5.2 使用自定义注解

package com.atguigu.annotation;

@Table("t_stu")
public class Student {
    @Column(columnName = "sid",columnType = "int")
    private int id;
    @Column(columnName = "sname",columnType = "varchar(20)")
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

10.5.3 读取和处理自定义注解

自定义注解必须配上注解的信息处理流程才有意义。

我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。

具体的使用见《尚硅谷_宋红康_第17章_反射机制.md》

10.6 JUnit单元测试

10.6.1 测试分类

**黑盒测试:**不需要写代码,给输入值,看程序是否能够输出期望的值。

**白盒测试:**需要写代码的。关注程序具体的执行流程。

10.6.2 JUnit单元测试介绍

JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个测试框架(regression testing framework),供Java开发人员编写单元测试之用。

JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。

要使用JUnit,必须在项目的编译路径中引入JUnit的库,即相关的.class文件组成的jar包。jar就是一个压缩包,压缩包都是开发好的第三方(Oracle公司第一方,我们自己第二方,其他都是第三方)工具类,都是以class文件形式存在的。

10.6.3 引入本地JUnit.jar

第1步:在项目中File-Project Structure中操作:添加Libraries库

其中,junit-libs包内容如下:

第2步:选择要在哪些module中应用JUnit库

第3步:检查是否应用成功

注意Scope:选择Compile,否则编译时,无法使用JUnit。

第4步:下次如果有新的模块要使用该libs库,这样操作即可

10.6.4 编写和运行@test单元测试方法

JUnit4版本,要求@test标记的方法必须满足如下要求:

package com.atguigu.junit;

import org.junit.Test;

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

    @Test
    public void test02(){
        System.out.println("TestJUnit.test02");
    }

    @Test
    public void test03(){
        System.out.println("TestJUnit.test03");
    }
}

10.6.5 设置执行JUnit用例时支持控制台输入

1. 设置数据:

默认情况下,在单元测试方法中使用Scanner时,并不能实现控制台数据的输入。需要做如下设置:

idea64.exe.vmoptions配置文件中加入下面一行设置,重启idea后生效。

-Deditable.java.test.console=true

2. 配置文件位置:

添加完成之后,重启IDEA即可。

3. 如果上述位置设置不成功,需要继续修改如下位置

修改位置1:IDEA安装目录的bin目录(例如:D:\develop_tools\IDEA\IntelliJ IDEA 2022.1.2\bin)下的idea64.exe.vmoptions文件。

修改位置2:C盘的用户目录C:\Users\用户名\AppData\Roaming\JetBrains\IntelliJIdea2022.1 下的idea64.exe.vmoptions`件。

10.6.6 定义test测试方法模板

选中自定义的模板组,点击”+”(1.Live Template)来定义模板。

框架 = 注解 + 反射 + 设计模式

11. 包装类

public class WrapperTest {
    public static void main(String[] args) {
        //基本数据类型->包装类
        int num1 = 10;
        //将基本数据类型int转换为包装类Integer
        Integer integer = num1;
        //输出包装类Integer的字符串表示
        System.out.println(integer.toString());
        //输出包装类Integer的值
        System.out.println(integer);

        //包装类->基本数据类型
        Integer integer1 = new Integer(100);
        //将包装类Integer转换为基本数据类型int
        int num2 = integer1.intValue();
        //输出基本数据类型int的值
        System.out.println(num2);

        boolean b = true;
        //将基本数据类型boolean转换为包装类Boolean
        Boolean boolean1 = b;
        //输出包装类Boolean的字符串表示
        System.out.println(boolean1.toString());
        //输出包装类Boolean的值
        System.out.println(boolean1);

        Boolean boolean2 = new Boolean(true);
        //将包装类Boolean转换为基本数据类型boolean
        boolean b1 = boolean2.booleanValue();
        //输出基本数据类型boolean的值
        System.out.println(b1);

        String str = "false";
        //将字符串转换为包装类Boolean
        Boolean boolean3 = Boolean.valueOf(str);
        //输出包装类Boolean的值
        System.out.println(boolean3);

        String str1 = "123";
        //将字符串转换为包装类Boolean
        Boolean boolean4 = Boolean.valueOf(str1);
        System.out.println(boolean4);//输出为false
        //只要不是true 就转化为false

        String str2 = "True";
        //将字符串转换为包装类Boolean
        Boolean boolean5 = Boolean.valueOf(str2);
        System.out.println(boolean5);//输出为true

    }
}
//包装类转化为基本数据类型
        int num3 = boolean5.intValue();
        System.out.println(num3);//输出为1

11.1 为什么需要包装类

Java提供了两个类型系统,基本数据类型引用数据类型。使用基本数据类型在于效率,然而当要使用只针对对象设计的API或新特性(例如泛型),怎么办呢?例如:

为了使得基本数据类型的变量具有引用数据类型的特征(比如 封装性 多态性),我们给各个基本类型都提供包装类以获得引用数据类型的特征

为什么需要转换

一方面,在有些场景下,需要基本数据类型对应的包装类的对象。此时就需要将基本数据类型变量转换为包装类的对象

比如

ArrayList 的 add(Object obj); Object 类的 equals(Object obj)

对于包装类来讲,既然使用的是对象,那么对象是不能进行加减乘除运算的,为了能进行运算,我们就需要将它转化为基本数据类型

包装类的命名一般为基本数据类型首字母大写

特殊

//情况1:方法形参
Object类的equals(Object obj)

//情况2:方法形参
ArrayList类的add(Object obj)
//没有如下的方法:
add(int number)
add(double d)
add(boolean b)

//情况3:泛型
Set<T>
List<T>
Cllection<T>
Map<K,V>

11.2 有哪些包装类

Java针对八种基本数据类型定义了相应的引用类型:包装类(封装类)。有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。

封装以后的,内存结构对比:

public static void main(String[] args){
    int num = 520;
    Integer obj = new Integer(520);
}

11.3 自定义包装类

public class MyInteger {
    int value;

    public MyInteger() {
    }

    public MyInteger(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

11.4 包装类与基本数据类型间的转换

public class WaperTest {
    public static void main(String[] args) {
        //基本数据类型,包装类 -> String类型
        //方式一
        
        int a = 10;
        //将基本数据类型a转换为字符串类型
        String str = a + "";
        System.out.println(str);

        //方式二
        //将基本数据类型a转换为字符串类型
        String str1 = String.valueOf(a);
        System.out.println(str1);

        //String转化为基本数据类型
        //调用包装类的parseXxx方法
        String str2 = "123";
        //将字符串类型str2转换为int类型
        int i = Integer.parseInt(str2);
        System.out.println(i);
        
    }
}

11.4.1 装箱

装箱:把基本数据类型转为包装类对象

转为包装类的对象,是为了使用专门为对象设计的API和特性

基本数值---->包装对象

Integer obj1 = new Integer(4);//使用构造函数函数
Float f = new Float(“4.56”);
Long l = new Long(“asdf”);  //NumberFormatException

Integer obj2 = Integer.valueOf(4);//使用包装类中的valueOf方法

11.4.2 拆箱

拆箱:把包装类对象拆为基本数据类型

转为基本数据类型,一般是因为需要运算,Java中的大多数运算符是为基本数据类型设计的。比较、算术等

包装对象---->基本数值

Integer obj = new Integer(4);
int num1 = obj.intValue();

自动装箱与拆箱:

由于我们经常要做基本类型与包装类之间的转换,从JDK5.0 开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:

Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。

注意:只能与自己对应的类型之间才能实现自动装箱与拆箱。

Integer i = 1;
Double d = 1;//错误的,1是int类型

11.5 基本数据类型、包装类与字符串间的转换

(1)基本数据类型转为字符串

**方式1:**调用字符串重载的valueOf()方法

int a = 10;
//String str = a;//错误的

String str = String.valueOf(a);

**方式2:**更直接的方式

int a = 10;

String str = a + "";

(2)字符串转为基本数据类型

**方式1:**除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型,例如:

**方式2:**字符串转为包装类,然后可以自动拆箱为基本数据类型

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

**方式3:**通过包装类的构造器实现

int a = Integer.parseInt("整数的字符串");
double d = Double.parseDouble("小数的字符串");
boolean b = Boolean.parseBoolean("true或false");

int a = Integer.valueOf("整数的字符串");
double d = Double.valueOf("小数的字符串");
boolean b = Boolean.valueOf("true或false");

int i = new Integer(“12”);

其他方式小结:

11.6 包装类的其它API

11.6.1 数据类型的最大最小值

Integer.MAX_VALUE和Integer.MIN_VALUE
    
Long.MAX_VALUE和Long.MIN_VALUE
    
Double.MAX_VALUE和Double.MIN_VALUE

11.6.2 字符转大小写

Character.toUpperCase('x');

Character.toLowerCase('X');

11.6.3 整数转进制

Integer.toBinaryString(int i) 
    
Integer.toHexString(int i)
    
Integer.toOctalString(int i)

11.6.4 比较的方法

Double.compare(double d1, double d2)
    
Integer.compare(int x, int y) 

11.7 包装类对象的特点

11.7.1 包装类缓存对象

包装类 缓存对象
Byte -128~127
Short -128~127
Integer -128~127
Long -128~127
Float 没有
Double 没有
Character 0~127
Boolean true和false
Integer a = 1;
Integer b = 1;
System.out.println(a == b);//true

Integer i = 128;
Integer j = 128;
System.out.println(i == j);//false

Integer m = new Integer(1);//新new的在堆中
Integer n = 1;//这个用的是缓冲的常量对象,在方法区
System.out.println(m == n);//false

Integer x = new Integer(1);//新new的在堆中
Integer y = new Integer(1);//另一个新new的在堆中
System.out.println(x == y);//false
Double d1 = 1.0;
Double d2 = 1.0;
System.out.println(d1==d2);//false 比较地址,没有缓存对象,每一个都是新new的

11.7.2 类型转换问题

Integer i = 1000;
double j = 1000;
System.out.println(i==j);//true  会先将i自动拆箱为int,然后根据基本数据类型“自动类型转换”规则,转为double比较
Integer i = 1000;
int j = 1000;
System.out.println(i==j);//true 会自动拆箱,按照基本数据类型进行比较
Integer i = 1;
Double d = 1.0
System.out.println(i==d);//编译报错

11.7.3 包装类对象不可变

public class TestExam {
    public static void main(String[] args) {
        int i = 1;
        Integer j = new Integer(2);
        Circle c = new Circle();
        change(i,j,c);
        System.out.println("i = " + i);//1
        System.out.println("j = " + j);//2
        System.out.println("c.radius = " + c.radius);//10.0
    }
    
    /*
     * 方法的参数传递机制:
     * (1)基本数据类型:形参的修改完全不影响实参
     * (2)引用数据类型:通过形参修改对象的属性值,会影响实参的属性值
     * 这类Integer等包装类对象是“不可变”对象,即一旦修改,就是新对象,和实参就无关了
     */
    public static void change(int a ,Integer b,Circle c ){
        a += 10;
//		b += 10;//等价于  b = new Integer(b+10);
        c.radius += 10;
        /*c = new Circle();
        c.radius+=10;*/
    }
}
class Circle{
    double radius;
}

11.8 练习

笔试题:如下两个题目输出结果相同吗?各是什么。

Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);//1.0
Object o2;
if (true)
    o2 = new Integer(1);
else
    o2 = new Double(2.0);
System.out.println(o2);//1

面试题:

public void method1() {
    Integer i = new Integer(1);
    Integer j = new Integer(1);
    System.out.println(i == j);

    Integer m = 1;
    Integer n = 1;
    System.out.println(m == n);//

    Integer x = 128;
    Integer y = 128;
    System.out.println(x == y);//
}

练习:

利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。

import java.util.Scanner;
import java.util.Vector;

public class ScoreTest {

    public static void main(String[] args) {/*...*/
        //创建Vector对象
        Vector v = new Vector();
        //从键盘获取多个学生成绩
        Scanner input = new Scanner(System.in);
        int Max = 0;
        while (true) {
            //输入学生成绩,直到负数结束
            int score = input.nextInt();
            if (score < 0) {
                break;
            }

            //将学生成绩添加到Vector中
            v.add(score);

            if (score > Max) {
                Max = score;
            }
        }
        input.close();
        //输出Vector中所有学生成绩
        for (int i = 0; i < v.size(); i++) {
            //拆箱
            Object obj = v.get(i);
            int score = (Integer) obj;
            System.out.println(score);

            //System.out.println(v.get(i));
            if (Max - score < 10) {
                System.out.println("A");
            } else if (Max - score < 20) {
                System.out.println("B");
            } else if (Max - score < 30) {
                System.out.println("C");
            } else {
                System.out.println("D");
            }

            System.out.println("Student" + i + " score is " + score);
        }
        System.out.println("Max score is " + Max);
    }

}

1. 异常概述

1.1 什么是生活的异常

男主角小明每天开车上班,正常车程1小时。但是,不出意外的话,可能会出现意外。

出现意外,即为异常情况。我们会做相应的处理。如果不处理,到不了公司。处理完了,就可以正常开车去公司。

4.异常的体系结构

java.lang.Throwable:异常体系的根父类

1-java.ang.Error:错误。Java虛拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。

一般不编写针对性的代码进行处理。

----StackOverflowError、0utOfMemoryError

---java.lang.Exception:异常。我们可以编写针对性的代码进行处理。

----编译时异常:(受检异常)在执行javac.exe命令时,出现的异常

----运行时异常:(非受检异常)在执行java.exe命令时,出现的异常,

Java异常处理是Java程序中非常重要的一部分,它允许程序在发生错误时不会立即崩溃,而是能够优雅地处理错误或异常情况。Java异常处理机制基于几个关键概念和组件:

1. 异常类

Java中的所有异常都是Throwable类的子类。Throwable有两个重要的子类:Exception(可检查异常)和Error(系统错误)。

2. 异常处理关键字

3. 异常处理流程

  1. 抛出异常:当异常发生时,JVM会抛出一个异常实例。
  2. 捕获异常:可以使用try-catch语句捕获异常。
  3. 处理异常:在catch块中处理异常,比如打印错误信息、记录日志、恢复操作等。
  4. 资源清理:在finally块中进行资源清理,如关闭文件流或数据库连接。

4. 异常处理示例

try {
    // 可能抛出异常的代码
    int division = 10 / 0;
} catch (ArithmeticException e) {
    // 处理除以零的异常
    System.out.println("Cannot divide by zero!");
} finally {
    // 资源清理代码
    System.out.println("Cleanup resources");
}

5. 自定义异常

你可以创建自己的异常类来表示特定的错误情况,这通常是通过继承Exception类或其子类来实现的。

public class MyCustomException extends Exception {
    public MyCustomException(String message) {
        super(message);
    }
}

6. 异常传播

7. 异常链

当一个异常的处理器中又抛出了另一个异常时,可以使用Throwable类的initCause方法将原始异常设置为新异常的原因,这样可以保留原始异常的信息。

1.2 什么是程序的异常

在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式问题读取文件是否存在网络是否始终保持通畅等等。

异常指的并不是语法错误和逻辑错误。语法错了,编译不通过,不会产生字节码文件,根本不能运行。

代码逻辑错误,只是没有得到想要的结果,例如:求a与b的和,你写成了a-b

1.3 异常的抛出机制

Java中是如何表示不同的异常情况,又是如何让程序员得知,并处理异常的呢?

Java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理;如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止。

举例:

运行下面的程序,程序会产生一个数组角标越界异常ArrayIndexOfBoundsException。我们通过图解来解析下异常产生和抛出的过程。

public class ArrayTools {
    // 对给定的数组通过给定的角标获取元素。
    public static int getElement(int[] arr, int index) {
        int element = arr[index];
        return element;
    }
}

测试类

public class ExceptionDemo {
    public static void main(String[] args) {
        int[] arr = { 34, 12, 67 };
        intnum = ArrayTools.getElement(arr, 4)
        System.out.println("num=" + num);
        System.out.println("over");
    }
}

上述程序执行过程图解:

1.4 如何对待异常

对于程序出现的异常,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是程序员在编写程序时,就充分考虑到各种可能发生的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常的检测、以及异常的处理,保证代码的健壮性

2. Java异常体系

2.1 Throwable

java.lang.Throwable 类是Java程序执行过程中发生的异常事件对应的类的根父类

Throwable中的常用方法:

2.2 Error 和 Exception

Throwable可分为两类:Error和Exception。分别对应着java.lang.Errorjava.lang.Exception两个类。

**Error:**Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。

Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序也会挂掉。例如:

说明:

  1. 无论是Error还是Exception,还有很多子类,异常的类型非常丰富。当代码运行出现异常时,特别是我们不熟悉的异常时,不要紧张,把异常的简单类名,拷贝到API中去查去认识它即可。
  2. 我们本章讲的异常处理,其实针对的就是Exception。

2.3 编译时异常和运行时异常

Java程序的执行分为编译时过程和运行时过程。有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。

因此,根据异常可能出现的阶段,可以将异常分为:

3. 常见的错误和异常

3.1 Error

最常见的就是VirtualMachineError,它有两个经典的子类:StackOverflowError、OutOfMemoryError。

package com.atguigu.exception;

import org.junit.Test;

public class TestStackOverflowError {
    @Test
    public void test01(){
        //StackOverflowError
        recursion();
    }

    public void recursion(){ //递归方法
        recursion(); 
    }
}
package com.atguigu.exception;

import org.junit.Test;

public class TestOutOfMemoryError {
    @Test
    public void test02(){
        //OutOfMemoryError
        //方式一:
        int[] arr = new int[Integer.MAX_VALUE];
    }
    @Test
    public void test03(){
        //OutOfMemoryError
        //方式二:
        StringBuilder s = new StringBuilder();
        while(true){
            s.append("atguigu");
        }
    }
}

3.2 运行时异常

package com.atguigu.exception;

import org.junit.Test;

import java.util.Scanner;

public class TestRuntimeException {
    @Test
    public void test01(){
        //NullPointerException
        int[][] arr = new int[3][];
        System.out.println(arr[0].length);
    }

    @Test
    public void test02(){
        //ClassCastException
        Object obj = 15;
        String str = (String) obj;
    }

    @Test
    public void test03(){
        //ArrayIndexOutOfBoundsException
        int[] arr = new int[5];
        for (int i = 1; i <= 5; i++) {
            System.out.println(arr[i]);
        }
    }

    @Test
    public void test04(){
        //InputMismatchException
        Scanner input = new Scanner(System.in);
        System.out.print("请输入一个整数:");//输入非整数
        int num = input.nextInt();
        input.close();
    }

    @Test
    public void test05(){
        int a = 1;
        int b = 0;
        //ArithmeticException
        System.out.println(a/b);
    }
}

3.3 编译时异常

package com.atguigu.exception;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class TestCheckedException {
    @Test
    public void test06() {
        Thread.sleep(1000);//休眠1秒  InterruptedException
    }

    @Test
    public void test07(){
        Class c = Class.forName("java.lang.String");//ClassNotFoundException
    }

    @Test
    public void test08() {
        Connection conn = DriverManager.getConnection("....");  //SQLException
    }
    @Test
    public void test09()  {
        FileInputStream fis = new FileInputStream("尚硅谷Java秘籍.txt"); //FileNotFoundException
    }
    @Test
    public void test10() {
        File file = new File("尚硅谷Java秘籍.txt");
        FileInputStream fis = new FileInputStream(file);//FileNotFoundException
        int b = fis.read();//IOException
        while(b != -1){
            System.out.print((char)b);
            b = fis.read();//IOException
        }
        
        fis.close();//IOException
    }
}

4. 异常的处理

4.1 异常处理概述

在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0数据为空输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长臃肿可读性差,程序员需要花很大的精力“堵漏洞”。因此采用异常处理机制。

Java异常处理

Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

Java异常处理的方式:

方式一:try-catch-finally

方式二:throws + 异常类型

4.2 方式1:捕获异常(try-catch-finally)

Java提供了异常处理的抓抛模型

4.2.1 try-catch-finally基本格式

捕获异常语法如下:

try{
    ......	//可能产生异常的代码
}
catch( 异常类型1 e ){
    ......	//当产生异常类型1型异常时的处置措施
}
catch( 异常类型2 e ){
    ...... 	//当产生异常类型2型异常时的处置措施
}  
finally{
    ...... //无论是否发生异常,都无条件执行的语句
} 

1、整体执行过程:

当某段代码可能发生异常,不管这个异常是编译时异常(受检异常)还是运行时异常(非受检异常),我们都可以使用try块将它括起来,并在try块下面编写catch分支尝试捕获对应的异常对象。

2、try

3、catch (Exceptiontype e)

将可能出现异常的代码声明在try语句中。一旦代码出现异常,就会自动生成一个对应异常类的对象。并将此对象抛出。

针对于try中抛出的异常类的对象,使用之后的catch语句进行匹配。一旦匹配上,就进入catch语句块进行处理。

一旦处理接触,代码就可继续向下执行

如果声明了多个 catch 结构 ,不同的异常类型在不存在的子父类关系的情况下,声明前后顺序无所谓,但是若满足父类关系,要将父类声明在子类下面,否则编译报错

catch 中异常的处理方式

1, 自己编写输出语句

2, printStackTrace() 打印异常的详细信息(推荐 用的多)

3 , getMessage() 获取发生异常的原因

其它说明

try 中声明的变量出来 try 就无法调用

开发体会:

对于运行时异常:
开发中,通常就不进行显示的处理了,
一旦在程序执行中,出现了运行时异常,那么就根据异常的提示信息修改代码即可
对于编译时异常:
一定要进行处理。否则编译不通过。

4.2.2 使用举例

举例1:

public class IndexOutExp {
    public static void main(String[] args) {
        String friends[] = { "lisa", "bily", "kessy" };
        try {
            for (int i = 0; i < 5; i++) {
            System.out.println(friends[i]);
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("index err");
        }
        System.out.println("\nthis is the end");
    }
}

举例2:

public class DivideZero1 {
    int x;
    public static void main(String[] args) {
        int y;
        DivideZero1 c = new DivideZero1();
        try {
            y = 3 / c.x;
        } catch (ArithmeticException e) {
            System.out.println("divide by zero error!");
        }
        System.out.println("program ends ok!");
    }
}

举例3:

@Test
public void test1(){
    try{
        String str1 = "atguigu.com";
        str1 = null;
        System.out.println(str1.charAt(0));
    }catch(NullPointerException e){
        //异常的处理方式1
        System.out.println("不好意思,亲~出现了小问题,正在加紧解决...");	
    }catch(ClassCastException e){
        //异常的处理方式2
        System.out.println("出现了类型转换的异常");
    }catch(RuntimeException e){
        //异常的处理方式3
        System.out.println("出现了运行时异常");
    }
    //此处的代码,在异常被处理了以后,是可以正常执行的
    System.out.println("hello");
}

举例4:

4.2.3 finally使用及举例

finally的使用说明:

finally的理解

我们将一定要被执行的代码声明在finally结构中。

更深刻的理解:无论try中或catch中是否存在仍未被处理的异常,无论try中或catch中是否存在return语句等,finally

中声明的语句都一定要被执行,

但有一个例外 System.exit(0)会强行终止 java 虚拟机

什么样的代码我们一定要声明在finally中呢?

我们在开发中,有一些资源(比如:输入流、输出流,数据库连接、Socket连接等资源),在使用完以后,必须显式的进行
关闭操作,否则,GC不会自动的回收这些资源。进而导致内存的泄漏。
为了保证这些资源在使用完以后,不管是否出现了未被处理的异常的情况下,这些资源能被关闭。我们必须将这些操作声明
在finally中!

try{
     
}finally{
    
} 

举例1:确保资源关闭

package com.atguigu.keyword;

import java.util.InputMismatchException;
import java.util.Scanner;

public class TestFinally {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int a = input.nextInt();
            System.out.print("请输入第二个整数:");
            int b = input.nextInt();
            int result = a/b;
            System.out.println(a + "/" + b +"=" + result);
        } catch (InputMismatchException e) {
            System.out.println("数字格式不正确,请输入两个整数");
        }catch (ArithmeticException e){
            System.out.println("第二个整数不能为0");
        } finally {
            System.out.println("程序结束,释放资源");
            input.close();
        }
    }
    
    @Test
    public void test1(){
        FileInputStream fis = null;
        try{
            File file = new File("hello1.txt");
            fis = new FileInputStream(file);//FileNotFoundException
            int b = fis.read();//IOException
            while(b != -1){
                System.out.print((char)b);
                b = fis.read();//IOException
            }

        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try {
                if(fis != null)
                    fis.close();//IOException
            } catch (IOException e) {
                e.printStackTrace();
            }	
        }
    }
}

举例2:从try回来

public class FinallyTest1 {
    public static void main(String[] args) {
        int result = test("12");
        System.out.println(result);
    }

    public static int test(String str){
        try{
            Integer.parseInt(str);
            return 1;
        }catch(NumberFormatException e){
            return -1;
        }finally{
            System.out.println("test结束");
        }
    }
}

举例3:从catch回来

public class FinallyTest2 {
    public static void main(String[] args) {
        int result = test("a");
        System.out.println(result);
    }

    public static int test(String str) {
        try {
            Integer.parseInt(str);
            return 1;
        } catch (NumberFormatException e) {
            return -1;
        } finally {
            System.out.println("test结束");
        }
    }
}

举例4:从finally回来

public class FinallyTest3 {
    public static void main(String[] args) {
        int result = test("a");
        System.out.println(result);
    }

    public static int test(String str) {
        try {
            Integer.parseInt(str);
            return 1;
        } catch (NumberFormatException e) {
            return -1;
        } finally {
            System.out.println("test结束");
            return 0;
        }
    }
}

笔试题:

public class ExceptionTest {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result); //100
    }

    public static int test(){
        int i = 100;
        try {
            return i;
        } finally {
            i++;
        }
    }
}

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

4.2.4 练习

编写一个类ExceptionTest,在main方法中使用try、catch、finally,要求:

4.2.5 异常处理的体会

4.3 方式2:声明抛出异常类型(throws)

4.3.1 throws基本格式

声明异常格式:

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{   }	

在throws后面可以写多个异常类型,用逗号隔开。

举例:

public void readFile(String file)  throws FileNotFoundException,IOException {
    ...
    // 读文件的操作可能产生FileNotFoundException或IOException类型的异常
    FileInputStream fis = new FileInputStream(file);
    //...
}

4.3.2 throws 使用举例

是否真正处理了异常?

从编译是否能通过的角度看,看成是给出了异常万一要是出现时候的解决方案。此方案就是,继续向上抛出(throws)

但是,此throws的方式,仅是将可能出现的异常抛给了此方法的调用者。此调用者仍然需要考虑如何处理相关异常。

从这个角度来看,throws的方式不算是真正意义上处理了异常。

举例:针对于编译时异常

package com.atguigu.keyword;

public class TestThrowsCheckedException {
    public static void main(String[] args) {
        System.out.println("上课.....");
        try {
            afterClass();//换到这里处理异常
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("准备提前上课");
        }
        System.out.println("上课.....");
    }

    public static void afterClass() throws InterruptedException {
        for(int i=10; i>=1; i--){
            Thread.sleep(1000);//本来应该在这里处理异常
            System.out.println("距离上课还有:" + i + "分钟");
        }
    }
}

举例:针对于运行时异常:

throws后面也可以写运行时异常类型,只是运行时异常类型,写或不写对于编译器和程序执行来说都没有任何区别。如果写了,唯一的区别就是调用者调用该方法后,使用try...catch结构时,IDEA可以获得更多的信息,需要添加哪种catch分支。

package com.atguigu.keyword;

import java.util.InputMismatchException;
import java.util.Scanner;

public class TestThrowsRuntimeException {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int a = input.nextInt();
            System.out.print("请输入第二个整数:");
            int b = input.nextInt();
            int result = divide(a,b);
            System.out.println(a + "/" + b +"=" + result);
        } catch (ArithmeticException | InputMismatchException e) {
            e.printStackTrace();
        } finally {
            input.close();
        }
    }

    public static int divide(int a, int b)throws ArithmeticException{
        return a/b;
    }
}

4.3.3 方法重写中throws的要求

方法重写时,对于方法签名是有严格要求的。复习:

(1)方法名必须相同
(2)形参列表必须相同
(3)返回值类型
    - 基本数据类型和void:必须相同
    - 引用数据类型:<=
(4)权限修饰符:>=,而且要求父类被重写方法在子类中是可见的
(5)不能是static,final修饰的方法

此外,对于throws异常列表要求:

package com.atguigu.keyword;

import java.io.IOException;

class Father{
    public void method()throws Exception{
        System.out.println("Father.method");
    }
}
class Son extends Father{
    @Override
    public void method() throws IOException,ClassCastException {
        System.out.println("Son.method");
    }
}

方法的重写的要求:

子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。

import java.io.IOException;

public class OverrideTest {

    public static void main(String[] args) {
        // ...
    }
}

class Father {

    public void method1() throws IOException {
        // ...
    }
}

class Son extends Father {

    @Override
    public void method1() throws IOException {
        // ...
    }
    //抛出与父类相同
    //抛出父类更小范围
}

4.4 两种异常处理方式的选择

前提:对于异常,使用相应的处理方式。此时的异常,主要指的是编译时异常。

5. 手动抛出异常对象:throw

在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的异常对象

Java 中异常对象的生成有两种方式:

5.1 使用格式

throw new 异常类名(参数);

throw语句抛出的异常对象,和JVM自动创建和抛出的异常对象一样。

throw new String("want to throw");

5.2 使用注意点:

无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有被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;
    }
}

6. 自定义异常

6.1 为什么需要自定义异常类

Java中不同的异常类,分别表示着某一种具体的异常情况。那么在开发中总是有些异常情况是核心类库中没有定义好的,此时我们需要根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题,某员工已在团队中等。

6.2 如何自定义异常类

(1)要继承一个异常类型

		自定义一个编译时异常类型:自定义类继承`java.lang.Exception`。

		自定义一个运行时异常类型:自定义类继承`java.lang.RuntimeException`。

(2)建议大家提供至少两个构造器,一个是无参构造,一个是(String message)构造器。

(3)自定义异常需要提供serialVersionUID

6.3 注意点

  1. 自定义的异常只能通过throw抛出。
  2. 自定义异常最重要的是异常类的名字和message属性。当异常出现时,可以根据名字判断异常类型。比如:TeamException("成员已满,无法添加"); TeamException("该员工已是某团队成员");
  3. 自定义异常对象只能手动抛出。抛出后由try..catch处理,也可以甩锅throws给调用者处理。

6.4 举例

举例1:

class MyException extends Exception {
    static final long serialVersionUID = 23423423435L;
    private int idnumber;

    public MyException(String message, int id) {
        super(message);
        this.idnumber = id;
    }

    public int getId() {
        return idnumber;
    }
}
public class MyExpTest {
    public void regist(int num) throws MyException {
        if (num < 0)
            throw new MyException("人数为负值,不合理", 3);
        else
            System.out.println("登记人数" + num);
    }
    public void manager() {
        try {
            regist(100);
        } catch (MyException e) {
            System.out.print("登记失败,出错种类" + e.getId());
        }
        System.out.print("本次登记操作结束");
    }
    public static void main(String args[]) {
        MyExpTest t = new MyExpTest();
        t.manager();
    }
}

举例2:

package com.atguigu.define;
//自定义异常:
public class NotTriangleException extends Exception{
    static final long serialVersionUID = 13465653435L;

    public NotTriangleException() {
    }

    public NotTriangleException(String message) {
        super(message);
    }
}
package com.atguigu.define;

public class Triangle {
    private double a;
    private double b;
    private double c;

    public Triangle(double a, double b, double c) throws NotTriangleException {
        if(a<=0 || b<=0 || c<=0){
            throw new NotTriangleException("三角形的边长必须是正数");
        }
        if(a+b<=c || b+c<=a || a+c<=b){
            throw new NotTriangleException(a+"," + b +"," + c +"不能构造三角形,三角形任意两边之后必须大于第三边");
        }
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public double getA() {
        return a;
    }

    public void setA(double a) throws NotTriangleException{
        if(a<=0){
            throw new NotTriangleException("三角形的边长必须是正数");
        }
        if(a+b<=c || b+c<=a || a+c<=b){
            throw new NotTriangleException(a+"," + b +"," + c +"不能构造三角形,三角形任意两边之后必须大于第三边");
        }
        this.a = a;
    }

    public double getB() {
        return b;
    }

    public void setB(double b) throws NotTriangleException {
        if(b<=0){
            throw new NotTriangleException("三角形的边长必须是正数");
        }
        if(a+b<=c || b+c<=a || a+c<=b){
            throw new NotTriangleException(a+"," + b +"," + c +"不能构造三角形,三角形任意两边之后必须大于第三边");
        }
        this.b = b;
    }

    public double getC() {
        return c;
    }

    public void setC(double c) throws NotTriangleException {
        if(c<=0){
            throw new NotTriangleException("三角形的边长必须是正数");
        }
        if(a+b<=c || b+c<=a || a+c<=b){
            throw new NotTriangleException(a+"," + b +"," + c +"不能构造三角形,三角形任意两边之后必须大于第三边");
        }
        this.c = c;
    }

    @Override
    public String toString() {
        return "Triangle{" +
                "a=" + a +
                ", b=" + b +
                ", c=" + c +
                '}';
    }
}
package com.atguigu.define;

public class TestTriangle {
    public static void main(String[] args) {
        Triangle t = null;
        try {
            t = new Triangle(2,2,3);
            System.out.println("三角形创建成功:");
            System.out.println(t);
        } catch (NotTriangleException e) {
            System.err.println("三角形创建失败");
            e.printStackTrace();
        }

        try {
            if(t != null) {
                t.setA(1);
            }
            System.out.println("三角形边长修改成功");
        } catch (NotTriangleException e) {
            System.out.println("三角形边长修改失败");
            e.printStackTrace();
        }
    }
}

面试 throw 和 throws 的区别

throw new RuntimeException("非法输入");

public void method1() throws IOException {}

  1. 如何自定义异常类?
    • 继承于现有的异常体系。通常继承于 RuntimeExceptionException
    • 通常提供几个重载的构造器。
    • 提供一个全局常量,声明为:static final long serialVersionUID;

如何自定义异常类?

可使用自定义异常类: 具体的代码中,满足指定条件的情况下,需要手动使用“throw + 自定义异常类的对象”方式,将异常对象抛出。 如果自定义异常类是非运行时异常,则必须考虑如何处理此异常类的对象。(具体的:① try-catch-finally ② throws)

为什么需要自定义异常类?

我们其实更关心的是,通过异常的名称就能直接判断此异常出现的原因。因此,在实际开发场景中,当不满足我们指定的条件时,指明我们自己特有的异常类就显得很有必要。通过此异常类的名称,就能判断出具体出现的问题。

7. 练习

练习1:

public class ReturnExceptionDemo {
    static void methodA() {
        try {
            System.out.println("进入方法A");
            throw new RuntimeException("制造异常");
        }finally {
            System.out.println("用A方法的finally");
        }
    }

    static void methodB() {
        try {
            System.out.println("进入方法B");
            return;
        } finally {
            System.out.println("调用B方法的finally");
        }
    }
    public static void main(String[] args) {
        try {
            methodA();
           } catch (Exception e) {
          	System.out.println(e.getMessage());
        }
        methodB();
      }
}

练习2:

从键盘接收学生成绩,成绩必须在0~100之间。

自定义成绩无效异常。

编写方法接收成绩并返回该成绩,如果输入无效,则抛出自定义异常。

练习3:

编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。
对数据类型不一致(NumberFormatException)、缺少命令行参数(ArrayIndexOutOfBoundsException、
除0(ArithmeticException)及输入负数(EcDef 自定义的异常)进行异常处理。

提示:
(1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。

(2)在main()方法中使用异常处理语句进行异常处理。

(3)在程序中,自定义对应输入负数的异常类(EcDef)。

(4)运行时接受参数 java EcmDef 20 10 //args[0]=“20” args[1]=“10”

(5)Interger类的static方法parseInt(String s)将s转换成对应的int值。
如:int a=Interger.parseInt(“314”); //a=314;

8. 小结与小悟

8.1 小结:异常处理5个关键字

类比:上游排污,下游治污

8.2 感悟

小哲理:

世界上最遥远的距离,是我在if里你在else里,似乎一直相伴又永远分离;

世界上最痴心的等待,是我当case你是switch,或许永远都选不上自己;

世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。到那时,再来期待我们的finally