首先看下面的代码
提示出 Unhanded exception … 未处理的异常,产生这个异常的根源是:
下面是FileOutputStream的源代码:
这是FileOutputStream的一个构造器,从中我们看到了画红线的地方显式的throw了一个异常对象,这就是我们最开始出现错误提示的原因。
现在的问题是为什么在FileOutputStream的构造器中要抛出这个异常对象呢,原因其实就是程序不知道在这种(找不到文件)情况下该怎么办了,这个问题的解决办法在这个方法中无法解决,必须在调用这个方法的地方来解决,所以才要抛出这个异常,由此我们可以联想到自定义异常的使用场景:
当我们在定义一个方法时,如果出现了应该在调用该方法的地方才能解决的问题,就可以采用类似的处理手段。
异常分为Checked异常和 Runtime异常,上面的FileNotFoundException显然是Checked异常,否则也就失去了意义,因为我们希望的是在调用抛出异常的方法的地方来解决在这个方法中导致抛出异常的问题,可以Runtime异常是不需要程序显式处理的,也就失去了作用了。
关于这两种异常我的理解是:
checked异常之所以产生就是因为类似于上面分析的这种情况,它的根源是在代码中显示throw的,这所以要抛出就是因为有些问题是希望方法的调用者来解决,而不是在定义方法的地方解决。
Runtime异常则不是,它往往是由于代码本身的编写造成的,不是我们能预计到的,例如最典型的NullPointExption,它产生的原因是我们未预计到的,是由于编程本身造成的。所以Runtime异常并不需要我们显式处理,而Checked异常则需要我们显式处理。
我们通过打印的栈信息可以印证我们的观点:
Runtime异常的栈信息:
并没有给出抛出的根源
Checked异常的栈信息:
给出了抛出的根源
根据异常的产生原因我们还可以理解为什么重写时“两同两小一大”中其中一小的含义:子类中重写的方法抛出的异常要是父类方法抛出异常的子类或者本身。因为子类是对父类的具体实现,待解决的问题应该更具体才行,而不是更宽泛。
自定义异常的选取:
Checked异常:更加明确,方法的调用者能够明确知道要处理何种类型的异常
Runtime异常:更加灵活,无需显式处理,但是可能造成程序员的疏漏。
关于自定义异常时选择checked还是runtime,李刚疯狂java P359 有解释。
自定义异常的一般格式:
public class MyException extends Exception
{
public PersonNoNameException(){}
public PersonNoNameException(String message)
{
super(message);
}
}
下面根据这些观点写一个自定义异常的例子:
Person类:
public class Person
{
private String name;
public Person()
{
super();
}
public Person(String name)
{
super();
this.name = name;
}
}
自定义异常类(值得注意的是下面这个类是一个作用写死了的异常类,仅仅能用它来表示PersonNoName这一种异常,所以是用来一个final static的message)
public class PersonNoNameException extends Exception
{
private final static String message = "需要使用的Person对象的name属性为null";
public PersonNoNameException()
{
super(message);
}
}
Team类:
public class Team
{
public void put(Person p) throws PersonNoNameException
{
if(p.getName() == null)
{
//名字为null这种情况是put方法解决不了的,必须由、调用这个方法的地方来解决
throw new PersonNoNameException();
}
else
{
System.out.println("成功加入了" + p.getName());
}
}
}
Test类:
public static void main(String[] args)
{
Team team = new Team();
Person p = new Person();
try
{
//在调用put方法的地方必须给出解决办法,要么捕获在catch中解决,要么继续抛出,由下一次调用的地方解决
team.put(p);
}
catch (PersonNoNameException e)
{
System.out.println("让我来解决没有名字的问题");
e.printStackTrace();
}
}
最后需要说明的是这个例子仅仅是用来演示如何使用自定义异常,在编程中,像这样可以预见的错误的正确做法应该是编写处理错误的代码,而不是抛出异常,异常仅仅用来处理不可预计的情况。
考虑这么一种情况,要对已经存入系统的Person加入team中,这些person在可以预见的情况下是应该有名字的,这是就可以使用上面这种代码的处理方式了,其实还得具体问题具体分析。