处理错误
程序运行时出现错误使得某些操作被终止
程序需要返回到一种安全状态,并能够让用户执行一些其他的命令
运行用户保存所有操作的结果,并以合适的方式终止程序
异常处理的任务就是将控制权从出现错误的地方转移到能够处理这种情况的错误处理器
异常分类
所有的异常都是由Throwable继承而来的,Throwable下一层又分为Error和Exception
Error类层次结构描述了java运行时系统的内部错误和资源耗尽的错误,应用程序不用抛出这种类型对象
Exception是更加需要关注的,这个层次结构又分为:
RuntimeException(程序错误导致的异常)
错误的类型转换
数组越界
访问null指针
其他异常(程序本身没有问题,但是可能由于像I/O错误这类问题导致的异常)(checked)
试图在文件尾部后面读取数据
试图打开不存在的文件
试图根据一个字符串查找Class对象,但实际上这个字符串表示的类不存在
Error和RuntimeException类的所有异常都是不受查异常(unchecked,检查不出来,运行时才能检查到),其他异常都是受查异常(checked,编译时程序可以检查到)
声明受查异常(throws)
一个方法不仅要告诉编译器需要返回什么值,还要告诉编译器有可能发生什么错误,例如:读取的文件不存在,内容为空等等
在方法首部声明时声明所有可能会抛出的异常:public FileInputStream(String name) throws FileNotFoundException
常见使用情形:
调用一个抛出受查异常的方法
程序运行过程中发生错误,并利用throw语句抛出一个受查异常
程序出现错误
Java虚拟机和运行时库出现的内部错误(不需要声明,这种非受查异常不可控)
注:如果子类中覆盖了父类中的方法,子类方法声明中的受查异常不能比父类中方法声明的异常更通用(抛出更加特定的异常,也可以不抛出异常)
但是如果父类中方法没有抛出异常,子类也不可以抛出异常
如何抛出异常(throw)
找到一个合适的异常类
创建该异常类的一个对象
抛出这个对象
例如:
读取文件时读取一半突然结束了(EOFException)
throw new EOFException();
注:Java中只能抛出Throwable子类的对象,c++中可以抛出任何类型的值
创建异常类
定义一个继承自Exception或者Exception子类的异常类
自定义的异常类需要两个构造器:默认的构造器,带有详细描述信息的构造器
public class FileFormatException extends IOException { public FileFormatException() { } public FileFormatException(String info) { super(info); } } public class Test { public String readData() throws FileFormatException{ return ""; } }
捕获异常
异常发生时如果没被捕获,程序就会终止执行,并在控制台打印出异常信息(异常类型,堆栈信息)
捕获异常,需要设置try/catch语句块
try { //要捕获异常的代码 } catch (Exception e) { e.printStackTrace();//对捕获到的异常做处理 }
如果try语句块中的任何一句代码抛出了一个在catch中说明的异常:
程序将跳出try语句块的其他代码
程序将执行catch子句中的处理代码
如果try语句中没有抛出任何异常,那么程序将跳过catch子句
通常,应该将知道如何处理的异常捕获处理,不知道如何处理的异常继续使用throws传递给调用者
如果编写一个覆盖父类的方法,并且父类中的方法没有声明异常,那么这个方法就必须捕获代码中的所有受查异常(不允许子类的throws异常超出父类方法的异常类范围)
一个try语句块可以捕获多个异常类型,并使用catch子句对不同的异常做不同的处理
try { //Java code } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
同一个catch子句还可以捕获多个不同类型的异常(异常之间不存在父子类关系)
try { //Java code } catch (FileNotFoundException |UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); }
在catch子句中抛出异常(throw)
改变异常的类型try { //Java code } catch (FileNotFoundException |UnknownHostException e) { // TODO Auto-generated catch block throw new FileFormatException(""+e.getMessage()); }
可以将原始异常设置为新异常的原因try { //Java code } catch (SQLException e) { Throwable se = new ServletException("database error!"); se.initCause(e); throw se; }
finally子句不管异常是否被捕获,finally子句中的代码都被执行try { //1 in.read();//发生异常 //2 } catch (IOException e) { //3 e.printStackTrace();//处理异常 //4 } finally { //5 in.close(); } //6
如果代码没有抛出异常,先执行try语句块中的全部代码,再执行finally语句块,即1,2,5,6 抛出一个在catch中捕获的异常,跳过try块中遇到异常之后的全部代码,执行catch中的异常处理代码,如果catch子句中没有抛出异常,程序将执行try后的第一句代码,即1,3,4,5,6;如果catch中抛出异常,异常将会抛给方法的调用者,即执行1,3,5 try出抛出一个异常,但是异常没被catch捕获(上例中如果异常不是IOException,就不会被catch捕获),执行try块发生异常之前的代码,然后执行finally块中的代码,再将该异常抛给调用者,即执行1,5处语句
try语句可以只有finally块而没有catch块;无论try块中是否遇到异常,finally总会被执行
finally语句如果有return时,会覆盖原始返回值
int n = 2 ; try { return n*n;//1 } finally { return 0;//2 }
先执行1出代码返回值时4,但是在方法真正返回之前还要执行finally子句,返回值0将会覆盖原始的返回值4,最终返回值是0
带资源的try语句(避免close资源时出现异常,覆盖使用资源的异常返回给调用者)
try (Scanner in = new Scanner(new FileInputStream("f:\\test.txt"),"UTF-8"); PrintWriter out = new PrintWriter("out.txt") ){ while (in.hasNext()) { String str = in.next().toUpperCase(); out.println(str); System.out.println(str); } }
这个块正常退出或者遇到异常时,都会自动调用in.close()和out.close()方法,就像使用了finally块一样
注:只会默认关闭try()中出现的资源
堆栈轨迹(stack trace):方法调用过程的列表,包含程序执行过程中方法调用的特定位置
console:public static void main(String[] args) throws FileNotFoundException { Throwable t = new Throwable(); StringWriter out = new StringWriter(); t.printStackTrace(new PrintWriter(out)); System.out.println(out.toString()); StackTraceElement[] frames = t.getStackTrace(); for (StackTraceElement stackTraceElement : frames) { System.out.println(stackTraceElement); } }
java.lang.Throwable at com.qf.test.Test.main(Test.java:9) com.qf.test.Test.main(Test.java:9)
n的阶乘的堆栈信息:public class Test { public static int factorial(int n) { System.out.println("factorial("+n+"):"); Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for (StackTraceElement f : frames) { System.out.println(f); } int r; if(n<=1) r = 1; else r = n*factorial(n-1); System.out.println("return:"+r); return r; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("Enter n:"); int n = sc.nextInt(); factorial(n); sc.close(); } }
console:
Enter n: 3 factorial(3): com.qf.test.Test.factorial(Test.java:9) com.qf.test.Test.main(Test.java:29) factorial(2): com.qf.test.Test.factorial(Test.java:9) com.qf.test.Test.factorial(Test.java:19) com.qf.test.Test.main(Test.java:29) factorial(1): com.qf.test.Test.factorial(Test.java:9) com.qf.test.Test.factorial(Test.java:19) com.qf.test.Test.factorial(Test.java:19) com.qf.test.Test.main(Test.java:29) return:1 return:2 return:6
使用异常机制
执行简单的测试所用的时间要比捕获异常用时少的多,所以只在异常情况下使用异常机制
不要过分细化异常,讲一个完整的任务代码放在一个try块中,任一操作出现问题时,整个任务都可以取消
注意异常的层次结构,寻找更加合适的异常子类或者自己创建异常类
检测异常时,发现就捕获,传递异常到高层次的方法,高层次方法通知用户发生了错误,即早抛出,晚捕获
断言assert
假设确信某个属性符合要求,并且执行代码需要依赖这个属性
assert的两种形式
assert 条件;
assert 条件:表达式;
这两种形式都会对条件进行检测,如果结果是false,就抛出一个AssertionError异常;第二种形式,表达式会被传入AssertionError的构造器,并转换成一个消息字符串,但是AssertionError对象本身不存储表达式的值
断言的启动和禁用
启动:-enableassertions或者-ea
可以在运行程序时启动:java -ea MyApp
启动或者禁用断言是类加载器的功能,不需要重新编译程序;断言被禁用时,类加载器将跳过断言代码,不会降低程序运行速度
可以对某个类或者某个包使用断言:java -ea:MyClass -ea:com.mycompany.mylib... MyApp
开启MyClass类以及com.company.mylib包和其子包中的所有类的断言
日志禁用:-disableassertions或者 -da
java -ea:... -da MyClass MyApp
没有类加载器的系统类,使用 -esa/-enablesystemassertions启用
Java中处理系统错误的机制:异常,日志,断言
基本日志:可以使用全局日志记录器(global logger)并调用其中的info方法
高级日志:专业应用程序中,一般不要将所有的日志都放在全局日志记录器中,而是自定义日志记录器,可以调用getLogger方法创建或获取记录器console:Logger.getGlobal().info("assdada");
十二月 20, 2017 3:58:40 下午 com.qf.test.Test main 信息: assdada
取消日志:Logger.getGlobal().setLevel(Level.OFF)
console:Logger.getGlobal().info("assdada"); Logger.getGlobal().setLevel(Level.OFF); Logger.getGlobal().info("assdada");
十二月 20, 2017 4:03:23 下午 com.qf.test.Test main 信息: assdada
未被任何变量引用的日志记录器将会被垃圾回收,所以用一个静态变量存储日志记录器的引用private static final Logger myLogger = Logger.getLogger("com.qf.test"); public static void main(String[] args) { myLogger.info("test"); }
日志记录器的级别:
- SERVER
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
默认情况下,只记录前三个级别,但是也可以设置其他级别:logger.setLevel(Level.FINE);
记录低于INFO的级别,需要修改日志处理器的配置
Level.ALL——开启所有级别记录
Level.OFF——关闭所有级别记录
记录不同级别的方法:
logger.warning(message);
logger.fine(message);
或者
logger.log(Level.FINE,message);
记录异常
console:private static final Logger myLogger = Logger.getLogger("com.qf.test"); public static void main(String[] args) { myLogger.info("test"); IOException ie = new IOException("asad"); myLogger.log(Level.WARNING, "read", ie); }
十二月 20, 2017 4:57:42 下午 com.qf.test.Test main 信息: test 十二月 20, 2017 4:57:42 下午 com.qf.test.Test main 警告: read java.io.IOException: asad at com.qf.test.Test.main(Test.java:11)
或者console:public class Test { private static final Logger myLogger = Logger.getLogger("com.qf.test"); public static void main(String[] args) { try { File f = new File(""); InputStream in = new FileInputStream(f); } catch (IOException e) { e.printStackTrace(); myLogger.throwing("com.qf.test.Test", "main", e); } } }
throwingjava.io.FileNotFoundException: at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at com.qf.test.Test.main(Test.java:14)
throwing可以记录一条FINER级别的记录和一条以THROW开始的信息private static final Logger myLogger = Logger.getLogger("com.qf.test"); public static void read() throws IOException { myLogger.setLevel(Level.FINEST); IOException ie = new IOException("test"); myLogger.throwing("com.qf.test.Test", "read", ie); throw ie; }
修改日志处理器的配置
默认情况下,配置文件存在于jre/lib/logging.properties,想使用其他的配置文件,就要把java.util.logging.config.file设置为该配置文件的存储位置,并使用java -Djava.util.logging.config.file=configFile MainClass启动程序
.level= INFO ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ # default file output is in user's home directory. java.util.logging.FileHandler.pattern = %h/java%u.log java.util.logging.FileHandler.limit = 50000 java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter # Limit the message that are printed on the console to INFO and above. java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # Example to customize the SimpleFormatter output format # to print one-line log message like this: # <level>: <log message> [<date/time>] # # java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n ############################################################ # Facility specific properties. # Provides extra control for each logger. ############################################################ # For example, set the com.xyz.foo logger to only log SEVERE # messages: com.xyz.foo.level = SEVERE
编辑文件,修改命令行.level=INFO,设置自己的日志记录级别:com.qf.test.level=FINE
就是在日志记录器名后面添加.level
想要在控制台上看到日志消息:java.util.logging.ConsoleHandler.level=FINE
日志常用说明例子:
log.properties:
############################################################ # Default Logging Configuration File # # You can use a different file by specifying a filename # with the java.util.logging.config.file system property. # For example java -Djava.util.logging.config.file=myfile ############################################################ ############################################################ # Global properties ############################################################ # "handlers" specifies a comma separated list of log Handler # classes. These handlers will be installed during VM startup. # Note that these classes must be on the system classpath. # By default we only configure a ConsoleHandler, which will only # show messages at the INFO and above levels. handlers= java.util.logging.ConsoleHandler # To also add the FileHandler, use the following line instead. #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. # This specifies which kinds of events are logged across # all loggers. For any given facility this global level # can be overriden by a facility specific level # Note that the ConsoleHandler also has a separate level # setting to limit messages printed to the console. .level= INFO ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ # default file output is in user's home directory. java.util.logging.FileHandler.pattern = %h/java%u.log java.util.logging.FileHandler.limit = 50000 java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter # Limit the message that are printed on the console to INFO and above. java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # Example to customize the SimpleFormatter output format # to print one-line log message like this: # <level>: <log message> [<date/time>] # # java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n ############################################################ # Facility specific properties. # Provides extra control for each logger. ############################################################ # For example, set the com.xyz.foo logger to only log SEVERE # messages: com.xyz.foo.level = SEVERE
java code:
console:public static void main(String[] args) { Logger log = Logger.getLogger("lavasoft" ); Logger log1 = Logger.getLogger("lavasoft" ); System.out.println(log==log1); //true Logger log2 = Logger.getLogger("lavasoft.blog" ); log.info("aaa" ); log2.info("bbb" ); log2.fine("fine" ); }
true 十二月 20, 2017 5:44:46 下午 com.qf.test.Test main 信息: aaa 十二月 20, 2017 5:44:46 下午 com.qf.test.Test main 信息: bbb
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------修改log.properties:
console:############################################################ # Default Logging Configuration File # # You can use a different file by specifying a filename # with the java.util.logging.config.file system property. # For example java -Djava.util.logging.config.file=myfile ############################################################ ############################################################ # Global properties ############################################################ # "handlers" specifies a comma separated list of log Handler # classes. These handlers will be installed during VM startup. # Note that these classes must be on the system classpath. # By default we only configure a ConsoleHandler, which will only # show messages at the INFO and above levels. handlers= java.util.logging.ConsoleHandler # To also add the FileHandler, use the following line instead. #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. # This specifies which kinds of events are logged across # all loggers. For any given facility this global level # can be overriden by a facility specific level # Note that the ConsoleHandler also has a separate level # setting to limit messages printed to the console. .level= FINE ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ # default file output is in user's home directory. java.util.logging.FileHandler.pattern = %h/java%u.log java.util.logging.FileHandler.limit = 50000 java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter # Limit the message that are printed on the console to INFO and above. java.util.logging.ConsoleHandler.level = FINE java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # Example to customize the SimpleFormatter output format # to print one-line log message like this: # <level>: <log message> [<date/time>] # # java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n ############################################################ # Facility specific properties. # Provides extra control for each logger. ############################################################ # For example, set the com.xyz.foo logger to only log SEVERE # messages: com.xyz.foo.level = SEVERE
true 十二月 20, 2017 5:47:39 下午 com.qf.test.Test main 信息: aaa 十二月 20, 2017 5:47:39 下午 com.qf.test.Test main 信息: bbb 十二月 20, 2017 5:47:39 下午 com.qf.test.Test main 详细: fine
给一个简单的应用程序选择一个日志记录器,并把日志记录器命名为与主应用程序包一样的名字
Logger logger = Logger.getLogger("com.qf.test");
方便起见,可以将其作为静态域添加到类中:private static final Logger logger = Logger.getLogger("com.qf.test");
默认日志配置将级别高于或者等于INFO的所有信息记录到控制台,用户可以覆盖该配置文件
确保将所有消息记录到应用程序特定的文件中:
%h——系统属性user.home的值public class Test { private static final Logger logger = Logger.getLogger("com.qf.test"); public static void main(String[] args) { if(System.getProperty("java.util.logging.config.class") == null && System.getProperty("java.util.logging.config.file") == null) { try { Logger.getLogger("").setLevel(Level.ALL); final int LOG_ROTATION_COUNT = 10; Handler handler = new FileHandler("%h/myapp.log",0,LOG_ROTATION_COUNT); Logger.getLogger("").addHandler(handler); } catch (IOException e) { logger.log(Level.SEVERE, "can't create log file handler!", e); } } } }
所有级别为INFO,WARNING,SEVERE的消息都显示在控制台上,最好将对程序用户有意义的信息设置为这几个级别
调试时,可以给每个类准备一个main方法或者使用junit用于单元测试
日志代理:一个子类的对象,可以截获方法调用并进行日志记录,然后调用父类的方法
console:public class Test { private static final Logger logger = Logger.getLogger("com.qf.test"); public static void main(String[] args) { Random g = new Random() { public double nextDouble() { double result = super.nextDouble(); logger.info("nextDouble: "+result); return result; } }; g.nextDouble();//有日志信息 g.nextInt();//没有日志信息 } }
十二月 21, 2017 5:04:00 下午 com.qf.test.Test$1 nextDouble 信息: nextDouble: 0.9411068404098785