Kotlin - Void と Unit と Nothing の比較 質問する

Kotlin - Void と Unit と Nothing の比較 質問する

Kotlin には、性質が非常によく似た 3 つの型があります。

  • Void
  • Unit
  • Nothing

まるで JavaScript の間違いを犯しているようです:

  • null
  • undefined
  • void(0)

彼らがしていない同じ間違いを犯してしまった場合、それらはすべて何のためにあり、どう違うのでしょうか?

ベストアンサー1

ユニット

Unitのようなものですvoid

Kotlin では、関数が意味のある値を返さない場合は、Java とUnit同様にを返すように宣言されます。void

fun greet(): Unit { println("Good day!") }

Unit関数が戻るときは、コンパイラによってデフォルトの戻り値の型と見なされるUnitため、記述をスキップするのが慣例です。Unit

fun greet() { println("Good day!") }

Unitシングルトンです

Unit、単一のオブジェクト (シングルトン パターン) のみを持つクラスであり、そのオブジェクトは 自身です。これは、次に示すように、オブジェクト宣言を使用してパッケージUnit内で宣言されます。kotlin

public object Unit {
    override fun toString() = "kotlin.Unit"
}

Unit関数型プログラミング

Kotlin は関数型プログラミングを第一級にサポートしています。関数型プログラミング言語では を使用するのが一般的ですUnit。これにより、関数が値を返さない場合でも、すべての関数を戻り値を持つものとして宣言できるようになり、関数型が読みやすくなります。

val greet: () -> Unit = { println("Good day!") }

ここで、() -> Unitは関数型であり、Unitの後の は、->この関数型が意味のある値を返さないことを示します。 の記述は、Unit関数型では省略できません。

Unitジェネリックの拡張

すべての関数は値を返さなければなりません。Kotlinはこれを次のように表現することにしました。クラスJava のように特別な型を使用するのではなくvoid、クラスを使用する理由として、クラスを型階層の一部にすることで、型システムの一貫性を高めることができることが挙げられます。

例えば、何らかの作業を実行するジェネリックがあるとします。interfaceこのインターフェースの関数は何らかの作業を実行し、Worker<T>doWork()値を返す必要がある T:

interface Worker<T> {
    fun doWork(): T
}

しかし、時にはこのインターフェースを仕事に使いたいこともあるでしょう値を返す必要がないたとえば、ログ記録の作業は、LogWorker次に示すWorkerインターフェイスを拡張するクラスで行います。

class LogWorker : Worker<Unit> {
    override fun doWork() {
        // Do the logging
    }
}

Unitこれは、もともと値を返すように設計された既存のインターフェースを使用できるという魔法です。ここでは、返すものがない目的を果たすために、doWork()関数に値を返すようにしています。Unitしたがって、ジェネリック パラメーターを返す関数をオーバーライドする場合に便利です。

Unit関数の戻り値の型についても言及していないことに注意してくださいdoWork()。ステートメントを記述する必要もありませreturnん。

何もない

Nothingの価値は存在しない

Kotlinでは、クラスNothing決して存在しない価値constructorこのクラスの値/オブジェクトは保持されるため、存在することはできません。パッケージ内では次のようにprivate定義されています。kotlin

public class Nothing private constructor()

Nothingは、値を返さない関数の戻り値の型に使用されます。たとえば、無限ループを持つ関数や、常に例外をスローする関数などです。Kotlinerror()標準ライブラリの関数は、常に例外をスローして を返す例ですNothing。コードは次のとおりです。

fun error(message: Any): Nothing = throw IllegalStateException(message.toString())

Nothingボトムタイプ

型理論では、値を持たない型はボトム型と呼ばれ、他のすべての型のサブタイプですNothingサブタイプKotlinのすべての型はAny?スーパータイプすべての型の。したがって、型の(存在しない)値は、Nothingすべての型の変数に割り当てることができます。例:

val user: User = request.user ?: error("User not found")

ここで、が である場合、elvis 演算子 ( )を使用してerror()、前に定義した関数を呼び出しています。関数は 型 の値を返しますが、は のサブタイプであり、他の型のサブタイプであるのと同様に、 のサブタイプであるため、型の変数に割り当てることができます。コンパイラは、関数が値を返すことはないと認識しているため、これを許可するため、害はありません。usernull?:error()NothingUserNothingUsererror()

