异常
- 所有异常都是由Throwable继承而来,分为两类:Error和Exception(RuntimeException程序错误和IOException其他异常)
- 派生于 Error 类或 RuntimeException 类的所有异常称为非受查( unchecked ) 异常,其他的异常称为受查( checked) 异常
- 编译器将核查是否为所有的受査异常提供了异常处理器
一个方法必须声明所有可能抛出的受查异常, 而非受查异常要么不可控制(Error),要么就应该避免发生 ( RuntimeException )。
Java 的内部错误, 即从 Error 继承的错误。任何程序代码都具有抛出那些异常的潜能, 而我们对其没有任何控制能力。
如果在子类中覆盖了超类的一个方法, 子类方法中声明的受查异常不能比超类方法中声明的异常更通用 (也就是说, 子类方法中可以抛出更特定的异常, 或者根本不抛出任何异常)。特别需要说明的是, 如果超类方法没有抛出任何受查异常, 子类也不能抛出任何受查异常。
try {...} catch(...){...}块中,如果在 try语句块中的任何代码抛出了一个在 catch 子句中说明的异常类,那么:
- 程序将跳过 try 语句块的其余代码
- 程序将执行 catch 子句中的处理器代码
如果想传递一个异常, 就必须在方法的首部添加一个 throws 说明符, 以便告知调用者这个方法可能会抛出异常。
如果编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个受查异常。
可以声明多个catch块儿。
可以合并多个异常类型:当捕获的异常类型彼此之间不存在子类关系时,才可以使用这个特性。
try{
code that might throw exceptions
}catch (FileNotFoundException | UnknownHostException e){
emergency action for missing files and unknown hosts
}
捕获多个异常时,异常变量隐含为 final 变量。不能为其赋不同的值。
异常链
在 catch 子句中可以抛出一个异常, 这样做的目的是改变异常的类型。
捕获异常并将它再次抛出的基本方法:
try{
access the database
}catch (SQLException e){
throw new ServletException("database error: " + e.getMessageO) ;
}
有一种更好的处理方法,将原始异常设置为新异常的“ 原因”:
try{
access the database
}catch (SQLException e){
Throwable se = new ServletException ("database error");
se.initCause(e);
throw se;
}
使用下面这条语句重新得到原始异常:
Throwable e = se.getCause();
可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
对于这种代码模式:
open a resource
try{
work with the resource
}finally{
close the
}
假设资源属于一个实现了 AutoCloseable 接口的类,AutoCloseable 接口有一个方法:void close() throws Exception
还有一个 Closeable 接口。 这是AutoCloseable 的子接口, 也包含一个 close方法。 不过, 这个方法声明为抛出一个 IOException。
带资源的 try 语句(try-with-resources) 的最简形式:try块退出时,会自动调用 res.close()
try (Resource res = . . .){
work with res
}
要读取一个文件中的所有单词:
try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words")), "UTF-8"){
while (in.hasNext())
System.out.println(in.next()) ;
}
这个块正常退出时, 或者存在一个异常时, 都会调用 in.close() 方法, 就好像使用了finally块一样。
也可以指定多个资源:
try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"). "UTF-8");
PrintWriter out = new PrintWriter("out.txt")) {
while (in.hasNextO)
out.println(in.next().toUpperCase());
}
不论这个块如何退出, in 和 out 都会关闭。
如果 try 块抛出一个异常, 而且 close 方法也抛出一个异常:原来的异常会重新抛出,而 close方法抛出的异常会被抑制。这些异常将自动捕获,可以调用 getSuppressed 方法,它会得到从 close 方法抛出并被抑制的异常列表。
堆栈轨迹(stack trace)
是一个方法调用过程的列表,包含了程序执行过程中方法调用的特定位置
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 in = new Scanner(System.in);
System.out.print("Enter n: ");
int n = in.nextInt();
factorial(n);
}
结果:
Enter n: 3
factorial(3):
throwable.ThrowableTest.factorial(ThrowableTest.java:32)
throwable.ThrowableTest.main(ThrowableTest.java:49)
factorial(2):
throwable.ThrowableTest.factorial(ThrowableTest.java:32)
throwable.ThrowableTest.factorial(ThrowableTest.java:40)
throwable.ThrowableTest.main(ThrowableTest.java:49)
factorial(1):
throwable.ThrowableTest.factorial(ThrowableTest.java:32)
throwable.ThrowableTest.factorial(ThrowableTest.java:40)
throwable.ThrowableTest.factorial(ThrowableTest.java:40)
throwable.ThrowableTest.main(ThrowableTest.java:49)
return 1
return 2
return 6
上述即为递归阶乘函数的堆栈情况。
断言
Assert在默认情况下, 断言被禁用。可以在运行程序时用 -enableassertions 或 -ea 选项启用。
需要注意的是, 在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器( class loader) 的功能。当断言被禁用时, 类加载器将跳过断言代码, 因此,不会降低程序运行的速度。
记录日志
生成日志:
Logger.getGlobal().info("");
获取日志记录器:
private final Logger logger = LoggerFactory.getLogger(this.getClass()); // LoggerFactory位于 org.slf4j中
有7个日志级别:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
默认情况下,只记录前三个级别。
设置其他级别:
logger.setLevel(Level.FINE);
关闭所有级别的日志:
Logger.getGlobal().setLevel(Level.OFF); //在main里面调用可以
开启所有级别的日志:
Logger.getGlobal().setLevel(Level.ALL); //在main里面调用可以
记录方法:
logger.warning(message):
logger.fine(message):
或使用log方法:
logger.log(Level.FINE, message);
如果将记录级别设计为 INFO 或者更低, 则需要修改日志处理器的配置。 默认的日志处理器不会处理低于 INFO 级别的信息。
默认的日志记录将显示包含日志调用的类名和方法名, 如同堆栈所显示的那样。 但是,如果虚拟机对执行过程进行了优化,就得不到准确的调用信息。此时,可以调用 logp 方法获
得调用类和方法的确切位置, 这个方法的签名为:
void logp(Level l, String className, String methodName, String message)
以及跟踪执行流的方法:
void entering(String dassName, String methodName)
void entering(String className, String methodName , Object param)
void entering(String className, String methodName , Object[] params)
void exiting(String className, String methodName)
void exiting(String className, String methodName, Object result)
例如:
int read(String file, String pattern){
logger.entering("com.mycompany.mylib.Reader", "read",
new Object[] { file, pattern });
logger.exiting("com.mycompany.mylib. Reader", "read" , count):
return count;
}
生成 FINER 级别和以字符串 ENTRY 和 RETURN 开始的日志记录。
修改日志管理器配置
############################################################
# 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:
# : []
#
# 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
- 在默认情况下, 配置文件存在于:jre/lib/logging.properties
- 使用其它配置文件执行下列命令并启动应用程序:java -Djava.util.logging.config.file=configFile MainClass
- 日志管理器在 VM 启动过程中初始化, 这在 main 执行之前完成。
如果在 main中调用 System.setProperty("java.util_logging.config_file",file), 也会调用LogManager.readConfiguration() 来重新初始化曰志管理器 - 默认日志记录级别 .level=INFO
- 指定自己的日志记录级别com.yy.myapp.level=FINE
即在日志记录器名后面添加后缀 .level - 控制台打印消息级别控制:java.util.logging.ConsoleHandler.level
关于日志消息本地化、日志处理器、过滤器、格式化器更详细的参考核心技术(I)7.5.4-7.5.7