The if-else Control Flow Using Optional
Sometimes you may want to write the if-else control flow based on an Optional
object.
For example, an API from a third party library declares Optional
as its return type.
You need to compose an if-else like control flow using that Optional
object.
Of course, it can be done by testing isPresent()
in a traditional if-else statement.
var itemOpt = service.getItem(itemId);
if (itemOpt.isPresent()) {
addToOrder(itemOpt.get());
} else {
log.info("missing item {}", itemId);
sendOutItemMissedEvent();
}
The above code doesn’t take any advantage of Optional
.
Actually, since Java 9 the ifPresentOrElse(Consumer<? super T>, Runnable)
method can be used to implement such control flow, which is a bit more elegant.
service.getItem(itemId).ifPresentOrElse(
item -> {
addToOrder(item);
},
() -> {
log.info("missing item {}", itemId);
sendOutItemMissedEvent();
});
Print Exception Together With Parameterized Log Messages
Often when use SLF4J, you may wonder if an exception object can be printed in the same parameterized log message together with other objects. Actually SLF4J supports it since 1.6.0.
log.error("failed to add item {}", "item-id-1", new RuntimeException("failed to add item"));
The SLF4J call above prints log message like below.
13:47:16.119 [main] ERROR example.TmpTest - failed to add item item-id-1
java.lang.RuntimeException: failed to add item
at example.TmpTest.logErrorTest(TmpTest.java:10)
...
...
So it actually doesn’t need to log as below.
log.error("failed to add item {}", "item-id-1");
log.error("exception details:", new RuntimeException("failed to add item"));
Here is what the official FAQ says.
The SLF4J API supports parametrization in the presence of an exception, assuming the exception is the last parameter.
Use the first style to save one line.
SLF4J doesn’t clearly state this behavior in the Javadoc of Logger.error(String format, Object... arguments)
(at least not in the 1.7.26 version). Maybe if a new method like below is added to Logger
, programmer would
find out this feature more easily.
public void error(String format, Throwable t, Object... arguments);
Java Generics, Bounded Wildcards and PECS
With a class hierarchy below,
class Fruit {
public void hello() {}
}
class Apple extends Fruit {}
class Banana extends Fruit {}
Lower Bounded Wildcards and Upper Bounded Wildcards
? super Fruit
is called lower bounded wildcard.
? extends Fruit
is called upper bounded wildcard.
Usually a class hierarchy is illustrated like,
Object
|
Fruit
/ \
Apple Banana
The base class is placed upper, subclasses placed lower.
For ? super Fruit
, the lower bound of the type argument is determined, which is Fruit
, so it’s called lower bounded wildcard.
Similar, for type argument ? extends Fruit
, its upper bound is fixed, so it’s called upper bounded wildcard.
”? super Fruit”
void func(List<? super Fruit> fruits) {
fruits.add(new Fruit()); // ok
fruits.add(new Apple()); // ok
fruits.add(new Banana()); // ok
// Error: Cannot resolve method 'hello'
fruits.get(0).hello();
}
The ?
in the declaration List<? super Fruit>
means fruits
is a List
of an unknown type.
The ? super Fruit
means the unknown type is limited to be Fruit
or its super class (Object
in this case).
When invoke func(List<? super Fruit> )
, List<Fruit>
or List<Object>
can be passed as the argument.
All the fruits.add(...)
in the above code is ok, because no matter what type actually the type argument is
Fruit
, Apple
, and Banana
are the subtypes of that type.
An Apple
object is a Fruit
(or Object
) object, so an Apple
object can be added into a List
of Fruit
(or Object
).
Why fruits.get(0).hello()
fails?
The actual type of the elements in the fruits
List is undetermined when declare the method.
When call a method on an element, the compiler needs to make sure the unknown type has the method.
? super Fruit
in the type argument declaration sets bounds for the unknown type, one bound (“lower”) is type Fruit
, the other bound (“upper”) is type Object
To safely invoke methods on the unknown type, only methods of the most upper type can be invoked.
Obviously, method hello
is not from the most upper type (i.e. Object
).
If passing a List<Object>
as the parameter, then obviously the elements in fruits
would not have hello()
method. Therefore the compiler raises the error.
”? extends Fruit”
void func(List<? extends Fruit> fruits){
// Error: add(capture<? extends Fruit>) in List cannot be applied to add(Fruit)
fruits.add(new Fruit());
fruits.add(new Apple()); // similar error
fruits.add(new Banana()); // similar error
fruits.get(0).hello(); // ok
}
Similar, declaring the parameter as List<? extends Fruit>
means fruits
is an unknown type,
and can be List<Fruit>
, List<Apple>
or List<Banana>
when the method is invoked.
Why fruits.get(0).hello()
is ok in this case?
Similar, ? extends Fruit
sets bounds for the unknown type, the upper bound is determined as the type Fruit
.
The hello()
method is from the most upper type, so the compiler knows it’s safe to call it within the func
body.
Why statements like fruits.add(new Fruit())
fails?
The type of fruits
is unknown, it doesn’t mean the type of fruits
is “dynamic”.
Being “dynamic” means the type of fruits
can be List<Fruit>
at some point, then be List<Apple>
at some other point.
It’s not possible in Java, since Java is a static language.
The two types, List<Fruit>
and List<Apple>
, have nothing to do with each other
(List<Apple>
is not a subtype of List<Fruit>
).
The type of the fruits
parameter is determined on the invocation of func
.
For a specific invocation, the type of fruits
is determined and static.
For example, with an invocation like func(new ArrayList<Apple>())
, the statement fruits.add(new Fruit())
would raise compiler error
(since a Fruit
cannot be add()
ed into a List<Apple>
).
To ensure all the possible invocation of func
works, the compiler just can’t allow the statements like fruits.add(new Fruit())
appear in the method body.
What is “capture” (capture<? extends Fruit>
)?
Inject a Method Interceptor in Guice
I recently made a mistake to new an object (MethodInterceptor
) in an plain old way in a Guice configuration,
which caused the object’s @Inject
-annotated fields, like Logger
for example, were initialized with null
value.
public class FooInterceptor implements MethodInterceptor{
@Inject
private Logger logger;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// NPE if logger not inject correctly
logger.info("start to invoke");
return invocation.proceed();
}
}
public class BarModule extends AbstractModule {
@Override
protected void configure() {
bindInterceptor(Matchers.subclassesOf(OrderApi.class),
Matchers.any(),
// new the interceptor in the plain old way
new FooInterceptor());
}
}
Solution
Guice wiki clearly states that requestInjection()
should be used for injection of a “method interceptor”.
How do I inject a method interceptor?
In order to inject dependencies in an AOP MethodInterceptor, use requestInjection() alongside the standard bindInterceptor() call.
public class NotOnWeekendsModule extends AbstractModule {
protected void configure() {
MethodInterceptor interceptor = new WeekendBlocker();
// for injection of a "method interceptor"
requestInjection(interceptor);
bindInterceptor(any(), annotatedWith(NotOnWeekends.class), interceptor);
}
}
Some Thoughts
Once you decide to use a dependency injection framework, like Guice here, please DO NOT new Java objects in the plain old way any more. Otherwise, you may very possibly fail to set up the objects’ dependencies correctly.
Secondly, use constructor-injection for mandatory dependency, like Logger
here.
It’s impossible to forget to inject a dependency using constructor-injection, even if that object is constructed in the plain old way.
(However, too many dependencies injected via the constructor makes the constructor look a bit ugly.)
删除Arrays.asList返回的列表的元素会发生异常
对Arrays.asList(...)
返回的List进行remove()
/removeAll()
操作时,会抛出UnsupportedOperationException
异常。
据Arrays.asList(...)
的javadoc,这个方法返回的List实现类是基于数组的。
Returns a fixed-size list backed by the specified array. (Changes to the returned list “write through” to the array.)
这个List
实现类是Arrays
类的一个私有静态类,所有方法基本上只是简单地代理到内部的一个数组成员E[]
。
数组是不支持删除操作的,所以remove()
会抛异常。
实际上对所有基于数组的List
实现类最好都不要进行删除操作。
ArrayList
虽然支持remove()
,但是remove()
的实现会导致内部数组的拷贝“平移”,影响效率。
Guava Cache异步刷新的一个实现
Guava的cache提供了refresh功能。 在指定的时间间隔后,guava可以(惰性地/lazily)更新缓存。 默认的refresh实现是同步的(synchronously),一个线程在更新缓存时,其它线程会等待。 具体见LoadingCache和CacheLoader的javadoc。
LoadingCache
void refresh(K key)
… Loading is asynchronous only if CacheLoader.reload(K, V) was overridden with an asynchronous implementation.
CacheLoader
public ListenableFuture<V> reload(K key, V oldValue) throws Exception
… This implementation synchronously delegates to load(K).
下面提供了一个异步的CacheLoader实现,使得一个线程在refresh缓存时,其它线程可以不必等待,继续使用旧的缓存值。
运行时发生IncompatibleClassChangeError异常
执行Java程序时遇到一个IncompatibleClassChangeError
异常。
IncompatibleClassChangeError的javadoc是这么说的,
Thrown when an incompatible class change has occurred to some class definition. The definition of some class, on which the currently executing method depends, has since changed.
意思是Java程序在执行时发现类的定义发生了变化。
通常,如果编译时依赖的类定义和运行时依赖的类定义不一致就有可能抛出这种异常。
比如,有如下程序,
public class ChildType extends BaseType {
}
在编译时,BaseType
是一个类,编译顺利通过。
在运行时,classpath里的BaseType
如果是一个接口,就会在运行时抛出IncompatibleClassChangeError
异常。
其它会抛出IncompatibleClassChangeError
异常的场景有,
implements
a class- 静态成员变成非静态成员
- 非静态成员变成了静态成员
我的Java程序抛出IncompatibleClassChangeError
异常的原因是运行时的classpath里同时存在不同版本Guava jar包。
在一个早期的Guava版本里,Ticker
是一个接口(且这个Ticker
定义更早地在运行时被加载);
在另一个较新的Guava版本里,Ticker
变成了一个类,并有其它代码extends Ticker
。
所以在运行到新版本的Guava代码时,就会抛出异常。
另外,升级库版本时,为了防止运行时抛出IncompatibleClassChangeError
异常,最好针对新的库重新编译程序
(这样编译时就能发现错误);
而不是简单替换库的jar包。
Java的赋值操作是原子的
Object object = someObject;
Java的写操作是原子的(atomic),所以上面的引用赋值操作是一个原子操作。
(一个例外,对long
和double
类型的写操作是非原子的;会分成两步32位的写操作。)
详见JLS:
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
操作是原子的,不代表操作的结果对于其它线程的可见性(visibility)。 可见性由“happens-before”来保证。
Java中的动态代理
Java的动态代理(Dynamic Proxy)支持在运行时动态地生存代理类/对象。
Map proxy = (Map) Proxy.newProxyInstance(
this.getClass().getClassLoader(), // 指定class loader
new Class[] { Map.class }, // 生产的代理类所要实现的接口
new MyInvocationHandler()); // InvocationHandler对象
生产的代理类在一个class loader里只有一个。
对代理对象的所有方法调用都会经过InvocationHandler
的invoke
方法。
在invoke
方法里可以做任何事情;最简单是把方法调用转发给目标对象。
public class MyInvocationHandler implements InvocationHandler {
// 可以将目标对象(target,真正干活的对象)放在构造函数里传入
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// @proxy 是代理对象, @method 是正在被调用的方法,
// @args 是方法调用的参数
}
}
Dynamic Proxy可以用来实现Spring Data的Repository。
在Spring Data中用户只需要定义Repository接口和接口里的方法,Spring Data会帮忙生成实现类。
假设在Repository接口里有一个findByUsername
方法,InvocationHandler.invoke
在
接收到方法调用时可以解析方法名,然后生成相应的数据库操作代码。
JVM的invokedynamic指令
Java Virtual Machine Support for Non-Java Languages
invokedynamic是Java 7时引入的新的JVM指令。 通过invokedynamic可以在运行时再决定要调用哪个方法。 有了invokedynamic,可以更方便地在JVM上实现类似Ruby的动态语言。
下面的两段话非常好地概括了invokedynamic的机制。
Each instance of an invokedynamic instruction is called a dynamic call site. A dynamic call site is originally in an unlinked state, which means that there is no method specified for the call site to invoke. As previously mentioned, a dynamic call site is linked to a method by means of a bootstrap method. A dynamic call site’s bootstrap method is a method specified by the compiler for the dynamically-typed language that is called once by the JVM to link the site. The object returned from the bootstrap method permanently determines the call site’s behavior.
The invokedynamic instruction contains a constant pool index (in the same format as for the other invoke instructions). This constant pool index references a
CONSTANT_InvokeDynamic
entry. This entry specifies the bootstrap method (aCONSTANT_MethodHandle
entry), the name of the dynamically linked method, and the argument types and return type of the call to the dynamically linked method.
invokedynamic指令出现的地方叫做“call site”(“调用点”)。 一开始call site处于“unlinked”状态,也就是还没有确定要调用哪个方法。 执行invokedynamic时,JVM会先去调用一个“bootstrap method”来确定要调用的方法。
invokedynamic指令的参数里包含了当前这条invokedynamic指令的“bootstrap method”信息(通过一个constant pool index“指向”bootstrap method)。
“bootstrap method”是普通的Java方法,有固定的参数类型和返回类型。
返回类型是CallSite
(CallSite
对象里有MethodHandle
,MethodHandle
实际上就对应于一个方法)。
下面就是一个“bootstrap method”的声明,
public static CallSite mybsm(
MethodHandles.Lookup callerClass, String dynMethodName, MethodType dynMethodType) {}
使用invokedynamic时,先定义好“bootstrap method”(可以多个invokedynamic指令共用一个“bootstrap method”,视情况而定)。 然后再构造相关的字节码,比如invokedynamic指令对应的字节码,constant pool相关的字节码等。 字节码可以通过ASM库来帮助构造,比如这个例子。
invokedynamic除了用来在JVM上实现动态语言,还用来实现Java 8里的Lambda。
更多详细信息见官网文档。