深入学习java源码之PageException(String message)与PageException(Throwable cause)
长度为0的数组
长度为0的数组 int[] arr = new int[0],也称为空数组,虽然arr长度为0,但是依然是一个对象
null数组,int[] arr = null;arr是一个数组类型的空引用。
Effective Java第43条(返回零长度的数组或者集合,而不是null)清楚的说明了零长度或者集合的好处
为编写客户端程序的程序员可能会忘记写这种专门的代码来处理null返回值。这样的错误也许几年都不会被注意到,因为这样的方法通常返回一个或者多个对象。返回null而不是零长度的数组也会使返回数组或者集合的方法本身变得更加复杂,这一点虽然不是特别重要,但是也值得注意。
客户端必须有额外的代买来处理null的返回值。例如
Cheese[] cheeses = shop.getCheeses();
if(cheeses != null && Arrays.asList(cheeses).contains(Cheese.STILTON)){
System.out.println("Jolly good,just the thing.") ;
}
而不是下面这段代码:
if( Arrays.asList(cheeses).contains(Cheese.STILTON)){
System.out.println("Jolly good,just the thing.") ;
}
简而言之,返回类型为数组或集合的方法没理由返回null,而不是返回一个零长度的数组或者集合。
习惯用法中,零长度数组常量被传递给toArray方法,以指明所期望的返回类型.正常情况下,toArray方法分配了返回的数组,但是,如果集合是空的,它将使用零长度输入数组.
private final List cheeseInStock = …;
private static final Cheese[] EMPTY_CHEESE_ARRAY =new Cheese[0];
public Cheese[] getCheeses(){
return cheeseInStock.toArray(EMPTY_CHEESE_ARRAY);
}
集合值方法可以做成需要返回空集合时返回同一个不可变的空集合.Collections.emptySet(), Collections.emptyList(),Collections.emptyMap()方法正是所需要的
private final List cheeseInStock = …;
public List getCheesesList(){
if (cheeseInStock.isEmpty())
rerurnCollections.emptyList();
else
return new ArrayList(cheeseInStock);
}
异常简介
Java中的所有不正常类都继承于Throwable类。Throwable主要包括两个大类,一个是Error类,另一个是Exception类;
其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者;
Exception类,也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException)和检查异常(其他的一些异常)
.RuntimeException异常主要包括以下四种异常(其实还有很多其他异常,这里不一一列出):空指针异常、数组下标越界异常、类型转换异常、算术异常。RuntimeException异常会由java虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
检查异常,引起该异常的原因多种多样,比如说文件不存在、或者是连接错误等等。跟它的“兄弟”RuntimeException运行异常不同,该异常我们必须手动在代码里添加捕获语句来处理该异常,这也是我们学习java异常语句中主要处理的异常对象。
try-catch-finally语句
try{
//一些会抛出的异常
}catch(Exception e){
//第一个catch
//处理该异常的代码块
}catch(Exception e){
//第二个catch,可以有多个catch
//处理该异常的代码块
}finally{
//最终要执行的代码
}
当异常出现时,程序将终止执行,交由异常处理程序(抛出提醒或记录日志等),异常代码块外代码正常执行。 try会抛出很多种类型的异常,由多个catch块捕获多钟错误。
多重异常处理代码块顺序问题:先子类再父类(顺序不对编译器会提醒错误),finally语句块处理最终将要执行的代码。
finally语句块中加上return,编译器就会提示警告:
public int test2(){
int divider=10;
int result=100;
try{
while(divider>-1){
divider--;
result=result+100/divider;
}
return result;
}catch(Exception e){
e.printStackTrace();
System.out.println("异常抛出了!!");
return result=999;
}finally{
System.out.println("这是finally,哈哈哈!!");
System.out.println("result的值为:"+result);
return result;//编译器警告
}
}
finally块中的return语句可能会覆盖try块、catch块中的return语句;如果finally块中包含了return语句,即使前面的catch块重新抛出了异常,则调用该方法的语句也不会获得catch块重新抛出的异常,而是会得到finally块的返回值,并且不会捕获异常。
解决问题:面对上述情况,其实更合理的做法是,既不在try block内部中使用return语句,也不在finally内部使用 return语句,而应该在 finally 语句之后使用return来表示函数的结束和返回。如:
throw和throws关键字
throw ----将产生的异常抛出,是抛出异常的一个动作。
一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。如: 语法:throw (异常对象),如:
public static void main(String[] args) {
String s = "abc";
if(s.equals("abc")) {
throw new PageException(s);
} else {
System.out.println(s);
}
//function();
}
throws----声明将要抛出何种类型的异常(声明)。
当某个方法可能会抛出某种异常时用于throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理。如:
public static void function() throws NumberFormatException{
String s = "abc";
System.out.println(Double.parseDouble(s));
}
public static void main(String[] args) {
try {
function();
} catch (NumberFormatException e) {
System.err.println("非数据类型不能转换。");
//e.printStackTrace();
}
}
throw与throws的比较 1、throws出现在方法函数头;而throw出现在函数体。 2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。 3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
来看个例子:
throws e1,e2,e3只是告诉程序这个方法可能会抛出这些异常,方法的调用者可能要处理这些异常,而这些异常e1,e2,e3可能是该函数体产生的。 throw则是明确了这个地方要抛出这个异常。如:
void doA(int a) throws (Exception1,Exception2,Exception3){
try{
......
}catch(Exception1 e){
throw e;
}catch(Exception2 e){
System.out.println("出错了!");
}
if(a!=b)
throw new Exception3("自定义异常");
}
分析: 1.代码块中可能会产生3个异常,(Exception1,Exception2,Exception3)。 2.如果产生Exception1异常,则捕获之后再抛出,由该方法的调用者去处理。 3.如果产生Exception2异常,则该方法自己处理了(即System.out.println("出错了!");)。所以该方法就不会再向外抛出Exception2异常了,void doA() throws Exception1,Exception3 里面的Exception2也就不用写了。因为已经用try-catch语句捕获并处理了。 4.Exception3异常是该方法的某段逻辑出错,程序员自己做了处理,在该段逻辑错误的情况下抛出异常Exception3,则该方法的调用者也要处理此异常。这里用到了自定义异常,该异常下面会由解释。
使用throw和throws关键字需要注意以下几点:
1.throws的异常列表可以是抛出一条异常,也可以是抛出多条异常,每个类型的异常中间用逗号隔开
2.方法体中调用会抛出异常的方法或者是先抛出一个异常:用throw new Exception() throw写在方法体里,表示“抛出异常”这个动作。
3.如果某个方法调用了抛出异常的方法,那么必须添加try catch语句去尝试捕获这种异常, 或者添加声明,将异常抛出给更上一层的调用者进行处理
java中的异常链
异常需要封装,但是仅仅封装还是不够的,还需要传递异常。
异常链是一种面向对象编程技术,指将捕获的异常包装进一个新的异常中并重新抛出的异常处理方式。原异常被保存为新异常的一个属性(比如cause)。这样做的意义是一个方法应该抛出定义在相同的抽象层次上的异常,但不会丢弃更低层次的信息。
我可以这样理解异常链:
把捕获的异常包装成新的异常,在新异常里添加原始的异常,并将新异常抛出,它们就像是链式反应一样,一个导致(cause)另一个。这样在最后的顶层抛出的异常信息就包括了最底层的异常信息。
》场景
public class MyException extends Exception {
/**
* 错误编码
*/
private String errorCode;
public MyException(){}
/**
* 构造一个基本异常.
*
* @param message
* 信息描述
*/
public MyException(String message)
{
super(message);
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
}
public class Main {
public void test1() throws RuntimeException{
String[] sexs = {"男性","女性","中性"};
for(int i = 0; i < sexs.length; i++){
if("中性".equals(sexs[i])){
try {
throw new MyException("不存在中性的人!");
} catch (MyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
RuntimeException rte=new RuntimeException(e);//包装成RuntimeException异常
//rte.initCause(e);
throw rte;//抛出包装后的新的异常
}
}else{
System.out.println(sexs[i]);
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Main m =new Main();
try{
m.test1();
}catch (Exception e){
e.printStackTrace();
e.getCause();//获得原始异常
}
}
}
我们可以看到控制台先是输出了原始异常,这是由e.getCause()输出的;然后输出了e.printStackTrace(),在这里可以看到Caused by:原始异常和e.getCause()输出的一致。这样就是形成一个异常链。initCause()的作用是包装原始的异常,当想要知道底层发生了什么异常的时候调用getCause()就能获得原始异常。
》建议
异常需要封装和传递,我们在进行系统开发的时候,不要“吞噬”异常,也不要“赤裸裸”的抛出异常,封装后在抛出,或者通过异常链传递,可以达到系统更健壮、友好的目的。、
良好的编码习惯:
1、处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
2、在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
3、对于不确定的代码,也可以加上try-catch,处理潜在的异常
4、尽量去处理异常,切记只是简单的调用printStackTrace()去打印
5、具体如何处理异常,要根据不同的业务需求和异常类型去决定
6、尽量添加finally语句块去释放占用的资源
Throwable类
Throwable类是整个异常体系类的父级类,当然最终追根溯源到底的父类当然要归于Object类。Throwable类实现了Serializable接口,表示Throwable可以进行序列化,继承自Object类,他的子类主要是Error和Exception类还有一个StackRecorder类(不是很常见)。
序列化问题 序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
三种情况:
1. 将对象存储再硬盘上。
2. 将对象通过网络传输。
3. 通过RMI远程调用等方式传输对象的时候。
在这三种情况下,是需要进行序列化然后传输的。
在Throwable类中使用输出流来进行输出,并把其对象作为输出流对象,这就需要必须实现序列化接口,使得其可以进行序列化,才能作为输出流中的对象进行输出。
private synchronized void writeObject(ObjectOutputStream s)
throws IOException {
// Ensure that the stackTrace field is initialized to a
// non-null value, if appropriate. As of JDK 7, a null stack
// trace field is a valid value indicating the stack trace
// should not be set.
getOurStackTrace();
StackTraceElement[] oldStackTrace = stackTrace;
try {
if (stackTrace == null)
stackTrace = SentinelHolder.STACK_TRACE_SENTINEL;
s.defaultWriteObject();
} finally {
stackTrace = oldStackTrace;
}
}
继承Object类 1. 在编译时,如果类没有继承的父类的话,会自动为其加入继承父类Object的相关的类的编译信息,这样在后面虚拟器进行解释执行的时候,按照存在父类进行处理就可以了。
2. 在虚拟机进行执行的时候,如果仍然存在没有父类的类,仍然会默认其父类为Object。
第一种情况属于再编译器进行处理,第二种情况属于在虚拟机上面做适当的处理。
子类Error和Exception Error主要是用于表示java和虚拟机内部的异常信息,而Exception异常则是由于程序中可能存在各种的问题,是需要使用者去注意和捕获的异常。
从异常类的设计中体会到,设计者的抽象思维与设计水平令人叹服,通过一个类去抽象出所有异常中通用的方法与表示形式以及其表达的实体结构,而且通过继承的方式对异常这个领域做一个水平划分,将其切分为Error和Exception两种平行的异常类型,然后,这两者将再次作为各自类型的异常的父类,因为每一种异常同样是存在不同的分类,再次创建一系列的类去继承上面的两种异常派生出新的异常类型划分。这样不断的继承下去,逐步的细化到每一种具体的异常类型,形成一个丰富的异常类族。
从扩展性上而言,由于Throwable实现的是异常类中通用的部分,那么,如果再有特殊的异常分类的话,可以通过继承Throwable的方式去扩展该异常体系,当然,我们最常用的可能不会涉及到直接继承Throwable。
默认是空的StackTrace的节点数组初始化为空的stack,getOurStackTrace()方法实现的主要是获取当前节点异常的信息,获取栈上面的异常信息,遍历每一个异常信息,赋值给stackTrace进行保存。
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
private synchronized StackTraceElement[] getOurStackTrace() {
if (stackTrace == UNASSIGNED_STACK ||
(stackTrace == null && backtrace != null) /* Out of protocol state */) {
int depth = getStackTraceDepth();
stackTrace = new StackTraceElement[depth];
for (int i=0; i < depth; i++)
stackTrace[i] = getStackTraceElement(i);
} else if (stackTrace == null) {
return UNASSIGNED_STACK;
}
return stackTrace;
}
获取到栈异常信息以后,输出异常信息,对该数组进行遍历,输出异常的位置。
private void printStackTrace(PrintStreamOrWriter s) {
Set dejaVu =
Collections.newSetFromMap(new IdentityHashMap());
dejaVu.add(this);
synchronized (s.lock()) {
s.println(this);
StackTraceElement[] trace = getOurStackTrace();
for (StackTraceElement traceElement : trace)
s.println("\tat " + traceElement);
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
Throwable ourCause = getCause();
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
}
}
其中,getSuppressed方法返回了被抑制(可能是try住的)的所有的异常信息,然后getCause()方法返回的是异常的原因,其中cause是一个Throwable属性,初始化为其本身this, 如果这个throwable中cause要么是抛出异常的类,要么是null,如果是Throwable类本身的话,那么只能说明Throwable类没有初始化完毕。 这个cause属性使得java可以通过链式的结构来组织异常信息,通过cause指向其下一个异常的抛出类。依次构成链状的结构。
通过递归的形式遍历并输出的过程。
private void printEnclosedStackTrace(PrintStreamOrWriter s,
StackTraceElement[] enclosingTrace,
String caption,
String prefix,
Set dejaVu) {
assert Thread.holdsLock(s.lock());
if (dejaVu.contains(this)) {
s.println("\t[CIRCULAR REFERENCE:" + this + "]");
} else {
dejaVu.add(this);
// Compute number of frames in common between this and enclosing trace
StackTraceElement[] trace = getOurStackTrace();
int m = trace.length - 1;
int n = enclosingTrace.length - 1;
while (m >= 0 && n >=0 && trace[m].equals(enclosingTrace[n])) {
m--; n--;
}
int framesInCommon = trace.length - 1 - m;
// Print our stack trace
s.println(prefix + caption + this);
for (int i = 0; i = 0 ?
"(" + fileName + ":" + lineNumber + ")" :
(fileName != null ? "("+fileName+")" : "(Unknown Source)")));
}
public boolean equals(Object obj) {
if (obj==this)
return true;
if (!(obj instanceof StackTraceElement))
return false;
StackTraceElement e = (StackTraceElement)obj;
return e.declaringClass.equals(declaringClass) &&
e.lineNumber == lineNumber &&
Objects.equals(methodName, e.methodName) &&
Objects.equals(fileName, e.fileName);
}
/**
* Returns a hash code value for this stack trace element.
*/
public int hashCode() {
int result = 31*declaringClass.hashCode() + methodName.hashCode();
result = 31*result + Objects.hashCode(fileName);
result = 31*result + lineNumber;
return result;
}
private static final long serialVersionUID = 6992337162326171013L;
}
Modifier and TypeMethod and Description
void
addSuppressed(Throwable exception)
将指定的异常附加到为了传递此异常而被抑制的异常。
Throwable
fillInStackTrace()
填写执行堆栈跟踪。
Throwable
getCause()
如果原因不存在或未知,则返回此throwable的原因或 null
。
String
getLocalizedMessage()
创建此可抛出的本地化描述。
String
getMessage()
返回此throwable的详细消息字符串。
StackTraceElement[]
getStackTrace()
提供对 printStackTrace()
打印的堆栈跟踪信息的 编程访问 。
Throwable[]
getSuppressed()
返回一个包含所有被抑制的异常的数组,通常由 try
-with-resources语句来传递这个异常。
Throwable
initCause(Throwable cause)
将此throwable的 原因初始化为指定值。
void
printStackTrace()
将此throwable和其追溯打印到标准错误流。
void
printStackTrace(PrintStream s)
将此throwable和其追溯打印到指定的打印流。
void
printStackTrace(PrintWriter s)
将此throwable和其追溯打印到指定的打印作者。
void
setStackTrace(StackTraceElement[] stackTrace)
设置将被返回的堆栈微量元素 getStackTrace()
和由印刷 printStackTrace()
和相关方法。
String
toString()
返回此可抛出的简短描述。
package java.lang;
import java.io.*;
import java.util.*;
public class Throwable implements Serializable {
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -3042686055658047285L;
private transient Object backtrace;
private String detailMessage;
private static class SentinelHolder {
public static final StackTraceElement STACK_TRACE_ELEMENT_SENTINEL =
new StackTraceElement("", "", null, Integer.MIN_VALUE);
public static final StackTraceElement[] STACK_TRACE_SENTINEL =
new StackTraceElement[] {STACK_TRACE_ELEMENT_SENTINEL};
}
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
private Throwable cause = this;
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
// Setting this static field introduces an acceptable
// initialization dependency on a few java.util classes.
private static final List SUPPRESSED_SENTINEL =
Collections.unmodifiableList(new ArrayList(0));
private List suppressedExceptions = SUPPRESSED_SENTINEL;
/** Message for trying to suppress a null exception. */
private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception.";
/** Message for trying to suppress oneself. */
private static final String SELF_SUPPRESSION_MESSAGE = "Self-suppression not permitted";
/** Caption for labeling causative exception stack traces */
private static final String CAUSE_CAPTION = "Caused by: ";
/** Caption for labeling suppressed exception stack traces */
private static final String SUPPRESSED_CAPTION = "Suppressed: ";
public Throwable() {
fillInStackTrace();
}
public Throwable(String message) {
fillInStackTrace();
detailMessage = message;
}
public Throwable(String message, Throwable cause) {
fillInStackTrace();
detailMessage = message;
this.cause = cause;
}
public Throwable(Throwable cause) {
fillInStackTrace();
detailMessage = (cause==null ? null : cause.toString());
this.cause = cause;
}
protected Throwable(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
if (writableStackTrace) {
fillInStackTrace();
} else {
stackTrace = null;
}
detailMessage = message;
this.cause = cause;
if (!enableSuppression)
suppressedExceptions = null;
}
public String getMessage() {
return detailMessage;
}
public String getLocalizedMessage() {
return getMessage();
}
public synchronized Throwable getCause() {
return (cause==this ? null : cause);
}
public synchronized Throwable initCause(Throwable cause) {
if (this.cause != this)
throw new IllegalStateException("Can't overwrite cause with " +
Objects.toString(cause, "a null"), this);
if (cause == this)
throw new IllegalArgumentException("Self-causation not permitted", this);
this.cause = cause;
return this;
}
public String toString() {
String s = getClass().getName();
String message = getLocalizedMessage();
return (message != null) ? (s + ": " + message) : s;
}
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace(PrintStream s) {
printStackTrace(new WrappedPrintStream(s));
}
private void printStackTrace(PrintStreamOrWriter s) {
// Guard against malicious overrides of Throwable.equals by
// using a Set with identity equality semantics.
Set dejaVu =
Collections.newSetFromMap(new IdentityHashMap());
dejaVu.add(this);
synchronized (s.lock()) {
// Print our stack trace
s.println(this);
StackTraceElement[] trace = getOurStackTrace();
for (StackTraceElement traceElement : trace)
s.println("\tat " + traceElement);
// Print suppressed exceptions, if any
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
// Print cause, if any
Throwable ourCause = getCause();
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
}
}
private void printEnclosedStackTrace(PrintStreamOrWriter s,
StackTraceElement[] enclosingTrace,
String caption,
String prefix,
Set dejaVu) {
assert Thread.holdsLock(s.lock());
if (dejaVu.contains(this)) {
s.println("\t[CIRCULAR REFERENCE:" + this + "]");
} else {
dejaVu.add(this);
// Compute number of frames in common between this and enclosing trace
StackTraceElement[] trace = getOurStackTrace();
int m = trace.length - 1;
int n = enclosingTrace.length - 1;
while (m >= 0 && n >=0 && trace[m].equals(enclosingTrace[n])) {
m--; n--;
}
int framesInCommon = trace.length - 1 - m;
// Print our stack trace
s.println(prefix + caption + this);
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?