3.1. Create collections in Kotlin

Kotlin didn’t adopt his own collection class,Standard Java collection classes are used

The sample

  // Create a collection
  fun setCollection(a){
    val set= hashSetOf(1.7.53)
    / / create the list
    val list= arrayListOf(1.7.53)
  
    / / create a map
    // To is not a special structure, but a general function
    val map= hashMapOf(1 to "one".7 to "seven".53 to "fifty_three")
  
    for(number in set){
        println(number)
    }
    for(  (index,number)   in map){
        println("$index.$number")}// Find the type of the object
    println(set.javaClass)
    println(list.javaClass)
    println(map.javaClass)
    //class java.util.HashSet
    //class java.util.ArrayList
    //class java.util.HashMap
  
    // Get the last element of the element
    println("Element Last element:${list.last()}")
    println("Maximum element value:${list.max()}")}Copy the code

3.2. Make the function easier to call

Named parameters

  • example

     // Custom print toString
     fun <T> joinToString(collection:Collection<T>,separator:String,prefix:String,postfix:String):String{
     val result=StringBuilder(prefix)
     
     for((index,element) in collection.withIndex()){
         if(index>0)
             result.append(separator)
         result.append(element)
     }
     result.append(postfix)
     
     return result.toString()
     }
    Copy the code
  • When calling a function defined by Kotlin, it is possible to specify the names of some parameters explicitly. If a function is named when calling, it needs to specify the names of all subsequent parameters to avoid confusion

     println(joinToString(list,prefix = "&",postfix = "&",separator ="." ))
    Copy the code

Default Parameter Value

  • In Kotlin, you can specify default values for parameters when you declare a function

  • The default value of the argument is encoded into the function being called, not where it is called

  • example

     // Custom print toString, default parameter value
     fun <T> joinToString2(collection:Collection<T>,separator:String=",",prefix:String="",postfix:String=""):String{
     val result=StringBuilder(prefix)
     
     for((index,element) in collection.withIndex()){
         if(index>0)
             result.append(separator)
         result.append(element)
     }
     result.append(postfix)
     
     return result.toString()
     }
    Copy the code

Eliminate static utility classes: top-level functions and properties

  • In Kotlin, there is no need for static utility classes, and functions can be placed directly at the top of the code file without belonging to any class

  • These functions placed at the top of the file are still members of the package

  • Properties can also be placed at the top of the file

  • The top function

    • Declare joinToString() as the top-level function

      packageFunction definition and call// The top-level function is declared outside the class
      fun joinToString(name:String):String{
          return "helloworld"
      }
      class Join {}Copy the code
    • How does this work? When compiling this file will generate some kind of, because the JVM can only execute code in the class, when you were in the use of Kotlin, know these is enough, if you need to call these functions from Java, it is necessary to understand how it will be compiled, in order to facilitate understanding, let’s look at a piece of code, it will be compiled into the same class here

      packageIii. Function definition and call;public class JoinKt {
      public static String joinToString(String name){
         return "helloworld"; }}Copy the code
    • You can see the name of the class generated by Kotlin’s compilation, which corresponds to the name of the file containing the function. All top-level functions in this file are compiled as static functions of the class. Therefore, when calling this function from Java, it is as simple as calling any other static function

       public class JavaTest {
       public void test(){
          JoinKt.joinToString("heloworld"); }}Copy the code
    • All top-level functions are compiled as static functions of this class

  • Change the file class name

    • To change the name of the generated class that contains Kotlin’s top-level function, annotate the file with @jVMName at the beginning of the file, in front of the package name

       @file:JvmName("StringFunctions")
       packageFunction definition and call// The top-level function is declared outside the class
       fun joinToString(name:String):String{
           return "helloworld"
       }
       class Join {}Copy the code
  • Called in Java

     public class JavaTest {
     public void test(){
          StringFunctions.joinToString("heloworld"); }}Copy the code
  • The top attributes

    • Like functions, attributes can be placed at the top of a file

      Keeping individual pieces of data outside of a class is uncommon, but still valuable

    • code

       @file:JvmName("StringFunctions")
       packageFunction definition and call// The top-level function is declared outside the class
       fun joinToString(name:String):String{
           return "helloworld"
       }
       // Top-level attributes declared outside the class
       //const val opCount=0
       var opCount=0
       
       class Join {
           fun performOperation(a){
               // The top-level attribute is referenced in the method
               opCount++
               println("Current count value:$opCount")}}Copy the code
    • By default, the top-level property, like any other property, is exposed to Java for use through accessors (a single getter for val, a pair of getters and setters for var).

      • The const keyword

        • For ease of use, if you want to expose a constant to Java as a public static final property, use const (this applies to all properties of primitive data types, as well as strings). Static final does not have a getter, so the const modifier is used to provide a getter. Maybe get rid of the getter, and then access the property directly.)

          // Top-level attributes declared outside the class
          const val  opCount=0
          Copy the code

          Const must precede val and cannot be modified by var. It is equivalent to: public static final int opCount=0;

