2016/02/27

Android: Dagger2 - Subcomponent vs. dependencies

はじめに

Dagger2(google/dagger)でComponentの関連性を指定する@Subcomponentdependenciesについてまとめる.

Dagger2では依存オブジェクト群を”Component”と呼ばれる単位で管理する.
このComponentには他Componentと従属関係を築く方法と, 他Componentと使用関係を築く方法の2種類が用意されている.
さらにDagger2では”Scope”の概念も加わり, このあたりの仕様理解を難しくしている.

Subcomponentやdependenciesを使わなくてもDagger2はDI Frameworkとして十分役に立つ.
ただ, Subcomponentとdependenciesの理解はDagger2の依存性充足の仕組みを理解するのに大いに役立つため, 知っておくことをお勧めする.

NOTE:
本稿はDagger2 Ver.2.0.2をベースに作成している.
Dagger2 Ver.2.1からはややこしい Subcomponent周りにも改良が加えられる様子(@Component.Builder新設etc.)がある.
そのため, 本稿ではSubcomponentとdependenciesの基本に焦点を絞り, その他Tipsなどは非推奨化が見えているのでここでは触れないことにする.

基本的な構成

本稿における用語定義も兼ねてDagger2の基本をおさらいする.

依存性の充足
ClassAがClassBに依存する場合, Dagger2はClassAにClassBのオブジェクトを注入(DI)する. 本稿ではこういった依存性を解決することを依存性の充足(あるいは単に充足)と表現する.
依存性の要求
Dagger2では@Injectアノテーションで依存性を宣言し, これにより依存オブジェクトをDagger2へ要求することができる. あるいはコンストラクタに@InjectをつけることでそのオブジェクトをDagger2に管理・生成させることができる.
Component graph
Dagger2は依存オブジェクト群をComponent単位で管理する. この時に出来上がる依存オブジェクトのコレクションを本稿ではComponent graph(あるいは単にgraph)と表現する. Dagger1(square/dagger)ではObject graphと呼ばれていたものに相当する.
依存オブジェクト
Dagger2に管理される依存関係の対象となるオブジェクト. 他オブジェクトに必要とされているオブジェクトであったり, 他オブジェクトを要求するオブジェクトであったりする.
Module
依存オブジェクトのファクトリにあたるクラス. 依存性の要求ができないライブラリやシステムにライフサイクル制御されているクラス(Activity 等), 生成に構築作業が必要なオブジェクトの場合はModuleでファクトリメソッドを記述する.
Scope
Dagger2では依存オブジェクトとComponent graphのライフサイクルを決めるアノテーションとしてScopeの概念を持つ. Dagger2では依存性を充足させる 度に依存オブジェクトを生成するのか, あるいはComponent graph内で一度生成したインスタンスを使い回すのかをScopeで制御できる仕組みを持つ .
Component
Component graphの単位. また, Componentは依存オブジェクトとDagger2, あるいは他Componentとの橋渡しの役割を持つ.

ComponentとScope

Dagger2の依存性の充足を理解する上で重要な要素はComponentだ.
Dagger2はPluggable Annotation Processing(JSR 269/APT)の仕組みを使ってDIコンテナにあたるファクトリ群のコードを自動生成する.
このコード生成に大きく関わってくるのがComponentだ. つまり, Componentの書き方がDagger2の挙動に深く関わってくる.

細かな点は除いて, Component graphの構造を下記のようにイメージすると理解が早い.

私たちが定義するComponentはDagger~から始まるComponentの具象クラスを生成する設計図になる.
Dagger~インスタンスこそがComponent graphの正体であり, 依存オブジェクトを保持するインスタンスである.
私たちは, このインスタンスから依存オブジェクトを取得したり, @Injectの依存性の要求を充足させたりするのに使うわけだ.

Component graphはScopeを持ち, Scopeは依存オブジェクトを持つ.
Dagger2ではComponentがScopeを持つとScopedProviderというクラスによって, Scope毎のファクトリが作られる.
難しいことはさておき, 依存オブジェクト群はScopeによって管理されているということだ.

