Eclipse で Null 解析しつつ guava を使いたい

@yuichielectric さんが[こちら](http://qiita.com/items/8dd08da5058a64335090)で書かれていますが, 最近の Eclipse では静的な Null チェックが出来るみたいです。これは素晴らしいと思ったので早速試してみたのですが, guava と組み合わせて使おうとすると少々問題が発生することに気付きます……。

## セットアップ

まず [@yuichielectric さんの記事](http://qiita.com/items/8dd08da5058a64335090)を参考に Eclipse (Juno) のセットアップを行います。次に, ```javax.annotation.Nullable``` が必要なので JSR305 を依存関係に追加します。

```groovy
// build.gradle
dependencies {
  compile com.google.code.findbugs:jsr305:2.0.1
  compile com.google.guava:guava:14.0.1
}
```

これで準備は完了です。きちんと静的解析が効いていることが分かると思います（詳細は @yuichielectric さんの[記事](http://qiita.com/items/8dd08da5058a64335090)をご覧ください……）。

## guava を使ってみる

ここ数年, Java を使って開発する場合は [guava](https://code.google.com/p/guava-libraries/) を使うことが多いと思います。guava は ```javax.annotation.Nullable``` 等を使ってアノテートされているので, Eclipse の Null 解析対象を ```javax.annotation.Nullable``` にすれば ```Function``` 等を利用する際に静的に Null チェックが出来るはずです。早速使ってみましょう。

```java
public class Main {

    public static void main(String[] args) {

        List<Integer> list = Lists.newArrayList(1, 2, 3, null, 5);
        System.out.println(Joiner.on(", ").join(Collections2.transform(list, new Function<Integer, Integer>() {
            @Override
            public Integer apply(@Nullable Integer input) {
                return input == null ? -1 : input * 2;
            }
        })));

    }

}
```

ところがこれでは次のようなコンパイルエラーが発生してしまいます……。

```
The method equals(Object) from Object cannot implement the corresponding method from Function<Integer,Integer> due to incompatible nullness constraints
```

Guava の ```Function``` は ```equals``` を以下のようにオーバーライドしています。

```java
boolean equals(@Nullable Object object);
```

```equals``` の引数が ```@Nullable``` 指定されているので, 匿名クラスの定義と競合してしまうのですね。このエラーを回避しようと思うとこんな感じになってしまいます……。

```java
public class Main {

    public static void main(String[] args) {

        List<Integer> list = Lists.newArrayList(1, 2, 3, null, 5);
        System.out.println(Joiner.on(", ").join(Collections2.transform(list, new Function<Integer, Integer>() {
            /** {@inheritDoc} */
            @Override
            public Integer apply(@Nullable Integer input) {
                return input == null ? null : input * 2;
            }
            /** {@inheritDoc} */
            @Override
            public boolean equals(@Nullable Object obj) {
                return super.equals(obj);
            }
        })));

    }

}
```

だ、ダサい……。更に今度は Eclipse が「equals をオーバーライドしたのに hashCode をオーバーライドしてないよ？」という警告を出力してきます。関数をその場で作りたいだけなのに, ```apply``` / ```equals``` / ```hashCode``` の実装が必要になってしまいました……。

## どうすれば？

この問題は既に [guava](https://code.google.com/p/guava-libraries/issues/detail?id=1278) でも [Eclipse](https://bugs.eclipse.org/bugs/show_bug.cgi?id=385757) でも認識されているようですが, 特に結論は出ていないみたいです。自分の場合, 流石にここまで冗長な記法は受け入れ難い & 取り敢えずアプリケーションレイヤだけでもチェックしたいので, サードパーティライブラリを利用する部分の静的解析は捨てて, アプリケーションレイヤで別の ```@Nullable``` / ```@Nonnull``` アノテーションを定義して利用することにしました。ただ, これはあくまで妥協案なので, 暫く利用してみて問題があったりより良い方法が見つかったら別の方法にしようと思っています（この機能自体を今日知ったところなので……）。誰か良い解決策をご存知の方がいらっしゃれば教えて頂きたいところです。

ちなみに自分の場合, ```Function``` は大抵ラップしてしまうので, こんな感じでアプリケーション用の ```Function``` を定義して使うことにしました。

```java
// ココの @Nullable と @Nonnull はアプリケーションで定義した独自型（javax.annotation ではない）
public interface ExceptionalFunction<S, T, E extends Exception> {

    @Nullable
    T apply(@Nullable S input) throws E;

}

public interface Function<S, T> extends ExceptionalFunction<S, T, RuntimeException>, com.google.common.base.Function<S, T> {

    @Override
    @Nullable
    T apply(@Nullable S input);

}
```

クライアントはこんな感じで書けますね（以下の実装例は適当です）。例えば ```Option``` 型を導入したいときって結局 ```Option``` の参照自体に ```null``` が入る可能性があってイマイチだなーと思うのですが, この機能を使えば ```Option``` の参照自体を ```@Nonnull``` で注釈出来るのが嬉しいですね。

```java
public class Main {

    public static void main(String[] args) {

        @Nonnull List<Integer> list = Lists.newArrayList(1, 2, 3, null, 5);
        Option<Integer> found = Collections3.find(list, new Predicate<Integer>() {
            @Override
            public boolean apply(@Nullable Integer input) {
                return input == null ? false : input == 3;
            }
        });
        found.get(3); // Nonnull !

    }

}

public final class Collections3 {

    /**
     * コレクションから条件を満たす値を取得します。
     * <p>
     * 複数の値が見つかった場合, 先頭の要素が返ります。
     * 
     * @param <E> 値の型
     * @param collection コレクション
     * @param predicate 条件
     * @return 値
     */
    @Nonnull
    public static <E> Option<E> find(@Nonnull Collection<E> collection, @Nonnull Predicate<E> predicate) {
        Collection<E> filtered = Collections2.filter(collection, predicate);
        return Option.apply(filtered.isEmpty() ? null : filtered.iterator().next());
    }

    private Collections3() {
    }

}

public abstract class Option<T> implements Serializable {

    @Nonnull
    public static <T> Option<T> apply(@Nullable T value) {
        return value == null ? Option.<T> none() : some(value);
    }

    @Nonnull
    public static <T> Option<T> some(@Nonnull T value) {
        return new Some<>(value);
    }

    @Nonnull
    public static <T> Option<T> none() {
        return new None<>();
    }

    public abstract boolean isDefined();

    @Nonnull
    public abstract <D> Option<D> map(@Nonnull Function<T, D> f);

    @Nonnull
    public abstract <D> Option<D> bind(@Nonnull Function<T, Option<D>> f);

    @Nullable
    public abstract T getOrNull();

    @Nonnull
    public T getOrElse(@Nonnull T defaultValue) {
        T value = getOrNull();
        return value == null ? defaultValue : value;
    }

    public static class Some<T> extends Option<T> implements Serializable {

        @Nonnull
        private final T value;

        @Override
        public boolean isDefined() {
            return true;
        }

        @Override
        @Nonnull
        public <D> Option<D> map(@Nonnull Function<T, D> f) {
            return Option.apply(f.apply(value));
        }

        @Override
        @Nonnull
        public <D> Option<D> bind(@Nonnull Function<T, Option<D>> f) {
            Option<D> option = f.apply(value);
            return option == null ? Option.<D> none() : option;
        }

        @Override
        @Nullable
        public T getOrNull() {
            return value;
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Some) {
                return Objects.equal(value, ((Some<T>) obj).value);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }

        private Some(@Nonnull T value) {
            this.value = value;
        }

    }

    public static class None<T> extends Option<T> implements Serializable {

        @Override
        public boolean isDefined() {
            return false;
        }

        @Override
        @Nonnull
        public <D> Option<D> map(@Nonnull Function<T, D> f) {
            return none();
        }

        @Override
        @Nonnull
        public <D> Option<D> bind(@Nonnull Function<T, Option<D>> f) {
            return none();
        }

        @Override
        @Nullable
        public T getOrNull() {
            return null;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof None) {
                return true;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return None.class.hashCode();
        }

        private None() {
        }

    }

    private Option() {
    }

}
```

Eclipse 4.3 になったら改善されてるのかなーと期待しつつ……。今まで特にこの手の機能は使っていなかったので、より良い方法があれば是非是非教えて頂きたいです。

---

### 追記

ちょっと使ってみました。幾つか問題がありますがそのうちのひとつが **フィールドに付加した ```@Nonnull``` アノテーションは処理されない** ということです。なので例えば ```@Nonull``` のプロパティを返す Getter を、同じく ```@Nonnull``` として定義するとエラーになってしまいます。これは Eclipse 4.3 で修正されるそうです。次に、アノテーションが付加されていないサードパーティライブラリの取り扱いについてですが、これは現状どうしようもなく Eclipse 4.2 では取り敢えず暗黙の型変換（```@Nullable``` → ```@Nonnull```）については警告に留めるしかなさそうです。他の問題は後々……。