Java基础知识扫盲(三)——异常、断言、日志

异常

  • 所有异常都是由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

你可能感兴趣的:(Java基础知识扫盲(三)——异常、断言、日志)