——◆编程心得[4]:在构造函数中抛出异常——
根据对构造子的理解,是可以知道构造函数本身属于一个特殊的数据结构,是不存在返回值的,那么如果在构造函数里面来获得异常信息就成为了很困难的事情。面对这样的情况如何进行相关处理呢?
【*:两年前我参与开发一个基于J2EE的CRM客户端的时候就遇到了这样一个问题:因为系统里面具有一个界面管理器,而在管理器最初需要针对初始化的内容进行监控,而且该管理器必须将这个过程的一些错误记录下来,而最开始使用普通的办法都没有做到,而且使用日志也不管用,主要原因是因为日志是正常运行的。所以针对这一点深有感触。】
有些技术可以实现所需要的这种情况的需求:
双阶段构造(two-stage construction):
将可能产生错误的代码移除到构造函数里面,这些函数能够返回一定的错误代码或者错误相关信息。而这样做就使得的调用顺序有了典型的不同:用户必须先调用构造函数、然后调用那些可能产生错误信息的函数、最后再检查返回代码以及捕捉相关异常。先提供一段代码,然后再进行详细的分析:
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.FileNotFoundException;
/**
*构造函数双阶段构造的概念说明
**/
public class FileScanner
{
public FileScanner(String filename) throws FileNotFoundException,IOException
{
FileReader reader = new FileReader(filename);
BufferedReader br = new BufferedReader(reader);
String str = br.readLine();
//……
}
public void scanFile(){}
public static void main(String args[])
{
FileScanner scanner = null;
try
{
scanner = new FileScanner("C:/text.txt");
}
catch(FileNotFoundException ex)
{
ex.printStackTrace();
}
catch(IOException ex)
{
ex.printStackTrace();
}
scanner.scanFile();
}
}
【*:仔细思考上边这段代码:FileScanner对象接受到了一个字符串参数,这个参数代表了一个文件路径,然后尝试着打开这个文件。若该文件不存在,构造函数就会引发一个FileNotFoundException异常,如果读取过程再出现问题,就会直接抛IOException。仔细考虑这段调用代码,如果构造失败,就会使得引用scanner为null,这种情况下,表示既然没有被构建,如果再访问该对象就会直接抛出NullPointerException。】
从这点上讲,即使构造函数不是一般函数,仍然可以使用异常处理或者throws语句,这样的方式来处理构造失败的情况是比较高效合理的一种方式。如果结合使用JVM里面深入引用,那么不仅仅可以在一个对象创建过程监控该对象,而且可以在对象毁灭一直到垃圾回收的过程里面监控整个对象的所有状态,在开发一些严格的程序的时候就会遇到这样各种各样的情况。
——◆编程心得[5]:深入理解throws——
throws是一种语言特性,用来列出[可以从某个函数传到外界]的所有可能的异常。JVM编译器会强迫必须在函数中捕捉这些列出的异常,否则就在该函数的throw子句中声明。如果某个项目已经完成了,但是有时候需要在里面为某些不良的设计添加一些新的可抛出的异常,这种情况如何来完成呢。当然和前边讲的异常处理一样的:
[1]使用try-catch将这个异常捕捉
[2]使用throws或者throw将这个异常抛出
【*:注意这里说的是在一个已经完善的系统里面,所以根据参考可以知道,这种worker method类型的低级系统或者不良设计里面,如果要使用try-catch的方式来处理,可能行不通,因为它没有办法独立处理这个异常,唯一的办法就是使用第二种方式,那么就需要理解throws的一些特性以及相关缺陷。要知道在一个已经完善的系统里面添加throw或者throws是一个大工作量,如果系统已经完成了过后才来考虑异常抛出,是一个很严重的问题。而且在添加过程不小心会影响系统的整体开销或者资源调配,假设在设计之初忽略了一个来自数据层的异常,那么添加和修改的时候不是进行的处理而是throws,注意这里说的是使用try和catch不能处理的时候,那么不仅仅会在业务逻辑层遇到该处理的问题,还会在其他层次遇到这种一直往外抛出的问题,那么在系统里面加入该异常的时候,难免会很有影响。】
其实最好的方式应该是在设计之初将这些可能的关键性因素考虑到,这样就使得throws的使用更加合理,记住一点:
在一个系统开发设计之初最好能够设计一个良好的异常体系处理结构,而这部分设计可能往往会成为最容易忽略的问题,因为最初没有办法考虑到一些系统本身的异常问题。设计异常本身对系统而言就是一个挑战,这种方式和TDD的设计方式是一个出发点,因为要在设计之初考虑到系统可能会出现和遇到的问题,所以这样的设计经常是出于积累和思考。
先考虑这样一段代码:
class ExceptionOne extends Exception{}
class ExceptionTwo extends ExceptionOne{}
class ExceptionThree extends ExceptionTwo{}
public class ExceptionThrowTester
{
public void testException(int i) throws ExceptionOne
{
if( i == 1 )
throw new ExceptionOne();
if( i == 2 )
throw new ExceptionTwo();
if( i == 3 )
throw new ExceptionThree();
}
}
注意上边这段代码,编译是合理的,但是这样的方法有一定的弱点。实际上这个地方抛出的所有异常都是属于ExceptionOne类型的,这里能够通过编译是因为符合throws语句的语法要求,这段代码可以这样理解:函数可能产生ExceptionOne的异常。可以稍稍修改一下testException函数的定义:
public void testException(int i) throws ExceptionOne,ExceptionTwo,ExceptionThree
但是这样改写过后,抛出的异常还是会这样理解:函数还是可能会产生一个隶属于ExceptionOne的异常,因为这里这三个自定义异常是存在继承关系的。如果想要知道ExceptionOne的派生异常类,就得查看源代码才能够办到,假设这个代码设计存在于已经编译好的jar库里面,其实这本身而言是不切实际的。这种做法使得真正在调用过程里面会出现下边的这种写法,这种写法在编程的时候很常见:
public class ExpThrowTesterTwo
{
public static void main(String args[])
{
try
{
//正常的逻辑代码
}
catch(ExceptionOne ex)
{
//异常的处理代码
}
}
}
上边这种写法是不会出现任何编译错误的,但是考虑一点,会使得养成了一个不良的习惯:正确地说,应该使得抛出的异常精确到某种类型,而不应该是它的父类型,这样真正在开发过程也方便进行调试工作。也就是说可以避免[试图精确推断这个函数可能产生哪种异常]的困境。再看一段代码:
import java.io.File;
import java.io.FileReader;
import java.io.FileNotFoundException;
public class ExpThrowTesterThree
{
public static void main(String args[])
{
try
{
File file = new File("C:/readme.txt");
FileReader reader = new FileReader(file);
}
catch(Exception ex)
{
//对应的异常处理方式
}
}
}
按照理论来讲这段代码没有任何问题,但是会有一点,比如try块里面出现了其他异常,那么只能从一个异常的堆栈信息来确定程序里面到底遇到了什么问题,其实最好的方法就是把其中的异常处理分开操作。比如会出现FileNotFoundException的地方使用对应的异常,而会出现NumberFormatException的地方写另外的catch语句来进行分类匹配,这样程序里面有什么异常就一目了然了。
总之:使用throws的时候,最好能够明确抛出某种派生类型的异常,而且使用catch捕捉的时候也尽量使对应的子类异常。
——◆编程心得[6]:避免资源泄漏,finally用法——
在JVM异常处理机制里面,比较出色的一点就是finally方法,finally方法里面的代码块总是会得以执行的,而且不论是否有异常,都能够执行,这在维护对象内部状态和资源清理上是很实用的:
对比两段代码:
import java.net.*;
import java.io.*;
/**
*不使用finally写法【*:概念说明代码里面可能存在很多不规范的内容】
**/
public class FinallyTester{
public static void main(String args[])
{
ServerSocket ss = new ServerSocket(0);
try{
Socket socket = ss.accept();
// 其他操作
}
catch(Exception ex)
{
ss.close();
throw ex;
}
ss.close();
}
}
以下是使用finally的代码:
import java.net.*;
import java.io.*;
/**
*使用finally写法
**/
public class FinallyTester{
public static void main(String args[])
{
ServerSocket ss = new ServerSocket(0);
try{
Socket socket = ss.accept();
// 其他操作
}
finally
{
ss.close();
}
}
}
【*:仔细思考一下上边两段代码,当然第二段更加规范一点。因为有可能程序员会忘记了close()的操作,这样就有可能造成忘记关闭而出现资源泄漏的情况,所以确保资源能够正常回收,最好的办法就是使用finally块防止资源泄漏,和try/catch块匹配使用,finally代码块里面的内容存在于异常处理机制里面作为统一的出口,就是任何情况都会执行的,而且会在异常处理机制里面确保退出之前执行。】
所以使用finally进行资源回收是最好的方式,例如:JDBC连接关闭、网络连接关闭、对象回收等
后续链接:
http://blog.csdn.net/silentbalanceyh/archive/2009/09/18/4564884.aspx
分享到:
相关推荐
总结来说,Java异常处理机制提供了一种结构化的方法来处理程序运行时的错误,通过try-catch-finally结构捕获和处理异常,同时,利用断言进行内部逻辑验证,以及日志记录来跟踪程序行为。这样的机制增强了代码的健壮...
2. **Assert(断言)**:在Java编程中,`assert`关键字用于在测试阶段检查代码假设是否正确,它是进行单元测试和调试的强大工具。在本项目中,通过扩展Spring Boot的断言功能,我们可以创建自定义的断言方法,以适应...
Java异常处理是编程中至关重要的一个环节,它确保了程序在遇到错误情况时能够优雅地处理问题而不是突然崩溃。在Java中,异常分为两大类:错误(Error)和违例(Exception)。错误通常是JVM系统内部的问题,如内存...
Java提供了丰富的异常体系,包括标准的Java异常类和自定义异常。 1. **算术异常类:ArithmeticException** - 当程序执行了非法的数学运算,如除以零,就会抛出此异常。 2. **空指针异常类:NullPointerException**...
Java异常处理是编程中至关重要的一个环节,尤其是在Android开发中,理解并熟练运用异常处理能够有效地定位和解决程序运行中的问题。以下将详细介绍在Java和Android开发中常见的一些异常类型及其处理策略。 1. **空...
Java异常处理是编程中至关重要的一个环节,它帮助开发者识别并修复程序运行时可能出现的问题。在Java中,异常是通过类来表示的,这些类都继承自`java.lang.Throwable`,并分为两种主要类型:Error和Exception。Error...
- **异常与测试**:在单元测试中,通过断言预期的异常被正确抛出来验证代码的健壮性,是一种常见的做法。这有助于确保程序在遇到错误情况时能够按照预期的方式响应。 通过上述讨论,可以看出自定义异常在 Java 开发...
手册会指导如何正确使用Java的异常处理机制,以及如何设置有效的日志记录,以便在问题发生时进行有效的调试和故障排查。 3. **单元测试**:单元测试是确保代码质量的重要手段。手册会介绍JUnit等测试框架的使用,...
包括Java基本的程序结构、对象与类、继承、接口与内部类、图形程序设计、事件处理、Swing用户界面组件、部署应用程序和Applet、异常日志断言和调试、叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Java...
包括Java基本的程序结构、对象与类、继承、接口与内部类、图形程序设计、事件处理、Swing用户界面组件、部署应用程序和Applet、异常日志断言和调试、叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Java...
第1章 Java 程序设计概述 1.1 Java 程序设计平台 1.2 Java 白皮书的关键术语 1.2.1 简单性 1.2.2 面向对象 1.2.3 网络技能 ...第11章 异常、日志、断言和调试 第12章 泛型程序设计 第13章 集合 第14章 多线程
### Java异常总结与详解 #### 引言 在Java编程中,异常处理是软件开发过程中不可或缺的一部分。良好的异常处理能够帮助我们及时发现并解决问题,从而提高程序的稳定性和健壮性。本文旨在全面总结Java中常见的异常...
包括Java基本的程序结构、对象与类、继承、接口与内部类、图形程序设计、事件处理、Swing用户界面组件、部署应用程序和Applet、异常日志断言和调试、叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Java...
这份“java开发规范.pdf”文档详细涵盖了多个关键领域,包括编程规约、异常日志处理、单元测试、安全性考量、MySQL数据库使用、工程结构规划以及设计规约。以下将对这些知识点进行深入的探讨。 一、编程规约 编程...
这份"java后端开发规范word文档"包含了多个方面的内容,包括但不限于编程风格、命名规则、异常处理、并发控制、数据库操作、单元测试、日志记录以及代码组织结构等。 1. **编程风格**:编程风格是代码可读性的基础...
第1章 Java 程序设计概述 1.1 Java 程序设计平台 1.2 Java 白皮书的关键术语 1.2.1 简单性 1.2.2 面向对象 1.2.3 网络技能 ...第11章 异常、日志、断言和调试 第12章 泛型程序设计 第13章 集合 第14章 多线程
异常处理、断言和日志记录章节提供了错误处理和调试的方法。在泛型编程章节,读者将学习如何使用泛型来增强代码的类型安全性和重用性。集合框架的介绍涵盖了ArrayList、LinkedList、Set、Map等容器的使用,以及迭代...
2. **异常处理**:Java提供了丰富的异常处理机制,如try-catch-finally语句块,使得程序能够优雅地处理错误情况。测试项目中,异常处理的测试是非常重要的一环,确保代码在遇到预期之外的问题时能妥善应对。 3. **...