Java Generics, Bounded Wildcards and PECS

2019 Aug 8

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

2018 Sep 4

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返回的列表的元素会发生异常

2018 Feb 5

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异步刷新的一个实现

2018 Jan 16

Guava的cache提供了refresh功能。 在指定的时间间隔后,guava可以(惰性地/lazily)更新缓存。 默认的refresh实现是同步的(synchronously),一个线程在更新缓存时,其它线程会等待。 具体见LoadingCacheCacheLoader的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异常

2017 Nov 28

执行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的赋值操作是原子的

2017 Nov 23
  Object object = someObject;

Java的写操作是原子的(atomic),所以上面的引用赋值操作是一个原子操作。 (一个例外,对longdouble类型的写操作是非原子的;会分成两步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中的动态代理

2017 Nov 7

Java的动态代理(Dynamic Proxy)支持在运行时动态地生存代理类/对象。

Map proxy = (Map) Proxy.newProxyInstance(
  this.getClass().getClassLoader(), // 指定class loader
  new Class[] { Map.class }, // 生产的代理类所要实现的接口
  new MyInvocationHandler()); // InvocationHandler对象

生产的代理类在一个class loader里只有一个。 对代理对象的所有方法调用都会经过InvocationHandlerinvoke方法。 在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指令

2017 Mar 8

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 (a CONSTANT_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方法,有固定的参数类型和返回类型。 返回类型是CallSiteCallSite对象里有MethodHandleMethodHandle实际上就对应于一个方法)。 下面就是一个“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

更多详细信息见官网文档

API Design with Java 8

2017 Mar 5

如果返回值可能为null,那么使用Optional类型。 尽量不要把Optional做为接口参数,这会使得接口调用变得很笨拙

public Optional<String> getComment() {
    return Optional.ofNullable(comment);
}

不要使用数组做为返回值或者参数。 使用数组做为返回值时,为了防止调用者修改source数组,常常要先拷贝source数组再返回,效率不高(比如Enum::values())。 使用数组做为参数时,其它线程可能会同时在修改数组。 如果需要强调返回的集合是不可修改时,可以考虑使用Stream做为返回类型。

public Stream<String> comments() {
    return Stream.of(comments);
}

可以考虑增加静态的接口方法来创建接口的实现类。 相比Point point = new PointImpl(1,2);而言,Point point = Point.of(1,2);隐藏了具体的实现类。

考虑使用lambda,而不是继承,来个性化对象的行为。

//不要这样
Reader reader = new AbstractReader() {
    @Override
    public void handleError(IOException ioe) {
        ioe. printStackTrace();
    }
};
//better to expose a static method or a builder in the Reader interface that takes a Consumer<IOException> and applies it to an internal generic ReaderImpl
Reader reader = Reader.builder()
    .withErrorHandler(IOException::printStackTrace)
    .build();

给Functional Interface加上@FunctionalInterface,保证接口只有一个抽象方法。

如果接口要求参数不为null,考虑使用Objects.requireNonNull()做参数验证,可以提前抛出异常。 不要过分考虑带来的性能损失,JVM会优化掉不必要的检查。

public void addToSegment(Segment segment, Point point) {
    Objects.requireNonNull(segment); //可以更早地抛出异常
    Objects.requireNonNull(point);
    segment.add(point);
}

更多详见原文。

Spock,又一种Java测试框架

2017 Mar 3

Spock是用Groovy语言实现的测试框架,可以测试Groovy代码和Java代码。

比JUnit简洁,

class Math extends Specification {
    def "maximum of two numbers"(int a, int b, int c) {
        expect:
        Math.max(a, b) == c

        where:
        a | b | c
        1 | 3 | 3   //passes
        7 | 4 | 4   //fails
        0 | 0 | 0   //passes
    }
}

原生地支持Mocking和Stubbing,不再依赖Mockito之类的mock框架,

def "should send messages to all subscribers"() {
    when:
    publisher.send("hello")

    then:
    1 * subscriber.receive("hello") //subsriber should call receive with "hello" once.
    1 * subscriber2.receive("hello")
}
subscriber.receive(_) >>> ["ok", "error", "error", "ok"]

支持BDD(Behavioral Driven Development),

given: //data initialization goes here (includes creating mocks)
when: //invoke your test subject here and assign it to a variable
then: //assert data here
cleanup: //optional
where: //optional:provide parametrized data (tables or pipes) 

更好的错误提示,

maximum of two numbers   FAILED

Condition not satisfied:

Math.max(a, b) == c
	|    |  |  |  |
	|    7  0  |  7
	42         false

Spock使用了JUnit的runner infrastructure,所以也支持code coverage等报告。

其它参考:

An introduction to Spock
So Why is Spock Such a Big Deal?
Comparing Spock and Junit

← Previous