Every language needs a convenient way to represent basic kinds of values, like numbers or characters. All languages need to have built-in types and literals. Types are used to represent certain types of values. Some type examples are Int, Boolean, or String. Literals are built-in notations that are used to create instances. Some literal examples are a string literal, which is text in quotation marks, or an integer literal, which is a bare number.
In this chapter, we’ll learn about the basic Kotlin types and their literals:
numbers (Int, Long, Double, Float, Short, Byte),
booleans (Boolean),
characters (Char),
strings (String).
There is also the array primitive type in Kotlin, which will be covered in the chapter Collections.
In Kotlin, all values are considered objects (there are no primitive types), so they all have methods, and their types can be used as generic type arguments (this will be covered later). Types that represent numbers, booleans, and characters might be optimized by the Kotlin compiler and used as primitives, but this optimization does not affect Kotlin developers, therefore you don’t need to even think about it.
Let's start discussing the basic types in Kotlin, one by one.
Numbers
In Kotlin, there is a range of different types that are used to represent numbers. They can be divided into those representing integer numbers (without decimal points) and those representing floating-point numbers (with decimal points). In these groups, the difference is in the number of bits used to represent these numbers, which determines the possible number size and precision.
To represent integer numbers, we use Int, Long, Byte, and Short.
| Type | Size (bits) | Min value | Max value |
|---------|-------------|------------|---------------|
| Byte | 8 | -128 | 127 |
| Short | 16 | -32768 | 32767 |
| Int | 32 | $-2^{31}$ | $2^{31} - 1$ |
| Long | 64 | $-2^{63}$ | $2^{63} - 1$ |
To represent floating-point numbers, we use Float and Double.
A plain number without a decimal point is interpreted as an Int. A plain number with a decimal point is interpreted as a Double.
You can create Long by using the L suffix after the number. Long is also used for number literals that are too big for Int.
Similarly, you can create a Float by ending a number with the F or f suffix.
There is no suffix to create Byte or Short types. However, a number explicitly typed as one of these types will create an instance of this type. This also works for Long.
fun main() {
val b: Byte = 123
val s: Short = 345
val l: Long = 345
}
This is not a conversion! Kotlin does not support implicit type conversion, so you cannot use Byte or Long where Int is expected.
If we need to explicitly convert one number to another type, we use explicit conversion functions like toInt or toLong.
fun main() {
val b: Byte = 123
val l: Long = 123L
val i: Int = 123
val i1: Int = b.toInt()
val i2: Int = l.toInt()
val l1: Long = b.toLong()
val l2: Long = i.toLong()
}
Underscores in numbers
In number literals, we can use the underscore _ between digits. This character is ignored, but we sometimes use it to format long numbers for better readability.
fun main() {
val million = 1_000_000
println(million) // 1000000
}
Other numeral systems
To define a number using the hexadecimal numeral system, start it with 0x. To define a number using the binary numeral system, start it with 0b. The octal numeral system is not supported.
fun main() {
val hexBytes = 0xA4_D6_FE_FE
println(hexBytes) // 2765553406
val bytes = 0b01010010_01101101_11101000_10010010
println(bytes) // 1382934674
}
Number and conversion functions
All basic types that represent numbers are a subtype of the Number type.
fun main() {
val i: Int = 123
val b: Byte = 123
val l: Long = 123L
val n1: Number = i
val n2: Number = b
val n3: Number = l
}
The Number type specifies transformation functions: from the current number to any other basic type representing a number.
abstract class Number {
abstract fun toDouble(): Double
abstract fun toFloat(): Float
abstract fun toLong(): Long
abstract fun toInt(): Int
abstract fun toChar(): Char
abstract fun toShort(): Short
abstract fun toByte(): Byte
}
This means that for each basic number you can transform it into a different basic number using the to{new type} function. Such functions are known as conversion functions.
fun main() {
val b: Byte = 123
val l: Long = b.toLong()
val f: Float = l.toFloat()
val i: Int = f.toInt()
val d: Double = i.toDouble()
println(d) // 123.0
}
Operations on numbers
Numbers in Kotlin support the basic mathematical operations:
Kotlin also supports operations that modify a read-write variable var:
+=, where a += b is the equivalent of a = a + b,
-=, where a -= b is the equivalent of a = a - b,
*=, where a *= b is the equivalent of a = a * b,
/=, where a /= b is the equivalent of a = a / b,
%=, where a %= b is the equivalent of a = a % b,
post-incrementation and pre-incrementation ++, which increment variables value by 1,
post-decrementation and pre-decrementation --, which decrement variables value by 1.
fun main() {
var i = 1
println(i) // 1
i += 10
println(i) // 11
i -= 5
println(i) // 6
i *= 3
println(i) // 18
i /= 2
println(i) // 9
i %= 4
println(i) // 1
// Post-incrementation
// increments value and returns the previous value
println(i++) // 1
println(i) // 2
// Pre-incrementation
// increments value and returns the new value
println(++i) // 3
println(i) // 3
// Post-decrementation
// decrements value and returns the previous value
println(i--) // 3
println(i) // 2
// Pre-decrementation
// decrements value and returns the new value
println(--i) // 1
println(i) // 1
}
Operations on bits
Kotlin also supports operations on bits using the following methods, which can be called using the infix notation (so, between two values):
and keeps only bits that have 1 in the same binary positions in both numbers.
or keeps only bits that have 1 in the same binary positions in one or both numbers.
xor keeps only bits that have exactly one 1 in the same binary positions in both numbers.
shl shifts the left value left by the right number of bits.
shr shifts the left value right by the right number of bits, filling the leftmost bits with copies of the sign bit.
ushr shifts the left value right by the right number of bits, filling the leftmost bits with zeros.
fun main() {
println(0b0101 and 0b0001) // 1, that is 0b0001
println(0b0101 or 0b0001) // 5, that is 0b0101
println(0b0101 xor 0b0001) // 4, that is 0b0100
println(0b0101 shl 1) // 10, that is 0b1010
println(0b0101 shr 1) // 2, that is 0b0010
println(0b0101 ushr 1) // 2, that is 0b0010
}
BigDecimal and BigInteger
All basic types in Kotlin have limited size and precision, which can lead to imprecise or incorrect results in some situations.
This is a standard tradeoff in programming, and in most cases we just need to accept it. However, there are cases where we need to have perfect precision and unlimited number size. On JVM, for unlimited number size we should use BigInteger, which represents a number without a decimal part. For unlimited size and precision, we should use the BigDecimal, which represents a number that has a decimal part. Both can be created using constructors[^04_1], factory functions (like valueOf), or a conversion from basic types that represent numbers (toBigDecimal and toBigInteger methods).
import java.math.BigDecimal
import java.math.BigInteger
fun main() {
val i = 10
val l = 10L
val d = 10.0
val f = 10.0F
val bd1: BigDecimal = BigDecimal(123)
val bd2: BigDecimal = BigDecimal("123.00")
val bd3: BigDecimal = i.toBigDecimal()
val bd4: BigDecimal = l.toBigDecimal()
val bd5: BigDecimal = d.toBigDecimal()
val bd6: BigDecimal = f.toBigDecimal()
val bi1: BigInteger = BigInteger.valueOf(123)
val bi2: BigInteger = BigInteger("123")
val bi3: BigInteger = i.toBigInteger()
val bi4: BigInteger = l.toBigInteger()
}
BigDecimal and BigInteger also support basic mathematical operators:
On platforms other than Kotlin/JVM, external libraries are needed to represent numbers with unlimited size and precision.
Booleans
Another basic type is Boolean, which has two possible values: true and false.
fun main() {
val b1: Boolean = true
println(b1) // true
val b2: Boolean = false
println(b2) // false
}
We use booleans to express yes/no answers, like:
Is the user an admin?
Has the user accepted the cookies policy?
Are two numbers identical?
In practice, booleans are often a result of some kind of comparison.
Equality
A Boolean is often a result of equality comparison. In Kotlin, we compare two objects for equality using the double equality sign ==. To check if two objects are not equal, we use the non-equality sign !=.
Kotlin does not support any kind of automatic conversion to Boolean (or any other type), so logical operators should be used only with objects of type Boolean.
Characters
To represent a single character, we use the Char type. We specify a character using apostrophes.
fun main() {
println('A') // A
println('Z') // Z
}
Each character is represented as a Unicode number. To find out the Unicode of a character, use the code property.
fun main() {
println('A'.code) // 65
}
Kotlin accepts Unicode characters. To describe them by their code, we start with \u, and then we need to use hexadecimal format, just like in Java.
fun main() {
println('\u00A3') // £
}
Strings
Strings are just sequences of characters that form a piece of text. In Kotlin, we create a string using quotation marks " or triple quotation marks """.
fun main() {
val text1 = "ABC"
println(text1) // ABC
val text2 = """DEF"""
println(text2) // DEF
}
A string wrapped in single quotation marks requires text in a single line. If we want to define a newline character, we need to use a special character \n. This is not the only thing that needs (or might need) a backslash to be expressed in a string.
| Escape Sequence | Meaning |
|-----------------|-----------------------|
| \t | Tab |
| \b | Backspace |
| \r | Carriage return |
| \f | Form feed |
| \n | Newline |
| \' | Single quotation mark |
| \" | Quotation mark |
| \\ | Backslash |
| \$ | Dollar |
Strings in triple quotation marks can be multiline; in these strings, special characters can be used directly, and forms prefixed by a backslash don’t work.
fun main() {
val text1 = "Let\'s say:\n\"Hooray\""
println(text1)
// Let's say:
// "Hooray"
val text2 = """Let\'s say:\n\"Hooray\""""
println(text2)
// Let\'s say:\n\"Hooray\"
val text3 = """Let's say:
"Hooray""""
println(text3)
// Let's say:
// "Hooray"
}
To better format triple quotation mark strings, we use the trimIndent function, which ignores a constant number of spaces for each line.
fun main() {
val text = """
Let's say:
"Hooray"
""".trimIndent()
println(text)
// Let's say:
// "Hooray"
val description = """
A
B
C
""".trimIndent()
println(description)
// A
// B
// C
}
String literals may contain template expressions, which are pieces of code that are evaluated and whose results are concatenated into a string. A template expression starts with a dollar sign ($) and consists of either a variable name (like "text is $text") or an expression in curly braces (like "1 + 2 = ${1 + 2}").
fun main() {
val name = "Cookie"
val surname = "DePies"
val age = 6
val fullName = "$name $surname ($age)"
println(fullName) // Cookie DePies (6)
val fullNameUpper =
"${name.uppercase()} ${surname.uppercase()} ($age)"
println(fullNameUpper) // COOKIE DEPIES (6)
val description = """
Name: $name
Surname: $surname
Age: $age
""".trimIndent()
println(description)
// Name: Cookie
// Surname: DePies
// Age: 6
}
If you need to use a special character inside a triple quotation mark string, the easiest way is to specify it with a regular string and include it using template syntax.
fun main() {
val text1 = """ABC\nDEF"""
println(text1) // ABC\nDEF
val text2 = """ABC${"\n"}DEF"""
println(text2)
// ABC
// DEF
}
In Kotlin strings, we use Unicode, so we can also define a Unicode character using a number that starts with \u, and then specifying a Unicode character code in hexadecimal syntax.
You can use + operator to concatenate two strings, so to create a new string that is a combination of two other strings. You can also use this operator to concatenate a string with any other object, which will be converted to a string. The result is always a string.
fun main() {
val text1 = "ABC"
val text2 = "DEF"
println(text1 + text2) // ABCDEF
println(text1 + 123) // ABC123
println(text1 + true) // ABCtrue
}
Summary
In this chapter, we've learned about the basic Kotlin types and the literals we use to create them:
Numbers that are represented by types Int, Long, Double, Float, Short, and Byte are created with bare number values with possible suffixes for type customization. We can define negative numbers or decimal parts. We can also use underscores for nicer number formatting.
Boolean values true and false are represented by the Boolean type.
Characters, which are represented by the Char type. We define a character value using single quotation marks.
Strings, which are used to represent text, are represented by the String type. Each string is just a series of characters. We define strings inside double quotation marks.
So, we have the foundations for using Kotlin. Let's move on to more-complicated control structures that determine how our code behaves.
[^04_1]: Constructors will be covered in the chapter Classes.
[^04_2]: This operator is similar to modulo. Both the remainder and the modulo operations act the same for positive numbers but differently for negative numbers. The result of -5 remainder 4 is -1 because -5 = 4 * (-1) + (-1). The result of -5 modulo 4 is 3 because -5 = 4 * (-2) + 3.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
Owen has been developing software since the mid 1990s and remembers the productivity of languages such as Clipper and Borland Delphi.
Since 2001, He moved to Web, Server based Java and the Open Source revolution.
With many years of commercial Java experience, He picked up on Kotlin in early 2015.
After taking detours into Clojure and Scala, like Goldilocks, He thinks Kotlin is just right and tastes the best.
Owen enthusiastically helps Kotlin developers continue to succeed.
Nicola Corti is a Google Developer Expert for Kotlin. He has been working with the language since before version 1.0 and he is the maintainer of several open-source libraries and tools.
He's currently working as Android Infrastructure Engineer at Spotify in Stockholm, Sweden.
Furthermore, he is an active member of the developer community.
His involvement goes from speaking at international conferences about Mobile development to leading communities across Europe (GDG Pisa, KUG Hamburg, GDG Sthlm Android).
In his free time, he also loves baking, photography, and running.
Software architect with 15 years of experience, currently working on building infrastructure for AI. I think Kotlin is one of the best programming languages ever created.
Emanuele is passionate about Android and has been fascinated by it since 2010: the more he learns, the more he wishes to share what he knows with others, which is why he started maintaining his own blog.
In his current role as Senior Android Developer at Mozio, he is now focusing on Kotlin Multiplatform Mobile: he has already given a couple of talks on this topic on various occasions, so far.
Interested in everything Android and Kotlin related, including architecture patterns, TDD, functional programming and Jetpack Compose. Author of several articles about Android and Kotlin Coroutines.