Nothing同様に、他の戻り値の型を持つ関数から戻ることもできます。

fun getUser(request: Request): User {
    return request.user ?: error("User not found")
}

ここで、getUser()関数は を返すように宣言されていますが、が の場合はUserを返すことがあります。Nothingusernull

NothingNullオブジェクトパターン

リストで指定されたファイルを削除する関数の次の例を考えてみましょう。

fun deleteFiles(files: List<File>? = null) {
    if (files != null) files.forEach { it.delete() }
}

この関数の設計上の問題は、 がList<File>空であるか、または要素があるかどうかを伝えないことです。また、使用する前にnullリストが であるかどうかを確認する必要があります。null

この問題を解決するために、null オブジェクト設計パターンを使用します。null オブジェクト パターンでは、null参照を使用してオブジェクトが存在しないことを示す代わりに、期待されるインターフェイスを実装するがメソッド本体を空のままにするオブジェクトを使用します。

そこで、インターフェースのオブジェクトを定義しますList<Nothing>

// This function is already defined in the Kotlin standard library
fun emptyList() = object : List<Nothing> {
    override fun iterator(): Iterator<Nothing> = EmptyIterator
    ...
}

ここで、この null オブジェクトをdeleteFiles()関数内でパラメータのデフォルト値として使用します。

fun deleteFiles(files: List<File> = emptyList()) {
    files.forEach { it.delete() }
}

これにより、または空の不確実性がなくなりnull、意図がより明確になります。また、nullオブジェクトの関数が空であるため、nullチェックも削除され、呼び出されますが、ノーオペレーション(それらには操作がないので、何も起こりません)。

Nothing共変ジェネリック

上記の例では、コンパイラーは が期待されるList<Nothing>場所に を渡すことを許可しますList<File>。これは、ListKotlin のインターフェースが キーワードを使用して定義されているため共変であるためです。outつまり、 です。また、はすべての型のサブタイプであることList<out T>がわかったので、 ものサブタイプです。また、共変性により、は、などのサブタイプになります。これは だけでなく、共変ジェネリック ( )を持つすべての型に適用されます。NothingNothingFileList<Nothing>List<File>List<Int>List<User>List<AllTypes>outList

Nothingパフォーマンス向上のために

この例で使用した関数と同様に、、、などの null オブジェクトを返すemptyList()定義済み関数があります。これらはすべて を使用して定義されます。次のように独自のオブジェクトを定義できます。emptyMap()emptySet()emptySequence()Nothing

ここでの利点は、これらがシングルトン オブジェクトを返すことです。たとえば、 に割り当てる場合でも、複数の場所で など、空のインスタンスを取得する場合でも、同じ関数を呼び出すことができます。毎回emptyList()同じオブジェクトが返されるため、オブジェクトの作成とメモリ割り当てのコストが節約されます。List<File>List<Int>List<AllTypes>

空所

VoidJavaでジェネリックを拡張する方法

クラスは パッケージVoidからのものでありjava.lang、 とUnitはパッケージNothingからのものですkotlin。はVoidKotlin で使用するためのものではありません。Kotlin には という形式の独自のクラスがありますUnit

VoidWorkerJava では、値を返す必要がある場合に記述したインターフェイスの例のように、ジェネリック インターフェイスを拡張するために使用されます。したがって、Kotlin コードを Java に変換するには、例で使用したのと同じ方法を使用して、次のように Java でコードを書き換えることUnitができます。VoidUnitWorker

interface Worker<T> {
    T doWork();
}

class LogWorker implements Worker<Void> {
    @Override public Void doWork() {
        // Do the logging
        return null;
    }
}

を使用する場合Void、 を戻り値の型として使用する必要がありVoid(省略不可)、 ステートメントを記述する必要があるのに対しreturn、 の場合はUnitどちらも省略できることに注意してください。これは、Kotlin コードで の使用を避けるもう 1 つの理由ですVoid

結論

したがって、Unitおよびは、Nothing私の意見では Kotlin 設計者のミスではなく、Javascript の、およびほど疑問視されるものではありません。nullおよびundefinedは、関数型プログラミングを容易にするとともに、前述のその他の便利な機能を提供します。これらは、他の関数型プログラミング言語でもよく使用されます。void(0)UnitNothing

それでおしまい!

おすすめ記事