3.3. Add methods to other people’s classes: extension functions and attributes

The introduction

  • The title is obvious: add some extended functions to a class already written, add some of your own functions or attributes to the existing base

Using the step

  • Extension functions are defined outside the class
  • Function declaration format:
    1. The name of the function is preceded by the class to be extended. For example, in this case, the String class that extends Java is preceded by the String.
    2. This is the receiver object used to call the extension function

code

  • packageFunction definition and call/** * Note: extended functions are defined outside the class */
    fun String.lastChar(a):Char=this.get(this.length-1)
    
    /** * Extended class */
    class ExtendUtils {
     fun test(a){
           println("Kotlin".lastChar())
       }
     }
    Copy the code

    String indicates the recipient type, and “Kotlin” indicates the recipient object

  • Self-written extension functions can be written in any.kt file:

    1. Functions are declared outside the class;
    2. The function name is preceded by the type String of the extension class.
    3. Receiver object this

    This adds an extra function to the String class that other classes can use directly, as well as Java classes

Pay attention to

  • In extension functions, you can directly access other methods and properties of the extended class
  • Extension functions do not allow you to break their encapsulation and access private or protected members

Import and extend functions

  • If you define an extension function, it does not automatically apply across the scope of the project. Instead, if you want to use it, you need to import it, just like any other class or function

  • To avoid accidental naming conflicts, Kotlin allows you to import individual functions using the same syntax as importing classes

     importFunction definition and call lastCharval c="Kotlin".lastChar()
    Copy the code
  • Use the keyword as to change the name of the imported class or function

     importFunction definition and call lastCharas last
     val c="Kotlin".last()
    Copy the code

    When you have functions with the same name in different packages, it is necessary to rename them at import time so that they can be used in the same file. In this case, for general classes and functions, there is an option to identify the class or function by its full name

Call extension functions from Java

  • When called by Java, the Kotlin file ExtendUtils becomes ExtendUtilsKt, with a Kt suffix:

     ExtendUtilsKt.lastChar("Ha ha");
    Copy the code

Utility functions as extension functions

  • Extension functions are nothing more than efficient syntactic candy for static functions that can use a more specific type as the receiver type rather than a class

An extension function that cannot be overridden

  • The static nature of extension functions means that they cannot be overridden by subclasses
  • Kotlin treats extension functions as static functions

