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()
、前に定義した関数を呼び出しています。関数は 型 の値を返しますが、は のサブタイプであり、他の型のサブタイプであるのと同様に、 のサブタイプであるため、型の変数に割り当てることができます。コンパイラは、関数が値を返すことはないと認識しているため、これを許可するため、害はありません。user
null
?:
error()
Nothing
User
Nothing
User
error()
Nothing
同様に、他の戻り値の型を持つ関数から戻ることもできます。
fun getUser(request: Request): User {
return request.user ?: error("User not found")
}
ここで、getUser()
関数は を返すように宣言されていますが、が の場合はUser
を返すことがあります。Nothing
user
null
Nothing
Nullオブジェクトパターン
リストで指定されたファイルを削除する関数の次の例を考えてみましょう。
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>
。これは、List
Kotlin のインターフェースが キーワードを使用して定義されているため共変であるためです。out
つまり、 です。また、はすべての型のサブタイプであることList<out T>
がわかったので、 ものサブタイプです。また、共変性により、は、などのサブタイプになります。これは だけでなく、共変ジェネリック ( )を持つすべての型に適用されます。Nothing
Nothing
File
List<Nothing>
List<File>
List<Int>
List<User>
List<AllTypes>
out
List
Nothing
パフォーマンス向上のために
この例で使用した関数と同様に、、、などの null オブジェクトを返すemptyList()
定義済み関数があります。これらはすべて を使用して定義されます。次のように独自のオブジェクトを定義できます。emptyMap()
emptySet()
emptySequence()
Nothing
ここでの利点は、これらがシングルトン オブジェクトを返すことです。たとえば、 に割り当てる場合でも、複数の場所で など、空のインスタンスを取得する場合でも、同じ関数を呼び出すことができます。毎回emptyList()
同じオブジェクトが返されるため、オブジェクトの作成とメモリ割り当てのコストが節約されます。List<File>
List<Int>
List<AllTypes>
空所
Void
Javaでジェネリックを拡張する方法
クラスは パッケージVoid
からのものでありjava.lang
、 とUnit
はパッケージNothing
からのものですkotlin
。はVoid
Kotlin で使用するためのものではありません。Kotlin には という形式の独自のクラスがありますUnit
。
Void
Worker
Java では、値を返す必要がある場合に記述したインターフェイスの例のように、ジェネリック インターフェイスを拡張するために使用されます。したがって、Kotlin コードを Java に変換するには、例で使用したのと同じ方法を使用して、次のように Java でコードを書き換えることUnit
ができます。Void
Unit
Worker
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)
Unit
Nothing
それでおしまい!