ただ, これだけを見るとScopeの価値がわかり辛い. Scopeをやめて直接Componentが依存オブジェクトを管理しても良いように思える.
そこでSubcomponentとdependenciesの登場だ.

Subcomponent

ComponentやScopeは階層化できる.
例えば, 親のComponentを作成し, それの子にあたるComponentを作ることも可能だ.
子にあたるComponentには@Subcomponentのアノテーションをつけて宣言する.

@Subcomponent(...)
public interface ChildComponent {

親にあたるComponentは子Componentを作成するAbstract factory methodを宣言する.

@Component(...)
public interface ParentComponent {
    ChildComponent newChildComponent(HogeModule hoge);

親Componentは子Componentを明示的に宣言し, 子Componentは自身が子であることを宣言する.
この構造は面白い. 通常の継承関係は子が親を指定するのに対してDagger2では親が子を指定する.

上記のComponentからどのようなコードが生成されるか見てみよう.
実は, SubComponentにあたるComponentのクラスファイル(.java)は生成されない.
その理由はSubComponentが親Componentの内部クラスとして宣言されるからだ.

// クラス名はそのままComponent間の関係性(Parent-Child)を表している
public final class DaggerParentComponent implements ParentComponent {
    ...
    @Override
    public ChildComponent newChildComponent(HogeModule hoge) {  
        return new ChildComponentImpl(hoge);
    }
    ...

    private final class ChildComponentImpl implements ChildComponent {
        ...
    }
}

つまり, 親Componentと子Componentの関係は, 親Componentが子Componentのエンクロージングインスタンスにあたる関係に等しい.
そして, 子Componentは非staticな内部クラスで宣言されていることから, エンクロージングインスタンスにあたる”親”への参照を保持しており, またそのライフサイクルも親のものより短くなる.

親, 子それぞれのComponentが持つ依存オブジェクトへの参照範囲について.
Dagger2のComponentはComponent graphの単位. つまりファクトリ群の単位である.
親Component, 子Componentを使うクライアント側のコードを見てみよう.

// 親ComponentのComponent graphを生成
parentComponent = DaggerParentComponent.builder()
        ...
        .build();
parentComponent.inject(this);

上記は親Componentを使って依存性を充足させる例である.
これによって充足される依存性は親Componentに属している依存オブジェクトのものに限られる.
これは, 生成されたコードからもわかる通り, 親Componentが子Componentへの参照を保持していないからだ.

次に子Componentの例を見てみる.

// 親ComponentのComponent graphから子Componentのgraphを生成
childComponent = ((MyApp) getApplication()).getParentComponent()
    .newChildComponent(new ScreenModule(this));
childComponent.inject(this);

これによって親Componentと子Componentに属している依存オブジェクト双方から依存性が充足される.
子Componentはエンクロージングインスタンスである親Componentへの参照を持っているからだ.

SubComponentによる親, 子それぞれの関係を整理する.


- 親Component 子Component
ライフサイクル 長い 短い
参照範囲 狭い(子を含まない) 広い(親を含む)

Scopeの階層化

SubComponent化する際のルールとしてScopeは異なるものでないといけない.
親と子で同じScopeを持っていては, どちらのComponentに属する依存オブジェクトであるか区別がつかないからだ.

“Scope”という単語からは, 参照範囲を決定する力を連想するが, 実際にそれを決めるのは前述の通り親Componentと子Componentのエンクロージングな関係によるものだ.
SubComponent化された状況において, Scopeは”どちらのComponentに属するものか”を問うているにすぎない.

// 親Component. スコープにはParentScopeを指定.
@ParentScope @Component(...)
public interface ParentComponent { ... }

// 子Component. スコープにはChildScopeを指定.
@ChildScope @Subcomponent(...)
public interface ChildComponent { ... }

// この状況で, @ParentScopeなオブジェクトはParentComponentに属する.
@ParentScope
public class ParentScopeClass {
  @Inject
  public ParentScopeClass() {...}
}

// この状況で, @ChildScopeなオブジェクトはChildComponentに属する.
@ChildScope
public class ChildScopeClass {
  @Inject
  public ChildScopeClass() {...}
}

SubComponentまとめ