Extended attributes

  • Extended properties provide a way to extend a class’s API to access properties using property syntax instead of function syntax

  • example

     val String.lastChar:Char get() =get(length-1)
    Copy the code
  • As you can see, like the extension function, the extension property is just like a normal member property of the receiver.

  • Here, getter functions must be defined because there are no supported fields, so there is no implementation of the default getter. Likewise, initialization cannot be done because there is no place to store the initial value

  • If you define the same attribute on StringBuilder, you can set it to var because the contents of StringBuilder are mutable

     // Declare a mutable extension property
     var StringBuilder.lastChar:Char 
     get() =get(length-1)
     set(value:Char) {this.setCharAt(length-1,value)
     }
    Copy the code
     // It can be accessed as if using member attributes
     println("Kotlin".lastChar())
     val sb=StringBuilder("Kotlin?")
     sb.lastChar='! '
     println(sb)
     
     >>
     n
     Kotlin!
    Copy the code
  • Pay attention to

    When accessing an extended property from Java, you should explicitly call its getter: stringutilkt.getlastchar (“Java”)

3.4. Processing collections: mutable arguments, infix calls, and library support

An API that extends Java collections

  • The premise for this chapter is that the Kotlin collection is the same as the Java class, but with an extension to the API. You can look at an example of getting the last element in a list and finding the maximum value in a collection of numbers:

     val strings:List<String> = listOf("first"."second"."fourteenth")
     println(strings.last())
     
     val numbers:Collection<Int> = setOf(1.14.2)
     println(numbers.max())
     
     fourteenth
     14
    Copy the code
  • The answer to why there are so many rich operations on sets in Kotlin is obvious: because they are declared as extension functions. Many extension functions are declared in the Kotlin library

Mutable arguments: Make a function support any number of arguments

  • When you call a function to create a list, you can pass any number of arguments to it:

    var list=listOf(2.3.5.7.11)
    Copy the code
  • Look at the declaration of this function in the library

    fun listOf<T>(vararg values:T):List<T>{... }Copy the code
  • Kotlin mutable parameters are similar to Java, but the syntax is slightly different: Instead of using three dots after the type, Kotlin uses the vararg modifier on the parameters

  • Expansion operator

    • In Java, arrays can be passed as is, whereas Kotlin requires that you explicitly unpack arrays so that each array element can be called as a separate argument in a function. Technically, this feature is calledThe expansion operator, when used, precedes the corresponding argument with a *

    This example shows that by expanding the operator, you can combine values from an array with some fixed values in a single call, which is not supported in Java

Handling of key-value pairs: infix calls and destruct declarations

  • You can create a map using the mapOf function

    val map=mapOf(1 to "one".7 to "seven".53 to "fifty-three")
    Copy the code
  • The word to is not a built-in construct, but a special kind of function call called an infix call

  • In infix calls, no additional separators are added and the function name is placed directly between the target object name and the parameter. The following two calls are equivalent

    1.to("one")
    Copy the code

    The general call to function

     1To "one"Copy the code

    Call the to function with the infix symbol

  • Infix calls can be used with functions that take only one argument, either plain functions or extension functions. To allow functions to be called using infix symbols, you need to mark them with the infix modifier

    Here is a simple declaration of the to function

     infix fun Any.to(other:Any)=Pair(this,other)
    Copy the code

    The to function returns an object of type Pair. Pair is a Kotlin library class that represents a Pair of elements

    Two variables can be initialized directly from the contents of a Pair

    val (number,name) = 1 to "one"
    Copy the code

    Kotlin allows multiple variables to be declared at once. This technique is called a destruct declaration, which shows how it can be used with Pair:

    • 1 to “one” creates a Pair, which is then expanded with a destruct declaration and assigned to the number and name variables, respectively

    • The destruct declaration feature is not only used for pairs, but also to initialize two variables using the map’s key and value contents

       for((index,element) in collection.withIndex()){
       if(index>0)
           result.append(separator)
       result.append(element)
       }
      Copy the code
    • The to function is an extension function that can create a pair of any elements, which means it is an extension of the generic receiver: you can write 1 to “one”, “one” to 1, list to list.size(), and so on

    • Look at the declaration of the mapOf function

      fun  <K,V>  mapOf(vararg values:Pair<K,V>):Map<K,V>
      Copy the code

      Like listOf, mapOf accepts a variable number of arguments, except that the arguments are key-value pairs

