1.1 Introduction
Skip
1.2. Why Kotlin?
We give an overview of the historical development of programming languages so you can understand where Kotlin fits and why you might want to learn it. This atom introduces some topics which, if you are a novice, might seem too complicated right now. Feel free to skip this atom and come back to it after you've read more of the book.
Programs must be written for people to read, and only incidentally for machines to execute.—Harold Abelson, Structure and Interpretation of Computer Programs
Programming language design is an evolutionary path from serving the needs of the machine to serving the needs of the programmer.
A programming language is invented by a language designer and implemented as one or more programs that act as tools for using the language. The implementer is usually the language designer, at least initially.
Early languages focused on hardware limitations. As computers become more powerful, newer languages shift toward more sophisticated programming with an emphasis on reliability. These languages can choose features based on the psychology of programming.
Every programming language is a collection of experiments. Historically, programming language design has been a succession of guesses and assumptions about what will make programmers more productive. Some of those experiments fail, some are mildly successful and some are very successful.
We learn from the experiments in each new language. Some languages address issues that turn out to be incidental rather than essential, or the environment changes (faster processors, cheaper memory, new understanding of programming and languages) and that issue becomes less important or even inconsequential. If those ideas become obsolete and the language doesn't evolve, it fades from use.
The original programmers worked directly with numbers representing processor machine instructions. This approach produced numerous errors, and assembly language was created to replace the numbers with mnemonic opcodes—words that programmers could more easily remember and read, along with other helpful tools. However, there was still a one-to-one correspondence between assembly-language instructions and machine instructions, and programmers had to write each line of assembly code. In addition, each computer processor used its own distinct assembly language.
Developing programs in assembly language is exceedingly expensive. Higher-level languages help solve that problem by creating a level of abstraction away from low-level assembly languages.
Compilers and Interpreters
Kotlin is compiled rather than interpreted. The instructions of an interpreted language are executed directly by a separate program called an interpreter. In contrast, the source code of a compiled language is converted into a different representation that runs as its own program, either directly on a hardware processor or on a virtual machine that emulates a processor:
Languages such as C, C++, Go and Rust compile into machine code that runs directly on the underlying hardware central processing unit (CPU). Languages like Java and Kotlin compile into bytecode which is an intermediate-level format that doesn't run directly on the hardware CPU, but instead on a virtual machine, which is a program that executes bytecode instructions. The JVM version of Kotlin runs on the Java Virtual Machine (JVM).
Portability is an important benefit of a virtual machine. The same bytecode can run on every computer that has a virtual machine. Virtual machines can be optimized for particular hardware and to solve speed problems. The JVM contains many years of such optimizations, and has been implemented on many platforms.
At compile time, the code is checked by the compiler to discover compile-time errors. (IntelliJ IDEA and other development environments highlight these errors when you input the code, so you can quickly discover and fix any problems). If there are no compile-time errors, the source code will be compiled into bytecode.
A runtime error cannot be detected at compile time, so it only emerges when you run the program. Typically, runtime errors are more difficult to discover and more expensive to fix. Statically-typed languages like Kotlin discover as many errors as possible at compile time, while dynamic languages perform their safety checks at runtime (some dynamic languages don't perform as many safety checks as they might).
Languages that Influenced Kotlin
Kotlin draws its ideas and features from many languages, and those languages were influenced by earlier languages. It's helpful to know some programming-language history to gain perspective on how we got to Kotlin. The languages described here are chosen for their influence on the languages that followed them. All these languages ultimately inspired the design of Kotlin, sometimes by being an example of what not to do.
FORTRAN: FORmula TRANslation (1957)
Designed for use by scientists and engineers, Fortran's goal was to make it easier to encode equations. Finely-tuned and tested Fortran libraries are still in use today, but they are typically "wrapped" to make them callable from other languages.
LISP: LISt Processor (1958)
~~~Skip
Why Kotlin? (Introduced 2011, Version 1.0: 2016)
Just as C++ was initially intended to be "a better C", Kotlin was initially oriented toward being "a better Java". It has since evolved significantly beyond that goal.
Kotlin pragmatically chooses only the most successful and helpful features from other programming languages—after those features have been field-tested and proven especially valuable.
Thus, if you are coming from another language, you might recognize some features of that language in Kotlin. This is intentional: Kotlin maximizes productivity by leveraging tested concepts.
Readability
Readability is a primary goal in the design of the language. Kotlin syntax is concise—it requires no ceremony for most scenarios, but can still express complex ideas.
Tooling
Kotlin comes from JetBrains, a company that specializes in developer tooling. It has first-class tooling support, and many language features were designed with tooling in mind.
Multi-Paradigm
Kotlin supports multiple programming paradigms, which are gently introduced in this book:
•
Imperative programming
•
Functional programming
•
Object-oriented programming
Multi-Platform
Kotlin source code can be compiled to different target platforms:
•
JVM. The source code compiles into JVM bytecode (.class files), which can then be run on any Java Virtual Machine (JVM).
•
Android. Android has its own runtime called ART (the predecessor was called Dalvik). The Kotlin source code is compiled into Dalvik Executable Format (.dex files).
•
JavaScript, to run inside a web browser.
•
Native Binaries by generating machine code for specific platforms and CPUs.
This book focuses on the language itself, using the JVM as the only target platform. Once you know the language, you can apply Kotlin to different application and target platforms.
Two Kotlin Features
This atom does not assume you are a programmer, which makes it hard to explain most of the benefits of Kotlin over the alternatives. There are, however, two topics which are very impactful and can be explained at this early juncture: Java interoperability and the issue of indicating "no value."
Effortless Java Interoperability
To be "a better C," C++ must be backwards compatible with the syntax of C, but Kotlin does not have to be backwards compatible with the syntax of Java—it only needs to work with the JVM. This frees the Kotlin designer to create a much cleaner and more powerful syntax, without the visual noise and complication that clutters Java.
For Kotlin to be "a better Java", the experience of trying it must be pleasant and frictionless, so Kotlin enables effortless integration with existing Java projects. You can write a small piece of Kotlin functionality and slip it in amidst your existing Java code. The Java code doesn't even know the Kotlin code is there—it just look like more Java code.
Companies often investigate a new language by building a standalone program with that language. Ideally, this program is beneficial but nonessential, so if the project fails it can be terminated with minimal damage. Not every company wants to spend the kind of resources necessary for this type of experimentation. Because Kotlin seamlessly integrates into an existing Java system (and benefits from that system's tests), it becomes very cheap or even free to try Kotlin to see whether it's a good fit.
In addition, JetBrains, the company that creates Kotlin, provides IntelliJ IDEA in a "Community" (free) version, which includes support for both Java and Kotlin along with the ability to easily integrate the two. It even has a tool that takes Java code and (mostly) rewrites it to Kotlin.
Appendix B covers Java interoperability.
Representing Emptiness
An especially beneficial Kotlin feature is its solution to a challenging programming problem.
What do you do when someone hands you a dictionary and asks you to look up a word that doesn't exist? You could guarantee results by making up definitions for unknown words. A more useful approach is just to say, "There's no definition for that word." This demonstrates a significant problem in programming: How do you indicate "no value" for a piece of storage that is uninitialized, or for the result of an operation?
The null reference was invented in 1965 for ALGOL by Tony Hoare, who later called it "my billion-dollar mistake." One problem was that it was too simple—sometimes being told a room is empty isn't enough; you might need to know, for example, why it is empty. This leads to the second problem: the implementation. For efficiency's sake, it was typically just a special value that could fit in a small amount of memory, and what better than the memory already allocated for that information?
The original C language did not automatically initialize storage, which caused numerous problems. C++ improved the situation by setting newly-allocated storage to all zeroes. Thus, if a numerical value isn't initialized, it is simply a numerical zero. This didn't seem so bad but it allowed uninitialized values to quietly slip through the cracks (newer C and C++ compilers often warn you about these). Worse, if a piece of storage was a pointer—used to indicate ("point to") another piece of storage—a null pointer would point at location zero in memory, which is almost certainly not what you want.
Java prevents accesses to uninitialized values by reporting such errors at runtime. Although this discovers uninitialized values, it doesn't solve the problem because the only way you can verify that your program won't crash is by running it. There are swarms of these kinds of bugs in Java code, and programmers waste huge amounts of time finding them.
Kotlin solves this problem by preventing operations that might cause null errors at compile time, before the program can run. This is the single-most celebrated feature by Java programmers adopting Kotlin. This one feature can minimize or eliminate Java's null errors.
An Abundance of Benefits
The two features we were able to explain here (without requiring more programming knowledge) make a huge difference whether or not you're a Java programmer. If Kotlin is your first language and you end up on a project that needs more programmers, it is much easier to recruit one of the many existing Java programmers into Kotlin.
Kotlin has many other benefits, which we cannot explain until you know more about programming. That's what the rest of the book is for.
1.3 Hello, World!
"Hello, world!" is a program commonly used to demonstrate the basic syntax of programming languages.
We develop this program in several steps so you understand its parts.
First, let's examine an empty program that does nothing at all:
The example starts with a comment, which is illuminating text that is ignored by Kotlin. // (two forward slashes) begins a comment that goes to the end of the current line:
Kotlin ignores the // and everything after it until the end of the line. On the following line, it pays attention again.
The first line of each example in this book is a comment starting with the name of the the subdirectory containing the source-code file (Here, HelloWorld) followed by the name of the file: EmptyProgram.kt. The example subdirectory for each atom corresponds to the name of that atom.
keywords are reserved by the language and given special meaning. The keyword fun is short for function. A function is a collection of code that can be executed using that function's name (we spend a lot of time on functions throughout the book). The function's name follows the fun keyword, so in this case it's main() (in prose, we follow the function name with parentheses).
main() is actually a special name for a function; it indicates the "entry point" for a Kotlin program. A Kotlin program can have many functions with many different names, but main() is the one that's automatically called when you execute the program.
The parameter list follows the function name and is enclosed by parentheses. Here, we don't pass anything into main() so the parameter list is empty.
The function body appears after the parameter list. It begins with an opening brace ({) and ends with a closing brace (}). The function body contains statements and expressions. A statement produces an effect, and an expression yields a result.
EmptyProgram.kt contains no statements or expressions in the body, just a comment.
Let's make the program display "Hello, world!" by adding a line in the main() body:
The line that displays the greeting begins with println(). Like main(), println() is a function. This line calls the function, which executes its body. You give the function name, followed by parentheses containing one or more parameters. In this book, when referring to a function in the prose, we add parentheses after the name as a reminder that it is a function. Here we say println().
println() takes a single parameter, which is a String. You define a String by putting characters inside quotes.
println() moves the cursor to a new line after displaying its parameter, so subsequent output appears on the next line. You can use print() instead, which leaves the cursor on the same line.
Unlike some languages, you don't need a semicolon at the end of an expression in Kotlin. It's only necessary if you put more than one expression on a single line (this is discouraged).
For some examples in the book, we show the output at the end of the listing, inside a multiline comment. A multiline comment starts with a /* (a forward slash followed by an asterisk) and continues—including line breaks (which we call newlines)—until a */ (an asterisk followed by a forward slash) ends the comment:
It's possible to add code on the same line after the close */ of a comment, but it's confusing, so people don't usually do it.
Comments add information that isn't obvious from reading the code. If comments only repeat what the code says, they become annoying and people start ignoring them. When code changes, programmers often forget to update comments, so it's good practice to use comments judiciously, mainly for highlighting tricky aspects of your code.
1.4 var & val
When an identifier holds data, you must decide whether it can be reassigned.
You create identifiers to refer to elements in your program. The most basic decision for a data identifier is whether it can change its contents during program execution, or if it can only be assigned once. This is controlled by two keywords:
•
var, short for variable, which means you can reassign its contents.
•
val, shot for value, which means you can only initialize it; you cannot reassign it.
You define a var like this:
The var keyword is followed by the identifier, an equals sign and then the initialization value. The identifier begins with a letter or an underscore, followed by letters, numbers and underscores. Upper and lower case are distinguished (so this value and thisValue are different).
Here are some var definitions:
In this book we mark lines with commented numbers in square brackets so we can refer to them in the text like this:
•
[1] Create a var named whole and store 11 in it.
•
[2] Store the "fractional number" 1.4 in the var fractional.
•
[3] Store some text (a String) in the var words.
Note that println() can take any single value as an argument.
As the name variable implies, a var can vary. That is, you can change the data stored in a var. We say that a var is mutable:
The assignment sum = sum + 2 takes the current value of sum, adds two, and assigns the result back into sum.
The assignment sum += 3 means the same as sum = sum + 3. The += operator takes the previous value stored in sum and increases it by 3, then assigns that new result back to sum.
Changing the value stored in a var is a useful way to express changes. However, when the complexity of a program increases, your code is clearer, safer and easier to understand if the values represented by your identifiers cannot change—that is, they cannot be reassigned. We specify an unchanging identifier using the val keyword instead of var. A val can only be assigned once, when it is created:
The val keyword comes from value, indicating something that cannot change—it is immutable. Choose val instead of var whenever possible. The Vars.kt example at the beginning of this atom can be rewritten using vals:
•
[1] Once you initialize a val, you can't reassign it. If we try to reassign whole to a different number, Kotlin complains, saying "Val cannot be reassigned".
Choosing descriptive names for your identifiers makes your code easier to understand and often reduces the need for comments. In Vals.kt, you have no idea what whole represents. If your program is storing the number 11 to represent the time of day when you get coffee, it's more obvious to others if you name it coffeetime and easier to read if it's coffeeTime (following Kotlin style, we make the first letter lowercase).
vars are useful when data must change as the program is running. This sounds like a common requirement, but turns out to be avoidable in practice. In general, your programs are easier to extend and maintain if you use vals. However, on rare occasions it's too complex to solve a problem using only vals. For that reason, Kotlin gives you the flexibility of vars. However, as you spend more time with vals you'll discover that you almost never need vars and that your programs are safer and more reliable without them.
1.5 Data Types
Data can have different types.
To solve a math problem, you write an expression:
You know that adding those numbers produces another number. Kotlin knows that too. You know that one is a fractional number (5.9), which Kotlin calls a Double, and the other is a whole number (6), which Kotlin calls an Int. You know the result is a fractional number.
A type (also called data type) tells Kotlin how you intend to use that data. A type provides a set of values from which an expression may take its values. A type defines the operations that can be performed on the data, the meaning of the data, and how values of that type can be stored.
Kotlin uses types to verify that your expressions are correct. In the above expression, Kotlin creates a new value of type Double to hold the result.
Kotlin tries to adapt to what you need. If you ask it to do something that violates type rules it produces an error message. For example, try adding a String and a number:
Types tell Kotlin how to use them correctly. In this case, the type rules tell Kotlin how to add a number to a String: by appending the two values and creating a String to hold the result.
Now try multiplying a String and a Double by changing the + in StringPlusNumber.kt to a *:
Combining types this way doesn't make sense to Kotlin, so it gives you an error.
We can be more verbose and specify the type:
You start with the val or var keyword, followed by the identifier, a colon, the type, an =, and the initialization value. So instead of saying:
You can say:
We've told Kotlin that n is an Int and p is a Double, rather than letting it infer the type.
Here are some of Kotlin's basic types:
•
[1] The Int data type is an integer, which means it only holds whole numbers.
•
[2] To hold fractional numbers, use a Double.
•
[3] A Boolean data type only holds the two special values true and false.
•
[4] A String holds a sequence of characters. You assign a value using a double-quoted String.
•
[5] A Char holds one character.
•
[6] If you have many lines and/or special characters, surround them with triple-double-quotes (this is a triple-quoted String).
Kotlin uses type inference to determine the meaning of mixed types. When mixing Ints and Doubles during addition, for example, Kotlin decides the type for the resulting value:
When you add an Int and to Double using type inference, Kotlin determines that the result n is a Double and ensures that it follows all the rules for Doubles.
Kotlin's type inference is part of its strategy of doing work for the programmer. If you leave out the type declaration, Kotlin can usually infer it.
Addition Myself
아래의 예제 처럼 연산하고자 하는 데이터의 타입의 순서에 따라 자동형변환이 될때가 있고 아닐 때가 있음
Ex) Int + String (X), String + Int(O)
1.6 Functions
A function is like a small program that has its own name, and can be executed (invoked) by calling that name from another functions.
A function combines a group of activities, and is the most basic way to organize your programs and to re-use code.
You pass information into a function, and the function uses that information to calculate and produce a result. The basic form of a function is:
p1 and p2 are the parameters: the information you pass into the function. Each parameter has an identifier name (p1, p2) followed by a colon and the type of that parameter. The closing parenthesis of the parameter list is followed by a colon and the type of result produced by the function. The lines of code in the function body are enclosed in curly braces. The expression following the return keyword is the result the function produces when it's finished.
A parameter is how you define what is passed into a function—it's the placeholder. An argument is the actual value that you pass into the function.
The combination of name, parameters and return type is called the function signature.
Here's a simple function called multiplyByTwo():
•
[1] Notice the fun keyword, the function name, and the parameter list consisting of a single parameter. This function takes an Intparameter and returns an Int.
•
[2] These two lines are the body of the function. The final line returns the value of its calculation x * 2 as the result of the function.
•
[3] This line calls the function with an appropriate argument, and captures the result into val r. A function call mimics the form of its declaration: the function name, followed by arguments inside parentheses.
The function code is executed by calling the function, using the function name multiplyByTwo() as an abbreviation for that code. This is why functions are the most basic form of simplification and code reuse in programming. You can also think of a function as an expression with substitutable values (the parameters).
println() is also a function call—it just happens to be provided by Kotlin. We refer to functions defined by Kotlin as library functions.
If the function doesn't provide a meaningful result, its return type is Unit. You can specify Unit explicitly if you want, but Kotlin lets you omit it:
Both sayHello() and sayGoodbye() return Unit, but sayHello() leaves out the explicit declaration. The main() function also returns Unit.
If a function is only a single expression, you can use the abbreviated syntax of an equals sign followed by the expression:
A function body surrounded by curly braces is called a block body. A function body using the equals syntax is called an expression body.
Here, multiplyByThree() uses an expression body:
This is a short version of saying return x * 3 inside a block body.
Kotlin infers the return type of a function that has an expression body:
Kotlin infers that multiplyByFour() returns an Int.
Kotlin can only infer return types for expression bodies. If a function has a block body and you omit its type, that function returns Unit.
When writing functions, choose descriptive names. This makes the code easier to read, and can often reduce the need for code comments. We can't always be as descriptive as we would prefer with the function names in this book because we're constrained by line widths.
1.7 if Expressions
An if expression makes a choice.
The if keyword tests an expression to see whether it's true or false and performs an action based on the result. A true-or-false expression is called a Boolean, after the mathematician George Boole who invented the logic behind these expressions. Here's an example using the > (greater than) and < (less than) symbols:
The expression inside the parentheses after the if must evaluate to true or false. If true, the following expression is executed. To execute multiple lines, place them within curly braces.
We can create a Boolean expression in one place, and use it in another:
Because x is Boolean, the if can test it directly by saying if(x).
The Boolean >= operator returns true if the expression on the left side of the operator is greater than or equal to that on the right. Likewise, <= returns true if the expression on the left side is less than or equal to that on the right.
The else keyword allows you to handle both true and false paths:
The else keyword is only used in conjunction with if. You are not limited to a single check—you can test multiple combinations by combining else and if:
Here we use == to check two numbers for equality. != tests for inequality.
The typical pattern is to start with if, followed by as many else if clauses as you need, ending with a final else for anything that doesn't match all the previous tests. When an if expression reaches a certain size and complexity you'll probably use a when expression instead. when is described later in the book, in when Expressions.
The "not" operator ! tests for the opposite of a Boolean expression:
To verbalize if(!y), say "if not y."
The entire if is an expression, so it can produce a result:
Here, we store the value produced by the entire if expression in an intermediate identifier called result. If the condition is satisfied, the first branch produces result. If not, the else value becomes result.
Let's practice creating functions. Here's one that takes a Boolean parameter:
The Boolean parameter exp is passed to the function trueOrFalse(). If the argument is passed as an expression, such as b < 3, that expression is first evaluated and the result is passed to the function. trueOrFalse() tests exp and if the result is true, line [1] is executed, otherwise line [2] is executed.
•
[1] return says, "Leave the function and produce this value as the function's result." Notice that return can appear anywhere in a function and does not have to be at the end.
Rather than using return as in the previous example, you can use the else keyword to produce the result as an expression:
Instead of two expressions in trueOrFalse(), oneOrTheOther() is a single expression. The result of that expression is the result of the function, so the if expression becomes the function body.
1.8 String Templates
A String template is a programmatic way to generate a String.
If you put a $ before an identifier name, the String template will insert that identifier's contents into the String:
•
[1] $answer substitutes the value of answer.
•
[2] If what follows the $ isn't recognizable as a program identifier, nothing special happens.
You can also insert values into a String using concatenation (+):
Placing an expression inside ${} evaluates it. The return value is converted to a String and inserted into the resulting String:
•
[1] if(condition) 'a' else 'b' is evaluated and the result is substituted for the entire ${} expression.
When a String must include a special character, such as a quote, you can either escape that character with a (backslash), or use a Stringliteral in triple quotes:
With triple quotes, you insert a value of an expression the same way you do it for a single-quoted String.
1.9 Number Types
Different types of numbers are stored in different ways.
If you create an identifier and assign an integer value to it, Kotlin infers the Int type:
For readability, Kotlin allows underscores within numerical values.
The basic mathematical operators for numbers are the ones available in most programming languages: addition (+), subtraction (-), division (/), multiplication (*) and modulus (%), which produces the remainder from integer division:
Integer division truncates its result:
If the operation had rounded the result, the output would be 2.
The order of operations follows basic arithmetic:
The multiplication operation 5 * 6 is performed first, followed by the addition 45 + 30.
If you want 45 + 5 to happen first, use parentheses:
Now let's calculate body mass index (BMI), which is weight in kilograms divided by the square of the height in meters. If you have a BMI of less than 18.5, you are underweight. Between 18.5 and 24.9 is normal weight. BMI of 25 and higher is overweight. This example also shows the preferred formatting style when you can't fit the function's parameters on a single line:
•
[1] If you remove the parentheses, you divide weight by height then multiply that result by height. That's a much larger number, and the wrong answer.
bmiMetric() uses Doubles for the weight and height. A Double holds very large and very small floating-point numbers.
Here's a version using English units, represented by Int parameters:
Why does the result differ from bmiMetric(), which uses Doubles? When you divide an integer by another integer, Kotlin produces an integer result. The standard way to deal with the remainder during integer division is truncation, meaning "chop it off and throw it away" (there's no rounding). So if you divide 5 by 2 you get 2, and 7/10 is zero. When Kotlin calculates bmi in expression [1], it divides 160 by 68 * 68and gets zero. It then multiplies zero by 703.07 to get zero.
To avoid this problem, move 703.07 to the front of the calculation. The calculations are then forced to be Double:
The Double parameters in bmiMetric() prevent this problem. Convert computations to the desired type as early as possible to preserve accuracy.
All programming languages have limits to what they can store within an integer. Kotlin's Int type can take values between and , a constraint of the Int 32-bit representation. If you sum or multiply two Ints that are big enough, you'll overflow the result:
Int.MAX_VALUE is a predefined value which is the largest number an Int can hold.
The overflow produces a result that is clearly incorrect, as it is both negative and much smaller than we expect. Kotlin issues a warning whenever it detects a potential overflow.
Preventing overflow is your responsibility as a developer. Kotlin can't always detect overflow during compilation, and it doesn't prevent overflow because that would produce an unacceptable performance impact.
If your program contains large numbers, you can use Longs, which accommodate values from to . To define a val of type Long, you can specify the type explicitly or put L at the end of a numeric literal, which tells Kotlin to treat that value as a Long:
By changing to Longs we prevent the overflow in IntegerOverflow.kt:
Using a numeric literal in both [1] and [2] forces Long calculations, and also produces a result of type Long. The location where the Lappears is unimportant. If one of the values is Long, the resulting expression is Long.
Although they can hold much larger values than Ints, Longs still have size limitations:
Long.MAX_VALUE is the largest value a Long can hold.
1.10 Booleans
Skip....
1.11 Repetition with while
Computers are ideal for repetitive tasks.
The most basic form of repetition uses the while keyword. This repeats a block as long as the controlling Boolean expression is true:
The Boolean expression is evaluated once at the beginning of the loop and again before each further iteration through the block.
Skip...
1.12 Looping & Ranges
The for keyword executes a block of code for each value is a sequence.
The set of values can be a range of integers, a String, or, as you'll see later in the book, a collection of items. The in keyword indicates that you are stepping through values:
Each time through the loop, v is given the next element in values.
Here's a for loop repeating an action a fixed number of times:
The output shows the index i receiving each value in the range from 1 to 3.
A range is an interval of values defined by a pair of endpoints. There are two basic ways to define ranges:
•
[1] Using .. syntax includes both bounds in the resulting range.
•
[2] until excludes the end. The output shows that 10 is not part of the range.
Displaying a range produces a readable format.
This sums the numbers from 10 to 100:
You can iterate over a range in reverse order. You can also use a step value to change the interval from the default of 1:
•
[1] downTo produces a decreasing range.
•
[2] step changes the interval. Here, the range steps by a value of two instead of one.
•
[3] until can also be used with step. Notice how this affects the output.
In each case the sequence of numbers form an arithmetic progression. showRange() accepts an IntProgression parameter, which is a built-in type that includes Int ranges. Notice that the String representation of each IntProgression as it appears in output comment for each line is often different from the range passed into showRange()—the IntProgression is translating the input into an equivalent common form.
You can also produce a range of characters. This for iterates from a to z:
You can iterate over a range of elements that are whole quantities, like integers and characters, but not floating-point values.
Square brackets access characters by index. Because we start counting characters in a String at zero, s[0] selects the first character of the String s. Selecting s.lastIndex produces the final index number:
Sometimes people describe s[0] as "the zeroth character."
Characters are stored as numbers corresponding to their ASCII codes, so adding an integer to a character produces a new character corresponding to the new code value:
The second println() shows that you can compare character codes.
A for loop can iterate over Strings directly:
ch receives each character in turn.
In the following example, the function hasChar() iterates over the String s and tests whether it contains a given character ch. The return in the middle of the function stops the function when the answer is found:
The next atom shows that hasChar() is unnecessary—you can use built-in syntax instead.
If you simply want to repeat an action a fixed number of times, you may use repeat() instead of a for loop:
repeat() is a standard library function, not a keyword. You'll see how it was created much later in the book.
1.13 The in Keyword
The in keyword tests whether a value is within a range.
In Booleans, you learned to check bounds explicitly:
0 <= x && x <= 100 is logically equivalent to x in 0..100. IntelliJ IDEA suggests automatically replacing the first form with the second, which is easier to read and understand.
The in keyword is used for both iteration and membership. An in inside the control expression of a for loop means iteration, otherwise in checks membership:
The in keyword is not limited to ranges. You can also check whether a character is a part of a String. The following example uses in instead of hasChar() from the previous atom:
Later in the book you'll see that in works with other types, as well.
Here, in tests whether a character belongs to a range of characters:
•
[1] !in checks that a value doesn't belong to a range.
You can create a Double range, but you can only use it to check for membership:
Floating-point ranges can only be created using .. because until would mean excluding a floating-point number as an endpoint, which doesn't make sense.
You can check whether a String is a member of a range of Strings:
Here Kotlin uses alphabetic comparison.
1.14 Expressions & Statements
Statements and expressions are the smallest useful fragments of code in most programming languages.
There's a basic difference: a statement has an effect, but produces no result. An expression always produces a result.
Because it doesn't produce a result, a statement must change the state of its surrounding to be useful. Another way to say this is "a statement is called for its side effect" (that is, what it does other than producing a result). As a memory aid:
A statement changes state.
One definition of "express" is "to force or squeeze out," as in "to express the juice from an orange." So
An expression expresses.
That is, it produces a result.
The for loop is a statement in Kotlin. You cannot assign it because there's no result:
A for loop is used for its side effects.
An expression produces a value, which can be assigned or used as part of another expression, whereas a statement is always a top-level element.
Every function call is an expression. Even if the function returns Unit and is called only for its side effects, the result can still be assigned:
The Unit type contains a single value called Unit, which you can return directly, as seen in unitFun(). Calling println() also returns Unit. The val u1 captures the return value of println() and is explicitly declared as Unit while u2 uses type inference.
If creates an expression, so. you can assign its result:
The first output line is x < y, even though result3 isn't displayed until the end of main(). This happens because evaluating result3 calls println(), and the evaluation occurs when result3 is defined.
Notice that a is defined inside the block of code for result2. The result of the last expression becomes the result of the if expression; here, it's the sum of 11 and 42. But what about a? Once you leave the code block (move outside the curly braces), you can't access a. It is temporary and is discarded once you exit the scope of that block.
The increment operator i++ is also an expression, even if it looks like a statement. Kotlin follows the approach used by C-like languages and provides two versions of increment and decrement operators with slightly different semantics. The prefix operator appears before the operand, as in ++i, and returns the value after the increment happens. You can read it as "first do the increment, then return the resulting value." The postfix operator is placed after the operand, as in i++, and returns the value of i before the increment occurs. You can read it as "first produce the result, then do the increment."
The decrement operator also has two versions: --i and i--. Using increment and decrement operators within other expressions is discouraged because it can produce confusing code:
Try to guess what the output will be, then check it.