  • SubComponentは親Componentと子Componentに強い結合をもたらす.
  • 親は子Componentを生成するAbstract factory methodを宣言する.
  • 子は@Subcomponentでアノテートする.
  • 親Componentが子Componentのエンクロージングインスタンスとして関係を持つ.
  • 親は子Componentの依存オブジェクトを参照した充足ができない.
  • 子は親Componentの依存オブジェクトを参照した充足ができる.
  • Scopeは”どちらのComponentに属するか”を決定する要素になる.

例えば, アプリのライフサイクルに合わせて生存するDatabaseのような依存オブジェクトは親Componentに割り当てる.
Activityのような複数個/複数回生成されるクラスに紐づく依存オブジェクトは子Componentとして定義するような分け方もできる.
あるいはユーザログイン/ログアウトといった特定の区間だけ生存する依存オブジェクトもこれで実現できる.
子Componentのライフサイクルを開始したければ親Componentが宣言したAbstract factory methodを呼べばよい. 子Componentのライフサイクルを終了したければ子Componentのインスタンス(SubComponent graph)を破棄すればよい.

dependencies

SubComponentをおさえたところで, Dagger2にはこれを混乱させる要素がもう1つある. dependenciesパラメータだ.

Componentアノテーションはパラメータにdependenciesを指定することができる.
これにより, Componentは他Componentに依存することができる.
この概念はSubComponentの概念と似通っているものの内部構造は大きく異なる.

// @Subcomponentアノテーションではない点に注意
@Component(dependencies = DependeeComponent.class, ...)
public interface DependerComponent {

dependenciesによる依存の要点は下記である.

