JavaRush/Courses/Kotlin SELF/Nullable types T? and the null value

Nullable types T? and the null value

Open

1. Why do we need null?

Programmers love certainty: if we have a variable name, then there should always be a name in it. But real life (and real users) love to break that romantic picture. Someone didn’t enter a nickname, someone skipped their age, someone pressed Enter “just to see what happens”. And now you have a task: how do you store “unknown”?

The option “well, let it be an empty string ""” sometimes works, but often breaks the meaning. An empty string is still a value; it’s just “a string of length 0”. And sometimes we need to store the absence of a value: “the field wasn’t provided”, “the person didn’t specify it”, “we couldn’t compute it”. For this, Kotlin has a special value: null.

Imagine you’re filling out a form. The “Middle name” field can be left blank. If you didn’t fill it in, it doesn’t mean your middle name is an empty string. It means there is no information. That’s exactly the kind of situation where null is needed.

null is not "", not 0, and not false

It’s important to get used to this idea: null is not an “empty value”, but the fact that a value is absent. The empty string "" is a string. Zero 0 is a number. false is a boolean value. And null is “nothing”, “not set”, “does not exist”.

Why does this difference actually matter? Because otherwise you start mixing meanings and writing weird conditions. For example, if the age wasn’t entered, you might choose the strategy “assume the age is 0”. But then the program will behave as if the user is a newborn. Funny, but only the first two times.

Let’s compare a few “similar, but not the same” options:

Value Typical meaning
""
there is text, but it is empty
"   "
there is text, but it consists of spaces (usually it is also cleaned with trim())
"0"
the text "0" (this is not a number!)
0
the number zero
null
there is no value at all

And here Kotlin makes a very smart move: it doesn’t allow you to “accidentally” mix these meanings. If a variable can be null, that should be visible directly in its type. In Kotlin, nullable types are explicitly marked with a question mark ? at the end of the type name.

2. Nullable and non-nullable types

Non-nullable types T

Almost all the types you worked with before were actually non-nullable: String, Int, Boolean. That is, a variable of such a type is not allowed to contain null. And this isn’t “strictness for the sake of strictness”, but a fundamental protection: if the type is String, then it definitely has string properties and methods (for example, length). Kotlin can rely on that.

Let’s look at a minimal example:

fun main() {
    val title: String = "Kotlin"
    println(title) // Kotlin

    title = null 	// Compilation error: String does not allow null
}

Here title is a regular string. Kotlin guarantees that title always contains a string, not “nothing”. Therefore, if you try to assign null, the compiler will stop you before the program even runs.

Why is that cool? Because “oops, it turned out to be null” errors are some of the most unpleasant ones: they pop up at runtime (when the user is already running the program), not at compile time (when you can still calmly fix everything).

Nullable types T?: how to declare and read them

When you understand that a value may be missing, you must honestly tell Kotlin about it. This is simple: add ? to the type. String? is read as “a string or null”, Int? as “an integer or null”.

Here’s a basic example:

fun main() {
    var nickname: String? = null
    println(nickname) // null

    nickname = "neo"
    println(nickname) // neo
}

Notice two things. First, nickname is a var, because we want to store “no value” first, and then assign a normal string. Second, println prints null as the text null—that’s fine, but it’s not “nice output” yet. We’ll learn to do nice output later; for now, it’s important to understand the types.

Let’s compare String and String? in one small table:

Type Can assign "hi" Can assign null What it means
String
yes no “a string is definitely present”
String?
yes yes “a string may be missing”

One more important conclusion: a nullable type is not “a string with some bonuses”. It’s a different type. That’s why Kotlin does not let you “pretend” that String? is a String.

3. Nullable function results

The T? contract in the signature

Before this, you were used to the idea that a function either returns a normal value, or you somehow “figure it out” inside the function body. But with null a very useful discipline appears: if a function may produce no result, that is reflected in the return type.

It’s like an honest contract. If it says Int, the calling code is allowed to expect a number every time. If it says Int?, the calling code must remember: “I may get a number, or I may get null”.

The most familiar example is parsing:

fun parseAge(text: String): Int? = text.toIntOrNull()

fun main() {
    println(parseAge("42"))   // 42
    println(parseAge("oops")) // null
}

Something important happens here: the parseAge function doesn’t have to “make up” an age on error. It honestly says: “if it works, I’ll return an Int; if not, I’ll return null”. And this is not “bad news”. It’s just a branch of reality that is now clearly visible from the type.

By the way, that’s exactly why names like ...OrNull in the Kotlin standard library aren’t an accident, but a hint: “null is possible”.

toIntOrNull() as the first practical example of Int?

You have already encountered toIntOrNull(), but today it’s useful to look at it not as a “crash saver”, but as a function with a very clear contract: it returns Int?. That is, either a number or null.

Let’s make a small snippet that is as close as possible to what you actually write in console tasks:

fun main() {
    print("Enter age: ")
    val input = readln().trim()

    val age: Int? = input.toIntOrNull()
    println("age = $age") // for example: age = 25  or  age = null
}

So far we are not doing anything “smart”—we just read, trim spaces, and try to parse. The most important part here: the variable age has type Int?, because we are getting the result from a function that may return null.

4. Mini app: user profile