3.5 processing of strings and regular expressions

Kotlin strings are exactly the same as Java strings

You can pass a string created in Kotlin code to any Java function, and you can apply any Kotlin library function to a string received from Java code, without converting, without creating additional wrapper objects

Kotlin makes standard Java strings easier to use by providing a series of useful extension functions

It also hides some obscure functions and adds extensions that are clearer and easier to use

Split string

  • Kotlin provides some overloaded extension functions with different arguments called split. The value used to carry a regular expression requires a Regex, not a String, to ensure that when a String is passed to these functions, it is not treated as a regular expression

  • A dot or dash is used to separate the string

    // Explicitly create a regular expression
    println(12.345 6. "A".split("\ \. | -".toRegex()))
    [12.345.6, A]
    Copy the code
    • Kotlin uses exactly the same regular expression syntax as in Java

    • The pattern here matches a dot (which we escape to indicate that we mean a literal, not a wildcard) or a dash

    • In Kotlin, the extension function toRegex is used to convert a string into a regular expression

  • For some simple cases, regular expressions are not needed, and other overloading of the split extension function in Kotlin supports any number of plain text string separators:

      println(12.345 6. "A".split("."."-"))
    Copy the code

Regular expression and triple quoted string

  • Example: Resolve the full path name of a file to the corresponding component: directory, filename, and extension. There are two ways to do this

  • 1. Use extension functions to process strings. The Kotlin library contains functions that can be used to get substrings before (or after) the first (or last) occurrence of a given delimiter

    /** * use the String extension function to resolve the file path */
    fun parsePath(path: String) {
    // The string before the last slash
    val directory = path.substringBeforeLast("/")
    // String after the last slash
    val fullName = path.substringAfterLast("/")
    
    val fileName = fullName.substringBeforeLast(".")
    val extension = fullName.substringAfterLast(".")
    
    println("Dir:$directory,name:$fileName,ext:$extension")}Copy the code

    Path The part of the string before the last slash is the directory path; The part after the dot is the extension of the file; And the file name is somewhere in between

    Parsing strings is much easier in Kotlin, and regular expressions are not required, which is very powerful

  • 2. Use regular expressions

    • You can also use regular expressions from the Kotlin standard library

        /** * use regular expressions to resolve file paths */
          fun parsePath2(path:String){
         
          val regex="" "(. +)/(. +) \. (. +) "" ".toRegex()
          val matchResult=regex.matchEntire(path)
          if(matchResult! =null) {// Destruct declaration is used here
              val (directory,filename,extension)=matchResult.destructured
              println("Dir:$directory,name:$filename,ext:$extension")}}Copy the code
      // Use regular expressions to resolve file paths
      parsePath2("/User/yole/kotlin-book/chapter.adoc")
      
      - Dir:/User/yole/kotlin-book,name:chapter,ext:adoc
      Copy the code
    • The regular expression is written in a triple-quoted string. In such a string, no characters need to be escaped, including backslashes, so it can be used. Instead of \. To represent dots, as you would with a normal string literal

    • This regular expression divides a path into three groups separated by slashes and dots. The. Pattern matches from the beginning of the string, so the first group (.+) contains the substring before the last slash. This substring contains all the preceding slashes because they match the “any character” pattern, similarly, the second group contains the substring before the last dot, and the third group contains the rest

A multi-line, triple-quoted string

  • The purpose of the triple quoted string is not only to avoid escaping characters, but also to allow it to contain any character, including line breaks, and to provide an easier way to embed text containing line breaks into programs

  • For example, you can draw something with ASCII code

     // A multi-line triple-quoted string
     val kotlinLogo="" |" / /. | / /. | / \ "" "
     println(kotlinLogo.trimMargin("."))
    Copy the code
  • A triple quoted string can contain a newline without special characters, such as \n, or escaping the \ character

  • Because multi-line strings do not support escape sequences, if you need to use the literal dollar sign in the content of the string, you must use an embedded expression like this:

    val price="" "${} '$' 99.9 "" "
    Copy the code
  • For better formatting, use the trimMargin function

