This is a quick start guide to Kotlin for people with prior coding experience (in any other language). I compiled what I learned from different sources like Kotlin Koans and JetBrains Academy, as a quick reference while working on coding challenges or Android applications. This guide is in no way a complete reference to Kotlin, but it probably will be sufficient for new users. I've assumed you already know programming in some other language, so I have skipped explanations for concepts like mutability and lambda expressions. However, if you are new to such terms, a simple web search will definitely help you undertand them.
A sample Hello-World program has been included in the src directory to give you an idea of how Kotlin files and packages look like.
Blocks of code are enclosed in curly braces, {...}
Each statement should start on a new line
There are three types of comments in Kotlin:
// This is a single line comment/* This is an example of a multiline comment. *//** * These are * documentation comments.*/
&&, ||, ! and xor represent the logical operators - and, or, not and xor.
Values and Variables
val is used to declare read-only values.
val name ="Kotlin"
name ="Programming"// won't work, as 'name' is read-only and cannot be reassigned
var is used to declare mutable variables. You can reassign the variables with values of the same type as the initial value.
var language ="English"
language ="Klingon"// this is possible
language =10// won't work, as language is a String variable
The variables type is inferred by the compiler. This is called type inference.
The type of a variable can be specified, if required, while declaring it. The type should be specified if the variable is not initialized during declaration, as the compiler cannot automatically infer the type.
var name:String="Kotlin"val num:Int=999val foo // won't work, as type can't be inferredval character:Char
character ='A'// works as expected
If you create a variable and assign an object to it, the new variable can point to the same object as well. This is called copying by reference. In other words, the = sign does not copy the object itself, it only copies a reference to it.
val msg1 ="Hey"val msg2 = msg1
// msg1 and msg2 will both point to a single object in memory, the String "Hey".
If the object is immutable, you cannot change it, but you can use another object and assign this new object to the same variable. When you reassign the variable, it will point to the new object and other variables will still point to the old object. Standard types such as strings or numbers are immutable, so it's safe to copy them by reference. The behavior of mutable objects is different. If you modify an object from one variable, the other assigned variables continue to point to that object, so they will also reflect the same changes.
If mutable types like lists or arrays are declared with the val keyword, the reference to the object is declared as read-only. However, the contents of the mutable type are still modifiable. In other words, if you declare a variable of type Array using val, you cannot reassign another Array to it, but you can change the values in the original Array.
The comparison operators == and != checks for structual equality, while === and !== checks for referential equality.
val blue =Box(3)
val green = blue // object reference is copiedval red =Box(3)
println(blue == green) // trueprintln(blue === green) // true, as both point (refer) to the same objectprintln(blue == red) // trueprintln(blue === red) // false, they point (refer) to different objectsvar two =2var anotherTwo =2println(two === anotherTwo) // true, because the '===' equality check is equivalent to the '==' check for primitive types
two = two +1println(two === anotherTwo) // false
The val keyword implies an immutable reference to the object. The var keyword implies a mutable reference to a variable, so you can reassign it.
Kotlin supports prefix and postfix increment/decrement operators (++ and --) on variables.
Data Types
The names of Kotlin datatypes start with a capital letter.
Types: Byte: 8 bits (1 byte), Range: -128 to 127 Short: 16 bits (2 bytes), Range: -32768 to 32767 Int: 32 bits (4 bytes), Range: −(231) to (231)−1 Long: 64 bits (8 bytes), Range: −(263) to (263)−1 Float: 32 bits (4 bytes), 6-7 significant decimal digits Double: 64 bits (8 bytes), 15-16 significant decimal digits Char: 16 bits (2 bytes), represents a 16-bit Unicode character Boolean: size is machine-dependent String: size depends on the string
Strings are enclosed in double quotes, "...".
Characters are enclosed in single quotes, '_'.
Fractional numbers are inferred as Double by default. Explicitly specify the type, or use the suffix f or F to use Float instead.
Long is used by the compiler only if the value won't fit in an Int variable. To use Long with a small value, explicitly specify the type of the variable, or use the suffix l or L with the value.
Underscores can be used to divide the a number into blocks for easier readability. 1_000_000 is the same as 1000000.
Types in Kotlin are organized into a hierarchy of subtype-supertype relationships. Supertype is a type that specifies some common characteristics and rules of behavior that every subtype will follow.
Number is a supertype for all types that represent numeric value. For example, Int, Float and Double are subtypes of Number type.
The type checker of the Kotlin compiler also enforces supertype-subtype relationships. For example, to a function waiting for an argument of type Number, you can pass its subtype, Int:
funcalc(number:Number) { /*...*/ }
val number:Int=1
calculate(number)
Any is a supertype for all non-nullable types in Kotlin. Any? is a supertype for Any that includes every other nullable type.
Unit is the equivalent of the void type found in many other programming languages. It is the return type of a function that does not return any meaningful value. If the return type of a function is not specified, it is inferred to be Unit. A return statement is not required for such functions as they are returned implicitly. Unit is also a subtype of Any.
Nothing is a type that has no instances, and are used in functions like fail() or expressions like throw, which doesn't return control. Any code following an expression of type Nothing is unreachable. When you call a function with a Nothing return type, the compiler won't execute any code beyond this call.
If you use null to initialize a value of an inferred type and there's no other information that can be used to determine a more specific type, the compiler will infer the Nothing? type:
val x =null// type: Nothing?val l =listOf(null, null) // type: List<Nothing?>
In Kotlin, 0 is not the same as false. So you cannot assign a Int value to a Boolean variable.
Small errors may accumulate after operations involving Float and Double types. Generally, avoid using == in expressions involving floating-point operations.
val one =0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1println(one) // 0.9999999999999999// one == 1.0 will return false
A Char can be also created by using its hexadecimal code in the Unicode table. They are interconvertible with Int values.
val alphabet ='\u0041'// represents 'A'
Int numbers can be added to characters, but the reverse is not true.
You can compare Char using relational operators according to their position in the Unicode table.
The Char class has built-in utility functions like isDigit(), isLetter(), isLetterOrDigit(), isWhitespace(), isUpperCase(), isLowerCase(), toUpperCase() and toLowerCase().
String has a property length that stores the number of characters in the string. Individual elements can be accessed using their indices. It also has functions like first(), last() and lastIndex() to improve readability.
val text ="Hello!"println(text.length) // 6println(text.first()) // Hprintln(text.last()) // !println(text.lastIndex()) // 5
Individual characters of a String can't be changed, because it is an immutable type.
String objects can be concatenated using the + operator. Values of other types can also be appended to a String using the + operator, but the expression must start with the String.
val firstName ="John"val lastName ="Doe"val fullName = lastName +", "+ firstName // Doe, Johnval userName ="johnny"+true+69// johnnytrue69val nickname =8+"ball"// Error, as a string can't be added to an Int
Kotlin supports string templates. Variables can be used directly in a String by prefixing the dollar sign ($) to the variable name. Similarly, expressions can be used in a String by enclosing them in curly braces after the dollar sign.
val name ="John"println("My name is $name.") // My name is John.println("My name has ${name.length} letters.") // My name has 4 letters.
The substring() method of a String accepts startIndex and lastIndex as arguments and returns the substring that starts at the startIndex and ends right before the lastIndex. If lastIndex is omitted, the default value (length of the original string) is used.
The substringBefore() and substringAfter() functions of String accept a delimiter as argument and return a String before/after the first occurrence of the specified delimiter. If the String does not contain the delimiter, the entire String is returned. Similarly, the substringBeforeLast() and substringAfterLast() methods return a String before or after the last occurrence of the delimiter. If a second argument is provided to these methods, it is returned if the delimiter is not found in the String.
The replace() method of a String replaces all occurrences of the first argument in the String with the second argument. Similarly, replaceFirst() replaces the first occurrence of the first argument with the second, and skips the rest.
The toLowerCase() and toUpperCase() methods of a String returns a String with its case converted accordingly.
The repeat() method of String allows you to repeat a String without using loops.
print("Hey".repeat(5)) // HeyHeyHeyHeyHey
A CharArray can be converted to a String using String(), and a String can be converted to a CharArray using the toCharArray() method. The toTypedArray() method can be used to convert an list of String to a TypedArray.
You can iterate through a String using a range of indices, array of indices, or using the characters of the String.
val text ="Hello World"for (i in0 until text.length) {
print("${text[i]}") // H e l l o W o r l d
}
for (index in text.indices) {
print("${text[index]}") // H e l l o W o r l d
}
for (i in text) {
print("$i") // H e l l o W o r l d
}
Type Conversion
Unlike some other programming languages, there are no implicit widening conversions for numbers in Kotlin. For example, an Int variable cannot be initialized with a Short or Byte variable. A function with a Double parameter can be called only on Double values, but not Float, Int, or other numeric values. The compiler will not perform any automatic type conversion in these cases.
There are methods built into the datatypes for explicit type conversion. It will not affect the type of the original variables.
Every number type supports the following conversions: toByte(), toShort(), toInt(), toLong(), toFloat(), toDouble() and toChar().
Numbers are converted to Char and vice versa using the Unicode Table.
When you convert a larger type to smaller type, the value may be truncated.
All datatypes can be converted to a string using toString().
Strings can be converted to numbers if they are in the correct format of the type.
If the string is "true" or "false" (case insensitive), it can be converted to a Boolean using toBoolean(). Any value other than "true" will be a false Boolean.
Type Inference
In expressions involving variables of different datatypes, the widest type in the expression is inferred by the compiler as the type of the result. Double > Float > Long > Int > (Byte, Short)
The result of an expression involving Byte or Short variables is of the type Int (or an even larger type), unless explicitly casted.
Ranges
Kotlin lets you create ranges of values using rangeTo() from the kotlin.ranges package, and its operator form ...
a..b is a range from a to b (inclusive), and in is a keyword used to check whether a value is within a range.
if (i in1..5) { // equivalent of 1 <= i && i <= 5print(i)
}
If you need to exclude the right border, you should subtract one from it:
val within = c in a..b -1// equivalent of a <= c && c < b
If you need to check that a value is not within a range, use !in:
val notWithin =100!in10..99// true
You can assign a range to a variable and use it later somewhere in the code:
val range =1..5print(3in range) // true
You can also use ranges of characters and strings (in dictionary order):
Integral type ranges can be iterated over using for loops:
for (i in1..5) print(i) // 12345
To iterate numbers in reverse order, use the downTo function instead of ..:
for (i in5 downTo 1) print(i) // 54321
To iterate over numbers with an arbitrary step, use the step function:
for (i in1..10 step 2) {
print(i) // 13579
}
To iterate a number range which does not include its end element, use the until function:
for (i in1 until 10) { // i in [1, 10), 10 is excludedprint(i)
}
Regex
A regex instance can be created using the toRegex() method of a String or by calling the Regex constructor:
val string ="ing"val regex1 = string.toRegex()
val regex2 =Regex("ing")
// regex1 and regex2 represent the same regular expression
matches() method of a string returns true if the string is full match for the regex specified:
val regex =Regex("ing")
val string1 ="ing"val string2 ="running"println(string1.matches(regex)) // trueprintln(string2.matches(regex)) // false
The dot character (.) can match any character except a newline. A question mark (?) denotes that the preceding character is optional. A double backslash (\\) is used to escape special characters. A single backslash (\) is used to escape double quotes (").
To match \, the regexp is \\\\.
BigInteger
The Java Class Library provides a BigInteger class for processing very large numbers, limited only by the memory available. It is immutable.
importjava.math.BigIntegerval number1 =BigInteger("52955871795228763416553091") // initialized using constructorval number2 =BigInteger.valueOf(1000000000) // initialized from a Long value using the valueOf() methodval number3 =1234.toBigInteger() // initialized from an Int value using the toBigInteger() method// Some constants defined in the BigInteger class:val zero =BigInteger.ZERO// 0val one =BigInteger.ONE// 1val ten =BigInteger.TEN// 10
The divideAndRemainder() function returns an array consisting of the result of integer division and its remainder.
val (result, remainder) = oneHundredTen.divideAndRemainder(nine) // 12 and 2
The abs() function returns a new BigInteger whose value is the absolute value of the initial BigInteger. The gcd() function returns the greatest common divisor of two numbers.
val number =BigInteger("-1")
println(number.abs()) // 1val twelve =BigInteger.valueOf(12)
val fifteen =BigInteger.valueOf(15)
println(twelve.gcd(fifteen)) // 3
Null Safety
In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that can not (non-null references).
To allow nulls, we have to declare a variable as nullable by defining the type of the variable as <type>?.
var b:String?="abc"// can be set null
You can generally handle operations using nullable variables in three ways:
You can explicitly check whether the variable is null.
var a:String?="abc"val l =if (a !=null) a.length else-1
Use the safe call operator, ?..
val b:String?=nullprintln(b?.length) // returns b.length if b is not null, and null otherwise// the type of this expression is Int?
Safe calls are useful in chains. For example, if Bob (Employee) may be assigned to a Department, that in turn may have another Employee as a department head, then to obtain the name of Bob's department head (if any), we write:
bob?.department?.head?.name // returns null if any of the properties is null
To perform an operation only for non-null values, you can use the safe call operator together with let:
val listWithNulls:List<String?> =listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it) } // prints Kotlin and ignores null
}
A safe call can also be placed on the left side of an assignment. If one of the receivers in the safe calls chain is null, the assignment is skipped, and the expression on the right is not evaluated at all:
// If either `person` or `person.department` is null, the function is not called
person?.department?.head = managersPool.getManager()
We can also use the Elvis operator (?:) in place of an if-expression. The expression to the right of ?: is evaluated only if the left-hand side is null.
val l = a?.length ?:-1// Equivalent to:// val l: Int = if (a != null) a.length else -1// Other examples:val parent = node.getParent() ?:returnnullval name = node.getName() ?:throwIllegalArgumentException("name expected")
The not-null assertion operator (!!) can be used to convert any value to a non-null type and throw an exception if the value is null.
val l = a!!.length
Regular casting may result in a ClassCastException if the object is not of the target type. You can use safe casts that return null if the attempt was unsuccessful:
var a:String?="abc"val n:Int?= a as?Int// null, as the String can't be casted to an Int type
If you have a collection of elements of a nullable type and want to filter non-null elements, you can use filterNotNull():
val nullableList:List<Int?> =listOf(1, 2, null, 4)
val intList:List<Int> = nullableList.filterNotNull() // [1, 2, 4]
请发表评论