实现方法的回调有几种办法:
1. 接口方式(注册监听器)
2. 反射机制,使用Method的invoke
3. lambda表达式
已检查异常:竭尽全力仍不能完全避免的异常,此时就要提供异常处理器
未检查异常:精心控制可以完全避免的异常,例如:空指针、除0操作
第六章 接口、lambda表达式与内部类
接口方法为public、域为public static final,JLS推荐无须显式标记
Java SE 8中,接口支持静态方法,之前的做法是将静态方法放在伴随类中。
例如: Collection/Collections 、 Path/Paths
Java SE 8 中,接口方法可以提供一个默认实现,用 default 修饰符
可以消除伴随类(当然 API 中肯定无法移除)
利于接口演进,就接口添加新默认方法可保证源代码、二进制兼容性
源代码兼容:改变类后,依赖该类的其他类能重新编译
二进制兼容:改变类后,依赖该类的其他类使用旧的编译文件还能执行
解决方法冲突规则
某类继承超类实现某接口,超类及接口默认含有相同方法名及参数的方法
且不管接口是否包含默认方法,此时子类一定使用是超类的方法
冲突特指类不实现接口时,使用接口默认方法是否存不知道该调用哪个方法(二义性)
某类实现了两个接口,且两接口含有方法名参数一致的方法
当这两个方法是抽象时,该类不会提示冲突,选择其中之一实现即可
当某个接口含默认方法,该类必须实现方法解决冲突(虽然只有一个默认方法)
当两接口都含有默认方法,该类同样必须实现方法解决冲突
Object类包含一个 protected 的 clone 方法
某类只能在内部调用 Object 的默认 clone 方法克隆它自己
某类包含其他类,要克隆其他类时,不能调用其他类默认继承 Object 的 clone 方法
只能让其他类实现 Cloneable 标记接口,并覆盖 Object 的 clone 方法,将修饰符扩大为public才行
所有数组类型都有一个 public 的 clone 方法,包含所有数组元素的副本(引用也是副本)
只有一个抽象方法的接口称函数式接口,需要这种接口对象时可以提供一个 λ 表达式
其实,接口中是可以抽象声明 Object 类的 public 方法,
但实际上每个接口的实例都有这些被声明为抽象的方法的Object类的默认实现
最好把λ表达式看做函数而非对象,λ表达式可以传递到函数式接口中
执行λ表达式体与传统接口的实体对象的调用相比,可能更加高效,
相比 invokeinterface , λ 采用 invokedynamic 调用引导方法( Bootstrape Method )
反编译后λ表达式的调用被编译成为一个invokedynamic的字节码操作
关于此字节码的介绍请查看: Invokedynamic :Java 的秘密武器
有时候现成方法可以完成想要传递到其他代码的某个动作就可以使用方法引用
方法引用有3种情况:
object::instanceMethod 例:System.out::println
Class::staticMethod 例:Math::pow
Class::instanceMethod 例:String::compareToIgnoreCase
第一种,等同于 x -> Sytem.out.println(x)
第二种,等同于 (x,y) -> Math.pow(x,y)
第三种,等同于 (x,y) -> x.compareToIgnoreCase(y)
this::instanceMethod 与 super::instanceMethod 也是合法的
分别调用当前类的方法或当前类的父类的方法
构造器的引用为:Object::new
Java无法构造泛型类型的T的数组,利用构造器引用有时能够保证类型不被丢失
例如:new T[n] 编译错误,所有之前返回不确定类型的方法声明只能为Object[]
新方法使用构造方法引用来解决此问题,例如 Stream 的方法:
<A> A[] toArray(IntFunction<A[]>)
lambda变量作用域
lambda表达式有3个部分:代码块、参数、自由变量的值(不在代码块定义的变量)
这些自由变量被lambda表达式引用,被称为捕获
被捕获的变量即使外部方法调用栈执行完后撤栈后仍然能被lambda表达式使用
被捕获的变量被要求在lambda代码块中及外部代码中不能改变(effectively final)
闭包:能够维持外部自由变量的代码块,即使外部自由变量已经不存在
lambda表达式中声明的变量名不能与局部变量同名
lambda表达式中的this关键字是指创建这个表达式的方法的this
处理lambda表达式
使用lambda表达式的重点是延迟执行(deferred execution)
想延迟的原因:
在单独线程中运行代码
多次运行代码
在算法适当的位置运行代码
发生某种情况时执行代码
只在必要时才运行代码
Java API 中默认提供了函数式接口,详见链接
大多数函数式接口都提供了非抽象方法来生产或合并函数
Predicate 的 and 、 or 等方法
使用 @FunctionalInterface 标注函数式接口是个好习惯
许多接口包含很多方便的静态方法来创建接口实例
Comparator 的静态 cpmparing 方法有一个键提取器函数,
接受方法引用或lambda表达式:
comparing(Function<? super T,? extends U> keyExtractor)
内部类访问外部类的私有域是通过在外部类加入特殊的静态的访问方法,类似:access$0
局部内部类不能有public、static或private修饰符,它们的作用域被限定在局部代码块中
内部类也可访问外部类的局部变量,只不过局部变量必须为final、或事实上的final
Java 7及之前:局部变量必须由final修饰
Java 8及之后:局部变量不须强制加final,但在外部类或内部类中都不能更改其值
匿名内部类可被用于进行双括号初始化,由于是匿名子类,故equals的getClass会不等
Arrays.toString(new ArrayList<String>() { {
add("One";
add("Two");
} } );
如果要对匿名子类调用 equals 方法,由于 getClass 获取到的是匿名子类的,
故并不相等,要在内部类获取外部类的类型信息可以使用如下技巧:
inner.getClass().getEnclosingClass();
出于隐藏一个类,且该类不需引用外部类对象,可以声明为静态内部类
声明在接口内的内部类,自动为static和public的类
声明在静态方法里的内部类,虽无法被static修饰但其也不包含外部类对象
外部类为为何不能被static修饰?
在链接给的回答中,necromancer的回答及Lumi的补答蛮贴切:
Top level classes are static by default. Inner classes are non-static by default You can change the default for inner classes by explicitly marking them static Top level classes, by virtue of being top-level, cannot have non-static semantics because there can be no parent class to refer to. Therefore, there is no way to change the default for top-level classes.
Top Level classes are static because you don’t need any instance of anything to refer to them. They can be referred to from anywhere. They’re in static storage (as in C storage classes). The language designers could have allowed the static keyword on a top-level class to denote and enforce that it may only contain static members, and not be instantiated; that could, however, have confused people as static has a different meaning for nested classes.
大概译文:
顶层类(外部类)默认即为静态的类 ,内部类默认为非静态的。可以通过static关键字修饰内部类,从而更改为非默认的静态内部类。顶层类不能有非静态语义其优点在于可以不需要父类就能引文它,因此不能通过任何方式更改顶层类的默认语义。
顶层类它们之所以是静态的是因为它们不需要任何具体的实例去引用它们,它们可以在任何地方被引用。它们存储在静态区,Java语言的设计者们原本大可允许使用static关键字修饰顶层类,强制该类只能包含静态的成员及方法而不能被实例化,但是为何不最终不允许static修饰呢?是因为如果允许static修饰外部类会使人们对staic修饰内部类的含义产生误导,静态的内部类是可以同时包含静态的、非静态的成员及方法的。
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
// 自定义操作
// do something...
return m.invoke(target, args);
所有代理类都覆盖了 Object 类的 toString 、equals 、hashCode 方法
Sun 虚拟机中的 Proxy 类将生成以 $Proxy 开头的类名
使用同一个加载器、接口数组调用两次 newProxyInstance 得到同一个类两个对象,
Class proxyClass = Proxy.getProxyClass(loader, interfaces);
代理类一定是 public 、final 的,其所实现的所有接口为 public 时,代理类不属于某个
特定包,否则所有 非 public 的接口必须属于同包,并且代理类也属于该包
第七章 异常、断言与日志
异常都派生于Throwable类,下层分解为:Error、Exception
Error:Java运行时系统的内部错误和资源耗尽错误,应用程序不抛此类型
Exception:下层分为RuntimeException、其他异常(例如:IOException)
RuntimeException:程序错误导致的异常,例如:数组越界、null指针
其他异常:程序本身没问题,由于像I/O错误问题导致的属其他异常
非受查异常与受查异常
非受查异常包括:派生于Error、RuntimeExceptioin的错误或异常
受查异常:除非受查异常外的其他异常 (此类异常需要进行异常处理)
应该抛异常情况:
调用一个抛出受检查异常的方法
程序运行过程中发现错误,并利用throw语句抛出一个受查异常
程序出现错误,如数组越界
Java虚拟机和运行时库出现的内部错误
1、2两条是需要使用throws声明显示抛出的,或者捕获异常
3、4两条为非检查异常,要么不可控、要么应该避免,故不强制一定抛出
子类覆盖超类方法,抛出异常不能比超类的异常更通用,只能更特定或不抛出
没有throws说明符的方法不能抛出任何受查异常
Java SE 7 及之后,单个 catch 支持捕获多个异常类型,但不存在子类关系,语法:
catch (Exception | Exception e) { ... }
此时,e 变量为隐含的 final 的变量只能被赋值为其中之一,这种语法更简洁更高效
再次抛出异常与异常链
catch子句可以抛出异常,可以起到如下作用
出于异常识别分类,改变异常类型
受查异常转运行异常(用于不能抛出受查异常的情形)
记录异常日志
catch子句抛出新异常时,可以调用 newE.initCause( oldE ) 使得原始异常不丢失
Java SE 7之后,抛出比声明的异常更广的类型时,只要try块仅有声明的异常类型,
且在catch块没被改变,声明的异常是合法的
如果final块也可能有异常时,最佳实践代码如下:
try {
try {
// might throw exceptions
} finally {
// might throw exceptions
} catch(Exception e) {
// show error message
这样可以解耦 try/catch 和 try/finally ,使职责清晰明了
try/finally 只负责确保异常后仍可执行 finally 的语句
try/catch 只负责记录出现的错误
嵌套try时,很可能会出现外层异常覆盖了里层异常,导致原始异常丢失,
在外层增加个里层异常是否为空的判断,如果不为空抛出里层异常否则抛出外层异常很有用,
InputStream in = ...;
Exception ex = null;
try {
try {
// 可能出现异常代码
} catch(Exception e) {
ex = e;
throw e;
} finally {
try {
in.close();
} catch(Exception e) {
if (ex == null) throw e; // 如果里层没有异常,才抛外层异常
throw ex; // 否则抛出里层异常
当然,Java SE 7有自动关闭资源的特性,会比这里的处理更为简洁
try (InputStream in = ...) { // 这里的in资源会自动关闭
// 可能出现异常代码
} catch(Exception e) {
// 处理代码可能出现的异常
} finally {
当in关闭出现异常时,会被自动捕获并被抑制,
异常将由e.addSuppressed方法追加到原代码异常的后面,
当然带资源的try/catch会在资源关闭之后执行
使用异常机制技巧 (早抛出,晚捕获)
异常不能代替简单的测试(不能将异常用于逻辑判断,性能太差)
不要过分细化异常(不要把每条可能出现异常的代码装在独立的try块里)
利用异常层次结构(寻找合适子类或自创的异常,逻辑错误不抛受查异常)
不要压制异常(吃掉异常不处理)重要的异常一定要处理
检测到错误及时处理,不要放过 早抛出
不要羞于传递异常(读一个文件时,出现错误传递异常好过捕获)晚捕获
断言只用于测试阶段确定程序内部的错误位置,它不改变程序的逻辑运行
不要将断言参与业务逻辑处理,实际上也参与不了,断言是致命的不可恢复的错误
处理系统错误的三种机制:抛异常、使用断言及记录日志
日志可以很容易的定制记录级别、或禁止,也可使用不同处理器进行不同格式的输出,
并且记录器、处理器都可过滤记录、格式化日志,可配置性很强
简单使用可以仅使用一个全局日志记录器
Logger.getGlobal().info("Need to be log...")
取消所有日志
Logger.getGlobal().setLevel(Level.OFF)
企业级的日志中,应该自定义日志记录器而不要全部交给全局日志记录器
private static final Logger myLog = Logger.getLogger("org.package");
之所以使用static修饰,是为了防止垃圾回收
传入的类似包名字符串能很好的使得子包默认继承父包的设置
例如:org包设置日志级别,org.package默认与org设置一致
日志有7个记录器级别:
SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST
默认值记录前三个级别的日志,调用myLog.setLevel(Level.FINE)可修改默认值
Level.ALL、Level.OFF为开启、关闭所有日志的特殊常量
记录日志有下面几种方法:
myLog.info(message) 方法名指定日志级别
myLog.log(Level.INFO, message) 参数指定日志级别
默认将显示日志调用的类名、方法名,虚拟机优化执行时得不到确切调用信息,采用:
void logp(Level l, String className, String method, String msg);
来获取类和方法的具体位置,另外下面这些方法可用来跟踪方法执行流向:
void entering(String cName, String mName)
void entering(String cName, String mName, Object p)
void entering(String cName, String mName, Object[] ps)
void exiting(String cName, String mName)
void exiting(String cName, String mName, Object r)
cName 类名,mName 方法名,p 单参数,ps 参数数组,r 返回值
这些调用产生FINER级别、字符串为ENTRY、RETURN开始的日志记录
记录异常可使用
void throwing(String cName, String mName, Throwable t)
void log(Level l, String message, Throwable t)
日志管理器配置
系统默认配置文件存在于jre/lib/logging.properties文件中
想要另外指定配置文件,有以下几种设置方式:
虚拟机参数式:-Djava.util.logging.config.file = configFile
代码调用式: System.setProperty("java.util.logging.config.file", file)
修改默认日志级别,可以在配置文件中加入
.level=INFO
或者指定自己的记录器的记录级别
org.package.level=FINE
程序运行时,可通过jconsole改变日志的记录级别
具体查看 Using JConsole to Monitor Applications
日志处理器
默认日志记录器发送到ConsoleHandler,由它输出到System.err流
并还将记录发送到父处理器,最终处理器为ConsoleHandler(名称为空,即“”)
通过myLog.addHandler(yourHandler)可以安装自己的处理器
通过myLog.setUseParentHandlers(false)可以阻止处理器向父处理器传递
常用的处理器:
SocketHandler 将记录发送到特定的主机和端口
FileHandler 将记录收集到文件当中,默认写到用户主目录 javan.log
其中 n 为唯一编号,例如:java001.log 或 java002.log
文件处理器的参数及用法,参考API文档
日志记录的加工处理
通过实现Filter接口,实现boolean isLoggable(LogRecord record)
扩展Formatter类,覆写String format(LogRecord record)
如果记录有头及尾部时,覆写String getHead(Handler h)和
String getTail(Handler h)