3.6 make your code cleaner: local functions and extensions

Local function

  • To make your code cleaner, you can nest these extracted functions in functions so that you get the structure you want without any additional syntax overhead

  • example

    • The saveUser function is used to save the user information to the database and ensure that the User object contains valid data

    • code

        /** * functions with duplicate code */
          fun saveUser(user:User){
          if(user.name.isEmpty()){
              throw IllegalArgumentException("Can't save user ${user.id}:empty Name")}if(user.address.isEmpty()){
              throw IllegalArgumentException("Can't save user ${user.id}:empty Address")}}Copy the code
        // Function with duplicate code
        saveUser(User(1."".""))
      Copy the code

      There’s very little duplication here, and you probably don’t want to validate every special case of a user field in an all-encompassing method in the class

    • However, you can do this if you put your validation code in local functions to get rid of duplication and keep your code structure clean

        /** * extract local functions to avoid duplication */
         fun saveUser2(user:User){
      
         // Declare a local function to validate all fields
         fun validate(user:User,value:String,fieldName:String){
             if(value.isEmpty()){
                 throw IllegalArgumentException("Can't save user ${user.id}:empty $fieldName")}}// Call local functions to validate specific fields
         validate(user,user.name,"Name")
         validate(user,user.address,"Address")
      
         // Save user to database
         }
      Copy the code
    • Local functions that can access all parameters and variables in the function, we can remove the User argument

      /** * Access the argument */ of the outer function in the local function
         fun saveUser3(user: User) {
      
         // Now you don't need to repeat the user argument in saveUser3
         fun validate(value: String, fieldName: String) {
             if (value.isEmpty()) {
                 // You can access the arguments of external functions directly
                 throw IllegalArgumentException("Can't save user ${user.id}:empty $fieldName")
             }
         }
         validate(user.name, "Name")
         validate(user.address, "Address")
      
         // Save user to database
         }
      Copy the code
    • Continue to improve by putting the validation logic in the User class extension function

      packageFunction definition and callimport java.lang.IllegalArgumentException
      
      /** * extract logic to extension function */
      fun User.validateBeforeSave(a){
      fun validate(value:String,fieldName:String){
         if (value.isEmpty()) {
             // The User attributes can be accessed directly
             throw IllegalArgumentException("Can't save user $id:empty $fieldName")
         }
      }
      validate(name, "Name")
      validate(address, "Address")}class User(val id:Int.val name:String,val address:String) {
      }
      Copy the code
      /** * Save user information */
      fun saveUser4(user:User){
      // Call the extension function
      user.validateBeforeSave()
      // Save user to database
      }
      
      saveUser4(User(1."".""))
      Copy the code
    • Extension functions can also be declared as local functions, but deeply nested local functions are often confusing, so multi-layer nesting is generally not recommended

3.7, summary

Instead of defining its own collection class, Kotlin provides a richer API based on the Java collection class

Kotlin can define default values for function parameters, which greatly reduces the need for overloaded functions, and named parameters make calls to multi-parameter functions more readable

Kotlin allows for a more flexible code structure: functions and attributes can be declared directly in a file, not just as members in a class

Kotlin can extend the API of any class, including those defined in an external library, with extension functions and attributes, without modifying its source code and without runtime overhead

Infix calls provide a concise syntax for call-like operator methods that handle a single parameter

Kotlin provides a number of string-friendly functions for both regular strings and regular expressions

Triple-quoted strings provide a succinct way around the verbose escaping and string concatenation that Java normally requires

Local functions help you keep your code clean while avoiding duplication

Attachment:

Chapter 3 function definition and call. SVG

Kotlin’s learning journey has begun

Chapter 1 Kotlin: Definition and Purpose

Chapter 2 Kotlin foundation