  1. dependenciesで指定されたComponentを使って依存性を充足させることができる
  2. ただし, その場合はdependenciesで指定された側が依存性をexportする必要がある

dependenciesを宣言して依存する側(dependerComponent)と, dependenciesの宣言で指定された依存される側(dependeeComponent)について.
dependerComponentはdependeeComponentの依存オブジェクトを使って自身が持つComponent graphの依存性を充足させることができる.

// 依存する側
@Component(dependencies = DependeeComponent.class, ...)
public interface DependerComponent { ... }

// 依存される側
@Component(modules = {HogeModule.class})
public interface DependeeComponent { ... }

これによってDependerComponentはDependeeComponentを使って依存性を充足させる.
SubComponentの例をみると, DependerComponentがChildComponent, DependeeComponentがParentComponentのように見えるが, 関係性を指定する方向が逆転しているのがわかる.

生成されるコードに目をやると, SubComponentの時とは違って, DependerComponentのクラスファイル(.java)が生成され, DaggerDependerComponentクラスが出来上がる.

その中身は単純で, DependeeComponentに直接アクセスしてDependeeComponentがもつ依存オブジェクトを取得することで依存性充足を実現していることがわかる.

public final class DaggerDependerComponent implements DependerComponent {
  // DependeeComponentが持つ依存オブジェクト
  private Provider<ParentScopeClass> parentScopeClassProvider;
  ...
  private void initialize(final Builder builder) {  
    this.parentScopeClassProvider = new Factory<ParentScopeClass>() {
      private final DependeeComponent dependeeComponent = builder.dependeeComponent;
      @Override public ParentScopeClass get() {
        // DependeeComponentの@Provideメソッド経由で依存オブジェクト取得
        ParentScopeClass provided = dependeeComponent.parentScopeClass();
        ...
      }
    ...
  }
  ...

上記コードからわかるように, DependerComponentがDependeeComponentの依存オブジェクトを参照できるようにするためには, DependeeComponent側に依存オブジェクトを参照できるExportメソッドを定義する必要がある.
(上記の例ではparentScopeClassメソッドがそれにあたる)

// 依存する側
@Component(dependencies = DependeeComponent.class, ...)
public interface DependerComponent { ... }

// 依存される側
@Component(modules = {HogeModule.class})
public interface DependeeComponent {
  // DependerComponentが要求するParentScopeオブジェクトをexportする必要がある
  ParentScopeClass parentScopeClass();
}

これらのComponentを使うクライアント側のコードを見てみよう.

// dependeeComponentのComponent graphを生成
dependeeComponent = DaggerDependeeComponent.builder()
        .databaseModule(new DatabaseModule(this))
        .build();
dependeeComponent.inject(this);

// dependerComponentのComponent graphを生成
dependerComponent = DaggerDependerComponent.builder()
        .dependeeComponent(((MyApp) getApplication()).getDependeeComponent())
        .screenModule(new ScreenModule(this))
        .build();
dependerComponent.inject(this);

dependenciesはSubComponentとは異なり, Compnentが別のComponentを生成するということはない.
depencenciesを宣言する側, つまり依存を要求する側のdependerComponentが必要な依存性を持ったdependeeComponentを自身のComponent graph生成時に組み込むといった形で充足を実現するわけだ.

dependenciesとScope

dependenciesによるComponent間の依存関係においてもScopeは異なるものにしなければならない.
その理由はSubComponentの場合と同じである.

SubComponent vs. dependencies

本題のSubComponentかdependencies, どちらを選択するべきかについて.

SubComponentは親Componentが子Componentを指定・生成し, 子Componentは@Subcomonentで明示的に宣言される.
一方で, dependenciesはSubComponentとは関係性の指定方向が逆転しており, 依存する側のComponentがdependenciesパラメータで依存先を宣言する. 依存先のComponentは必要な依存オブジェクトをExportするメソッドを宣言する.

dependenciesは依存される側のComponentで定義されるexportメソッドにより暗黙的な結合が生まれている.
一方でこれは関係するComponentを直接指定するSubComponentに比べて緩い結合である.
dependenciesは他Componentの依存性充足を手軽に拝借できるところが利点ともいえる.
ただ, “静的なファクトリさ”を持つDagger2の特性からみても, この”機敏さ”にはさほど魅力を感じられない.

ファクトリコードは複雑化しやすい. そのため, 多少の面倒さはあっても将来ファクトリが複雑化する可能性があるのであれば, Componentの関係性を明確に定義するSubComponentを使いたい.

補足

Dagger2ではScopeの定義が必須ではない. Scopeでアノテートしない場合, インスタンスはScopedProviderではなくModuleのファクトリで管理される.
本稿ではSubComponentとdependenciesに焦点を絞り, 説明をわかりやすくするためにあえてScopeを宣言しないケースについては除外した.
SubComponentやdependenciesでScopeを宣言しないProviderを作成した場合, 通常通りそのComponent/Moduleにファクトリがつく.

本稿で使用したコードはこちらのGitHubにアップしています.

以上.

2016/02/23

Android: よく使う静的解析ツール他のGradle定義

Androidプロジェクトで頻繁に使用されるプラグインの導入ソースプログラムをプロジェクト作成の都度書き直すのは非効率的であるため,
そういった繰り返し書かれるプログラムを下記にまとめ, 新規プロジェクト作成の際にはこれを適用することで対応できるようにする.

GitHub-AndroidProjectTemplate

導入されるプラグイン

主要な静的解析ツールと各種ユーティリティが導入される.
プラグインの導入ソースコード(gralde)は/android.gradleで定義されている.
これに含まれるプラグインは次の通り.

FindBugs
Javaプログラム(バイトコード)の静的解析ツール.
PMD
Javaプログラム(ソースコード)の静的解析ツール.
CheckStyle
プログラムの体裁チェックツール.
Lint
Androidに特化した潜在的な不具合を検出する静的解析ツール.
Jacoco
Javaプログラムのカバレッジレポート.
DexCount
APKが持つメソッド数を報告するプラグイン.
ApkSize
APKのバイナリサイズを報告するプラグイン.
GradleVersion
依存しているライブラリの最新バージョンをチェックするプラグイン.

ライブラリ

  • RxAndroid
  • RxJava
  • Dagger2
  • ButterKnife
  • Orma
  • Timber
  • Stetho
  • Okhttp
  • Robolectric
  • JUnit
  • Mockito
  • Hamcrest
  • Android support Appcompat-v7
  • Android support annotations
  • Android design support lib.
  • Android support testing lib.

android.gradleの適用

android.gradleをアプリケーションに適用するには2ステップ必要.
まずプロジェクトルートで下記を宣言する.

buildscript {
    dependencies {
        ...

        // dex method count
        classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.4.1'

        // apk size
        classpath 'com.vanniktech:gradle-android-apk-size-plugin:0.2.0'

        // check for plugin updates
        classpath 'com.github.ben-manes:gradle-versions-plugin:0.11.3'
    }
}

次に, 各モジュールのbuild.gradleandroid.gradleプラグインを読み込む.

apply from: rootProject.file('android.gradle')

追加・変更されるタスク

それぞれのタスクは各バリアント毎に用意される.

/*
 * 全ビルドバリアントに共通のタスクを設定する
 */
(android.hasProperty('applicationVariants')
        ? android.'applicationVariants'
        : android.'libraryVariants').all { variant ->

また, 各静的解析ツールはデフォルトで/configに格納されたコンフィギュレーションファイルを読み込む.

findbugs{variantName}
FindBugsによる静的解析を指定のビルドバリアントに対して実行する
pmd{variantName}
PMDによる静的解析を指定のビルドバリアントに対して実行する
checkstyle{variantName}
CheckStyleによる体裁チェックを指定のビルドバリアントに対して実行する
lint{variantName}
Lintによる静的解析を指定のビルドバリアントに対して実行する
jacoco{variantName}Report
Jacocoによるカバレッジレポートを指定のビルドバリアントに対して実行する
count{variantName}Methods
DexCountによるメソッド数の計測を指定のビルドバリアントに対して実行する
size{variantName}
ApkSizeによるAPKサイズの計測を指定のビルドバリアントに対して実行する
dependencyUpdates
依存しているライブラリの最新バージョンチェックを実行する
check
デバッガブルなビルドタイプの場合, いくつかの静的解析チェックを追加で行う
pullCodeStyleSettings
AndroidStudioに適用されるコードスタイル設定ファイルをダウンロードする
checkEnvironmentSettings
開発環境の設定確認用タスク

FindBugs

FindBugsの設定ファイルは/config/findbugs.xmlに格納される.

/*
 * Findbugs
 *   see: https://docs.gradle.org/current/dsl/org.gradle.api.plugins.quality.FindBugsExtension.html
 */
task("findbugs$variantName", type: FindBugs,
        dependsOn: "assemble$variantName") {
    group 'Reporting'
    description "Generate ${variantName} Findbugs reports."

    ignoreFailures = true
    reports {
        xml.enabled = false
        html.enabled = true
    }

    effort = 'max'
    reportLevel = 'low'
    source = files(android.sourceSets.main.java.srcDirs)
    classes = fileTree(dir: variant.javaCompiler.destinationDir,
            excludes: autoGenerated)
    classpath = files(configurations.compile.files)
    excludeFilter = rootProject.file('config/findbugs.xml')
}

PMD

PMDの設定ファイルは/config/pmd.xmlに格納される.

/*
 * PMD
 *   see: https://docs.gradle.org/current/dsl/org.gradle.api.plugins.quality.PmdExtension.html
 */
task("pmd$variantName", type: Pmd, dependsOn: "assemble$variantName") {
    group 'Reporting'
    description "Generate ${variantName} Pmd reports."

    ignoreFailures = true
    reports {
        xml.enabled = true
        html.enabled = true
    }

    ruleSetFiles = files("${rootProject.rootDir}/config/pmd.xml")
    ruleSets = []
    source = files(variant.javaCompiler.source)
    classpath = files(configurations.compile.files)
}

CheckStyle

CheckStyleの設定ファイルは/config/checkstyle-*.xmlに格納される.
checkstyle-easy.xmlは緩い体裁チェックルール. checkstyle-hard.xmlは厳しい体裁チェックルールとなっている.
プロジェクトのコーディング規約にあったファイルをandroid.gradleで指定する.

/*
 * CheckStyle
 *   see: https://docs.gradle.org/current/dsl/org.gradle.api.plugins.quality.Checkstyle.html
 */
task("checkstyle$variantName", type: Checkstyle,
        dependsOn: "assemble$variantName") {
    group 'Reporting'
    description "Generate ${variantName} Checkstyle reports."

    ignoreFailures = true
    reports {
        xml.enabled = true
        html.enabled = true
    }

    showViolations true  // CheckStyle解析結果をStdOutに出力する
    configFile = rootProject.file('config/checkstyle-easy.xml')
    source = files(android.sourceSets.main.java.srcDirs)
    classpath = files(configurations.compile.files)
}

Lint

Lintの設定ファイルは/config/lint.xmlに格納される.

// Lintの設定
//   Lint設定ファイルはプロジェクトルートのconfigフォルダに配置すること.
//   see: http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.LintOptions.html
lintOptions {
    lintConfig rootProject.file('config/lint.xml') // Lintチェックの無効化設定.
    textReport true
    textOutput 'stdout'  // StdOutにLint結果を出力する
    htmlReport true
    htmlOutput file("${buildDir}/reports/lint/lint_result.html")
    xmlReport false
    xmlOutput file("${buildDir}/reports/lint/lint_result.xml")
    checkAllWarnings true
    checkReleaseBuilds true
    warningsAsErrors true  // Warnレベルの警告をErrorと同様に扱う
    abortOnError true      // Errorが見つかった場合にビルドを失敗させる
}

Jacoco

/*
 * Jacoco
 *   see: https://docs.gradle.org/current/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.html
 */
task("jacoco${variantName}Report", type: JacocoReport,
        dependsOn: "test${variantName}UnitTest") {
    group 'Reporting'
    description "Generate ${variantName} Jacoco coverage reports."

    reports {
        xml.enabled = true
        html.enabled = true
    }

    sourceDirectories = files(android.sourceSets.main.java.srcDirs)
    executionData =
            files("${buildDir}/jacoco/test${variantName}UnitTest.exec")
    classDirectories = fileTree(dir: variant.javaCompiler.destinationDir,
            excludes: autoGenerated)
}

コンフィギュレーション

Release署名

APKのDebug/Release署名設定もandroid.gradleで定義されている.
Release署名で使用されるキーストア情報は/secretに格納されているrelease.gradleにある(secretフォルダについては後述).
android.gradle/secret/release.gradleを参照し, これを適用する.
もしrelease.gradleが見つからない場合はDebug署名の内容がRelease署名として流用される.

def releaseSettingGradleFile = rootProject.file('secret/release.gradle')
if (releaseSettingGradleFile.exists()) {
    apply from: releaseSettingGradleFile, to: android
} else {
    println "\n\t!! NOT FOUND RELEASE KEYSTORE SETTING. SIGNING DEBUG KEYSTORE !!\n"
    release {
        storeFile = debug.storeFile
        storePassword = debug.storePassword
        keyAlias = debug.keyAlias
        keyPassword = debug.keyPassword
    }
}

Debug署名はIDE標準で用意されるdebug.keystoreをプロジェクトルートに配置することで利用できる.

コードスタイル設定

AndroidStudioで使用するコードスタイル設定が/.idea/codeStyleSettings.xmlに定義されている.
android.gradlepullCodeStyleSettingsタスクを実行することで下記のシェルスクリプトが実行され, AndroidStudioのコードスタイル設定が更新される.

curl -L "https://raw.githubusercontent.com/YukiMatsumura/AndroidProjectTemplate/master/.idea/codeStyleSettings.xml" > .idea/codeStyleSettings.xml

コードスタイルを適用するにはIDEを再起動すること.

Checkタスク

FindBugs, PMD, CheckStyle, Jacocoはビルドバリアント毎に定義されたタスクを持つ.
例) findbugsDevDebug
android.gradleではDebuggableなビルドタイプに限定してこれらのタスクをCheckタスクに依存させている.
(ビルドタイプの限定を解除する場合はCIサービスでメモリ使用量が増えるため事前に確認が必要)

if (variant.buildType.debuggable) {
    check.dependsOn "pmd${variantName}"
    check.dependsOn "findbugs${variantName}"
    check.dependsOn "checkstyle${variantName}"
    check.dependsOn "jacoco${variantName}Report"
}

その他

android.gradleで定義されるcheckEnvironmentSettingsタスクはプロジェクトに必要な環境をチェックするためのタスク.
標準でJDKのバージョンチェックを実施する.

task checkEnvironmentSettings() {
    group 'Verification'
    description "Check environment settings"

    // Ormaはaptによるコード生成にJava1.8を要求する
    if (JavaVersion.current() < JavaVersion.VERSION_1_8) {
        println("\n\tYou will need Java 1.8 or higher if you use Orma.")
        println("\tCurrent Java version is really old. Found ver. " + JavaVersion.current() + ".\n")
        throw new GradleException("Please Update your Java.")
    }
}
2016/02/06

Android: NonNullアノテーション

Android Studio 0.5.5から@NonNullアノテーションがサポートされた.
今回はメソッドの引数に@NonNullアノテーションをつけるケースについて書いた.

@NonNull

@NonNullアノテーションはフィールドやメソッドの引数, メソッドのReturn値にNullを許容しないことを表明するアノテーションである.
このアノテーションが便利な点は, @NonNullアノテーションにNullを代入したり, 戻り値のNullチェックをしたりする場合に, IDEが@NonNullの制約をCode Inspectionで示してくれるところだ.

ただし, これはIDE付属のCode Inspection設定で報告レベルを調整可能でもあるし, @NonNullの値にNullを設定したからといってシンタックスエラーにはならない.
@NonNullはドキュメンテーションや支援機能であって, Nullを防ぐものではない点に注意が必要である.

下記のコードはhogeメソッドの引数sにNullを渡す恐れがある. しかし, これはCode Inspectionとして報告されない.

private String piyo;

public void foo() {
    hoge(piyo);
}

public void hoge(@NonNull String s) {
}

@NonNullはメソッドの呼び出し元でNullが渡らないことを保証する必要がある.
Code Inspectionは@NonNullフィールド, メソッド引数, 戻り値の非Null性を保証するものではないからだ.

@NonNullの使いどころ

メソッドが引数にNullを許容せず, NullPointerExceptionをスローするケースは少なくない.
そういう時こそ@NonNullアノテーションの出番だ. メソッドの利用者に配慮する意味でも@NonNullを是非つけておきたいところだ.

@NonNullをつければ, メソッド引数の事前条件確認としてのNullチェックはスキップしてもいいだろう.
ただ, “もしNullが渡ってきたら”を考えておいたほうが良さそうなケースがある.

例えば, 次のコードの場合を考えてみる.

public class Hoge {
    @NonNull private final Foo foo;

    public Hoge(@NonNull Foo foo) {
        this.foo = foo;
    }

    public String bar() {
        this.foo.call();
    }

    public void piyo(@NonNull Foo f) {
        f.call();
    }
// ...

クラスHogeはコンストラクタで@NonNullなfooを引数にとる.
このコンストラクタがNullPointerExceptionを返すことはないが, Hogeクラスとしてメンバ変数fooがnullの状態になることを許容したくないのだ.
それを明示するためにもコンストラクタの引数fooには@NonNullアノテーションを付けておいた.

もし, HogeのコンストラクタでfooにNullが渡った場合のことを考えると少し厄介な問題を引き起こすかもしれない.

まず, メソッドbarを確認してみる.
this.fooの値は同クラスのコンストラクタで@NonNullな引数fooで初期化されたものであり, 非Nullであることが事前条件として成立している(おまけにfinal付きだ).
もし, 仮にthis.fooがNullであるような事態に陥ったとしてもbarは実行時の予期せぬ例外としてNullPointerExceptionをスローする.
try-catchで例外を握りつぶすこともない. このメソッドのコードに問題はなさそうだ.

次に, コンストラクタを確認してみる.
このコンストラクタはシンプルで, 引数fooを受け取り, これでメンバー変数を初期化する.
もし, 仮にメソッドの引数fooにNullが代入されてしまっても, このコンストラクタは何の異常も報告しない(にも関わらず@NonNullアノテーションを使用している).
このケースは問題に発展するかもしれない.

メンバ変数fooが意図せずNullで初期化されていることを知るのはメソッドbarを実行した時だ.
クラスHogeが単純な責務しかもたず, 特定のクラスとしか関連しない場合や, コンストラクタで初期化した直後にbarを呼ぶ場合は, fooにNullを渡した犯人を突き止めることができるかもしれない.
しかし, クラスHogeが複数のクラスで共有されるshared objectのような立場であった場合.
Nullでthis.fooが初期化されるタイミングでは沈黙し, Nullを渡した犯人とは異なる運の悪い別インスタンスがbarを呼ぶことではじめてNullPointerExceptionが報告される.

こうなると話はややこしくなる.
当然, NullPointerExceptionのスタックトレースに犯人の痕跡は残っていない.
@NonNullであるはずの引数fooに一体誰がNullを代入したのか. NonNull Code Inspectionの網をくぐり抜けた犯人を追うことになる.

事件はすぐに解決できるかもしれないが, 事件を未然に防ぐことに努めるべきだろう.
クラスHogeのコンストラクタで何かできることはあるだろうか.

まず@NonNullのアノテーションを外してみよう. そしてJavadocの事前条件欄にでも“Nullは受け付けない”旨を強調した上で, コードでもNullチェックをしてみよう.

public class Hoge {
    /**
     * @param foo can noooooooooooooot be NULL!
     */
    public Hoge(Foo foo) {
        if (foo == null) {
            throw new NullPointerException("...");
        }
        this.foo = foo;
    }

こうすれば望まれないNullが設定された際には即座にNullPointerExceptionをスローすることができ, 犯人をスタックトレース上にあぶり出すことができるはずだ.
だが, これだとせっかくのNonNull Code Inspectionの恩恵を受けられない. 犯人をあぶり出すことができるようになっても, 誤ってNullを設定してしまう犯人(イージーなミス)が増えてしまいそうだ.

@NonNullアノテーションの恩恵は残しておきたい.
なので@NonNullアノテーションはそのまま残して, Nullチェックのコードを追加してみる.

    public Hoge(@NonNull Foo foo) {
        if (foo == null) {
            throw new NullPointerException("...");
        }
        this.foo = foo;
    }

些か奇妙なコードにも見える. そのせいかAndroid Studioも “Condition ‘foo == null’ is always ‘false’” と別のCode Inspectionをコメントしてきた.
確かにその通り(そうであって欲しいの)だが, 望まない副作用を避けるためにもこの場所にはNullチェックを書いておきたい事情があるのだ.
このまま放っておいても良いが, 常に警告されているのも気持ち良いものではないのでAndroid Studioには”そうではない”ことを伝えておく.

    public Hoge(@NonNull Foo foo) {
        // noinspection ConstantConditions
        if (foo == null) {
            throw new NullPointerException("...");
        }
        this.foo = foo;
    }

これで毎回 “些か奇妙なコード” をLint checkの度, 目にすることもなくなった.

毎回noinspectionを書くのが嫌なら, nonNullをチェックするutilメソッドを定義しておけばより簡素なコードにできる.

public static <T> T nonNull(T o) {
    if (o == null) {
        throw new NullPointerException("Require Non null object");
    }
    return o;
}

// this.foo = nonNull(foo);

ところで, 残ったメソッドpiyoの方は問題ないだろうか.

    public void piyo(@NonNull Foo f) {
        f.call();
    }

これは問題ないだろう. 引数fにNullを渡された場合でも潜伏期間なしにNullPointerExceptionがスローされ, スタックトレースには犯人が載っているはずだ.
fにNullを設定するとNullPointerExceptionがスローされる正しいコードだ.

まとめ

@NonNullはあくまでも補助的な機能であって非Nullを保証するものではない.
メソッドの引数にNullが渡るとNullPointerExceptionが発生することを明示する場合, @NonNullアノテーションはとても役に立つ.

本来Nullを許容しない(NullPointerExceptionをスローする)ことを明示する@NonNullアノテーションだが, Nullを受け取っても異常を報告せず終了してしまう@NonNullな引数を作ることもできてしまう.
そのようなケースでも, @NonNullが有益なケースはあるが, それによる副作用も考慮した上で, 必要なNullチェックは書いておこう.

以上.