So that null doesn’t remain abstract philosophy, let’s start building a small console app that we can gradually improve in the next topics (but today—strictly within the scope of types). The idea is simple: the user enters a profile. Some fields are required, some are not. For example, the name is required, while the nickname and age are optional.

It’s important to choose correctly where String should be used and where String? should be used. This is a good habit: make types non-nullable by default, and add ? only where the absence of a value is a real and meaningful situation.

Required and optional strings

Let’s start with the simplest part: read the name (required) and the nickname (can be skipped). To make null appear, let’s agree on this: if the user enters an empty string after trim(), we’ll consider that the value is missing.

fun main() {
    print("Name (required): ")
    val name: String = readln().trim()

    print("Nickname (can be empty): ")
    val nickInput = readln().trim()
    val nickname: String? = if (nickInput == "") null else nickInput

    println("name = $name")         // for example: name = Alice
    println("nickname = $nickname") // for example: nickname = null
}

Note: we are not “improving UX” yet and not validating the name properly. Right now it’s more important to see the type model: a required field is String, an optional one is String?.

Optional age: Int?

Now let’s add age. It’s also optional, but it must be a number as well. That means “empty” and “not a number” should both lead to null. This is conveniently expressed with toIntOrNull():

fun main() {
    print("Age (can be empty): ")
    val ageInput = readln().trim()

    val age: Int? = if (ageInput == "") null else ageInput.toIntOrNull()
    println("age = $age") // for example: age = 30  or  age = null
}

Yes, there’s a small “cosmetic rough edge” here: if the user enters "abc", toIntOrNull() will return null, and you won’t be able to distinguish “empty” from “error”. But for today’s lecture this is even useful: we practice seeing that both scenarios lead to no result, and this is expressed by the type Int?.

Putting the whole profile together

Now let’s put everything together and add profile output. Since we haven’t learned convenient tools for working with nullable values yet, we’ll print “as is” to observe null explicitly:

fun main() {
    print("Name (required): ")
    val name: String = readln().trim()

    print("Nickname (can be empty): ")
    val nickInput = readln().trim()
    val nickname: String? = if (nickInput == "") null else nickInput

    print("Age (can be empty): ")
    val ageInput = readln().trim()
    val age: Int? = if (ageInput == "") null else ageInput.toIntOrNull()

    println("Profile: name=$name, nick=$nickname, age=$age")
    // for example: Profile: name=Alice, nick=null, age=28
}

And here you get the main practical result of the lecture: you don’t “invent” values instead of the user and you don’t pretend that everything is filled in. You store the real picture of the world in types. If the nickname wasn’t entered, that’s null. If parsing the age failed, that’s null. This is honest, predictable, and very convenient for further logic.

To reinforce the skill of “reading types with your eyes”, try to say this out loud:

  • val name: String — “a string is always present”.
  • val nickname: String? — “a string may be missing”.
  • val age: Int? — “a number may be missing”.

Yes, it sounds like an exercise from a language class, but it really helps. Programming is, among other things, the ability to read conventions, and types in Kotlin are one of the most important conventions.

5. Common mistakes

Mistake #1: confusing null with "" (the empty string).
It’s very easy to start storing “no value” as an empty string, and then suddenly realize you can’t tell “the user entered nothing” from “the user really wanted an empty value” (yes, sometimes that happens). It’s better to agree in advance: if the field is optional, absence is null, and an empty string is still a string.

Mistake #2: making everything nullable “just in case”.
A beginner may think it’s safer to declare all strings as String? so they “definitely won’t crash”. The problem is that you smear uncertainty across the entire codebase: now every string has to be handled as potentially missing, even if it is required by meaning (for example, a team name or a menu title). A good habit in Kotlin is to treat values as non-nullable by default and add ? only where it truly reflects reality.

Mistake #3: expecting toIntOrNull() to return 0 on error.
Some people subconsciously expect that on a failed parse “well, it will be zero”. But toIntOrNull() has a different contract: it returns null if the string is not a number. And that makes sense: 0 is a specific number and may be the user’s real input, while null is an honest signal that “it didn’t work”.

Mistake #4: trying to return null from a function without specifying ? in the result type.
If you declared fun parseAge(...): Int, then you cannot return null—and the compiler will stop you. This isn’t nitpicking; it’s help: first define the contract (is the result required or not?), and then write the implementation. If it “may fail”, the return type must be Int? (or another T?).

Mistake #5: thinking that null is “just another value” and nothing changes.
Formally, yes, null is a value. But semantically it means absence. So as soon as null appears in a type, it starts affecting all the code around it: Kotlin won’t let you treat a nullable value the same way you treat a normal one. It can be annoying for the first half hour, but then you start to realize the compiler is simply preventing you from stepping on rakes that people in other languages consider “a normal way of life”.

1
Task
Kotlin SELF,  11 level,  1 lecture
Locked
Player profile
Player profile
1
Task
Kotlin SELF,  11 level,  1 lecture
Locked
User registration
User registration
1
Task
Kotlin SELF,  11 level,  1 lecture
Locked
Box counter
Box counter
1
Task
Kotlin SELF,  11 level,  1 lecture
Locked
Order multiplication
Order multiplication
Комментарии
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны sign in
This page has no comments yet