如今我现已毕业,即将踏上工作岗位,Java语言却才只看了这么一点,真的很是惭愧。现在的我,语言未通,业务无知,框架不懂,真的很是惶恐,惶恐于日后踏上工作岗位时一问三不知被同事耻笑,但又无能为力的尴尬感。姑且默默激励自己,因为我知道开始的艰辛和未知的技术必然会在今年下半年给予我重创,但是能否使自己蜕变和成长,这半年的效果也尤为重要。转行之路仍未结束,在自己独当一面之前,什么都仅仅是前行路上的成长点罢了。
几个基本概念的明晰:
这里简单表述了可能出现错误的原因:
Error类层次结构描述了Java运行时系统内部错误和资源耗尽错误;Exception层次结构分为由程序错误(如错误的类型转换、数组访问越界以及访问null指针等)所导致的RuntimeException,以及程序本身没有问题而是因为其他原因导致的异常(典型的如I/O错误IOException)。
通俗一点来说的话,就是RuntimeException异常必然是程序员所导致的问题。Error类或RuntimeException类的所有异常都被称为非受查异常,所有其他的异常都被称为受查异常。我们常说的异常处理,都是针对受查异常进行实现的。
一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制,要么就应该避免发生。这里特别注意java的异常抛出与throws后面的息息相关,只会抛出throws说明了的受查异常,如果没写或有其他受查异常则不会抛出(注意是不抛出受查异常,非受查异常仍然会抛出,无法人为控制)。
两种方式,不过一定要记得在方法前加上throws说明符:
throw new IOException();
IOException e = new IOException();
throw e;
从Exception或者Exception的子类进行派生即可,注意所派生的类需要实现两个构造器:默认的构造器和带有详细描述信息的构造器(使用super类的构造器实现):
class testException extends Exception
{
public testException();
public testException(String gripe)
{
super(gripe);
}
}
抛出和捕获是异常处理的两大过程。
抛出异常的方法只负责抛出异常,异常捕获和处理由调用该方法的调用者去操心(使用try catch语句进行捕获与处理,或者在调用者方法前声明对应的throws说明符以继续传递异常)
多个catch语句即可。
java可以将原始异常设置为新异常的“原因”,使用这种包装技术,可以使用户抛出子系统中的高级异常,而不会丢失原始异常的细节(比如在不允许抛出受查异常的地方,将受查异常包装为运行时异常),当然也可以不用包装原始异常,直接用throw语句即可再次抛出(throw 原异常;):
Throwable e = se.getCause();
throw e;
不论异常是否被捕获,finally子句中的代码都将会被执行。这里务必注意finally子句的执行特殊性,其主要用于在其中关闭try块或者之前的资源,但是如果涉及到退出语句比如return之类的有意想不到的后果,在编写时务必注意。
假设资源属于一个实现了AutoCloseable接口的类,Java SE7为这种代码模式提供了一个快捷的编写方式如下,在try块退出时会自动地调用AutoCloseable接口中的close()方法:
try(Scanner in = new Scanner(...) //Scanner类比于各种资源类
{
work with in;
}
堆栈轨迹是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特点位置。在实际使用中最简单的是通过所捕获的异常e,调用e的方法printStackTrace(),从而得到当前异常e的所有堆栈轨迹。
而静态的Thread.getAllStackTrace()方法可以产生所有线程的堆栈轨迹(该方法获取的每个线程的堆栈轨迹数据结构为StackTraceElement[]数组,对于StackTraceElement类,可以通过其内部的toString函数直接获取想相关的信息)。
这里有个很好玩的事情,就是对于使用异常与不使用异常的党争问题,以及对于大量使用异常与少量使用异常的党争问题,我不配做过多评价,不过我支持多使用异常处理。
public Image loadImage(String s)
{
try
{
//code that maybe throw checked exceptions
}
catch(Exception e)
{
//异常处理
}
}
断言是一种测试和调试阶段所使用的战术性工具,通过关键字assert来实现此功能,有两种形式如下。这两种形式都会对条件进行检测,如果结果为false,第一种形式会抛出一个AssertionError异常,第二种形式中的表达式会被传入AssertionError的构造器中后,再将AssertionError抛出。
assert 条件;
assert 条件 : 表达式;
在IDEA中,在项目配置的虚拟机选项里加入-enableassertions或-ea即可开启断言,加入(或者直接不写)-disableassertions或-da即可禁用断言,开启与禁用断言不需要重新编译程序,相关代码是否执行与类加载器直接相关。
断言失败是致命的、不可恢复的错误,断言检查只用于开发和测试阶段。只应该用于在测试阶段确定程序内部的错误位置。通俗来说,就是断言最好用于进行参数的格式检查,以此快速确定参数的格式问题(如前置条件 assert a != null;)。
这里的意思是将注释中的假设情况通过断言的方式来实现,感觉都可以。
书本的意思是建议使用日志记录来代替传统的System.out.println这样的控制台打印语句,这样可以充分利用日志的统一管理能力,整体功能效果优于简单的打印。
如果当前程序较为简单,所有的数据都只需要记录到一个日志记录器中,那么使用全局日志记录器即可,具体如下,不过全局日志记录器也是可以设置记录级别的。
Logger.getGlobal().info("info");
其实也没有所谓的“高级”一说,就是通过对日志记录器进行创建与命名,从而通过不同的日志记录器来记录彼此特定的信息罢了。实现方式有两种,一种是在类的内部建立一个私有常量静态域,一种是通过Logger.getLogger()方法来不断调用。
//由于日志记录器往往不会被任何其他变量引用,所以如果不将其设为static类型,会导致可能在完成时被垃圾回收,导致不必要的麻烦
private static final Logger myLogger = Logger.getLogger("custom name");
Logger.getLogger("custom name").(调用方法);
而在代码中对日志记录时,对应到日志本身的级别具体如下,从左到右级别依次降低。日志记录器默认只记录级别最高的三个级别,如果想要日志记录器记录对应级别的日志,最好将日志处理器的级别阈值进行重新设置。
SEVERE;WARNING;INFO;CONFIG;FINE;FINER;FINEST
myLogger.setLevel(Level.ALL);
在进行实际的日志记录时,两种最常规的日志记录器调用方式如下,两种方式的效果相同。
myLogger.warning(message);
myLogger.log(Level.WARNING, message);
在进行实际的日志记录时,跟踪执行流的调用方式如下,这些调用会生成FINER级别和以字符串ENTRY和RETURN开始的日志记录,通常用于方法的开始与结尾,代表方法的正常进入与退出。
myLogger.entering("className", "methodName");
myLogger.entering("className", "methodName", Object param);
myLogger.entering("className", "methodName", Object[] params);
myLogger.exiting("className", "methodName");
myLogger.exiting("className", "methodName", Object result);
在进行实际的日志记录时,记录异常与异常抛出的调用方式如下。其中异常的日志记录会记录异常发生的位置,与产生该异常的方法调用所在处;异常抛出的日志记录会得到一条FINER级别的记录(包括一个以THROW开始的信息),所以如果没有对日志记录级别进行配置,这条FINER级别的日志将不会被记录。
myLogger.log(Level l, "message", Throwable t);
myLogger.throwing("className", "methodName", Throwable t);
这是通过编辑文件来修改日志记录器的默认属性,不过操作较为繁琐。
书本中意思是在请求日志管理器时指定资源包,这样就可以将一些特定词根据资源包的映射实现存储:
Logger logger = Logger.getLogger(loggerName, resourceBundleName);
日志记录器在将日志记录后,会把所有的日志记录发送到自己的日志处理器以及自己的日志处理器的父处理器中进行处理,这里的所有日志处理器也有自己的处理级别,低于其处理级别的日志记录其不会处理。
常用的日志处理器有控制台日志处理器ConsoleHandler(日志在控制台中记录),文件日志处理器FileHandler(日志在文件中记录),套接字日志处理器SocketHandler(日志通过套接字传输),以及流日志处理器StreamHandler(日志以流的形式记录),这些日志处理器通过new出自己的实例后,将实例添加到日志记录器的处理器列表中即可。对于每一种类型的日志处理器,都对应同一个最终的ConsoleHandler(控制台日志处理器)父处理器,这个最终处理器的处理级别为INFO及以上。
FileHandler handler = new FileHandler();
myLogger.addHandler(handler);
Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
//%1为pattern参数,%2为append参数
FileHandler fileHandler = new FileHandler("%h/Desktop/simpleExcept%u.txt", true);
过滤器是日志记录器和日志处理器都可以使用的一种接口,在为两者添加过滤器时,通过设计一个实现了Filter接口中isLoggable方法的类(根据情况lambda表达式或者内部类应该都可以),再借助日志记录器/处理器的setFilter方法传参即可实现。
格式化器用于给处理器的输出进行格式调整,在为处理器添加格式化器时,需要拓展Formatter类并覆盖其中的format方法得到一个自定义类,再借助日志记录器的setFormatter方法传参即可实现。
总结而言,日志系统中的日志记录器、日志处理器、过滤器与格式化器的彼此关系如下图所示。
“最常用”的日志创建操作具体如下:
Logger logger = Logger.getLogger("com.mycompany.myapp");
private static final Logger logger = Logger.getLogger("com.mycompany.myapp");
这里主要讲了一些Java中的调试技巧:
System.out.println("x=" + x);
jconsole processID