Tái cấu trúc sangKotlin

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách chuyển đổi mã từ Java thành Kotlin. Bạn cũng sẽ tìm hiểu quy ước ngôn ngữ Kotlin là gì và cách đảm bảo rằng mã bạn đang viết tuân theo các quy ước đó.

Lớp học lập trình này phù hợp với mọi nhà phát triển sử dụng Java đang cân nhắc việc di chuyển dự án của họ sang Kotlin. Chúng ta sẽ bắt đầu bằng một số lớp Java mà bạn sẽ chuyển đổi sang Kotlin bằng IDE. Sau đó, chúng tôi sẽ xem xét mã đã chuyển đổi và xem cách chúng tôi có thể cải thiện mã bằng cách làm cho mã trở nên giống mạch hơn và tránh các lỗi phổ biến.

Kiến thức bạn sẽ học được

Bạn sẽ tìm hiểu cách chuyển đổi Java thành Kotlin. Để làm như vậy, bạn sẽ tìm hiểu các tính năng và khái niệm về ngôn ngữ Kotlin sau đây:

  • Xử lý tính chất rỗng
  • Triển khai singleton
  • Lớp dữ liệu
  • Xử lý chuỗi
  • Toán tử Elvis
  • Hủy cấu trúc
  • Thuộc tính và thuộc tính sao lưu
  • Đối số mặc định và thông số được đặt tên
  • Làm việc với tập hợp
  • Hàm mở rộng
  • Hàm và thông số cấp cao nhất
  • let, apply, withrun từ khoá

Các giả định

Bạn hẳn đã quen thuộc với Java.

Bạn cần có

Tạo dự án mới

Nếu bạn đang sử dụng IntelliJ IDEA, hãy tạo một dự án Java mới bằng Kotlin/JVM.

Nếu bạn đang sử dụng Android Studio, hãy tạo một dự án mới không có Hoạt động.

Chúng ta sẽ tạo một đối tượng mô hình User và một lớp singleton Repository hoạt động với các đối tượng User và hiển thị danh sách người dùng cũng như tên người dùng được định dạng.

Tạo một tệp mới có tên là User.java trong app/java/<yourpackagename> và dán mã sau đây:

public class User {

    @Nullable
    private String firstName;
    @Nullable
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

Tùy thuộc vào loại dự án, hãy nhập androidx.annotation.Nullable nếu bạn sử dụng một dự án Android hoặc org.jetbrains.annotations.Nullable.

Tạo một tệp mới có tên là Repository.java và dán mã sau:

import java.util.ArrayList;
import java.util.List;

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository();
                }
            }
        }
        return INSTANCE;
    }

    // keeping the constructor private to enforce the usage of getInstance
    private Repository() {

        User user1 = new User("Jane", "");
        User user2 = new User("John", null);
        User user3 = new User("Anne", "Doe");

        users = new ArrayList();
        users.add(user1);
        users.add(user2);
        users.add(user3);
    }

    public List<User> getUsers() {
        return users;
    }

    public List<String> getFormattedUserNames() {
        List<String> userNames = new ArrayList<>(users.size());
        for (User user : users) {
            String name;

            if (user.getLastName() != null) {
                if (user.getFirstName() != null) {
                    name = user.getFirstName() + " " + user.getLastName();
                } else {
                    name = user.getLastName();
                }
            } else if (user.getFirstName() != null) {
                name = user.getFirstName();
            } else {
                name = "Unknown";
            }
            userNames.add(name);
        }
        return userNames;
    }
}

IDE của chúng ta có thể tự động tái cấu trúc mã Java thành mã Kotlin, nhưng đôi khi, nó sẽ cần trợ giúp. Chúng tôi sẽ làm điều này trước, sau đó xem qua mã đã tái cấu trúc để hiểu cách và vì sao mã này được tái cấu trúc theo cách này.

Chuyển đến tệp User.java và chuyển đổi tệp đó sang Kotlin: Thanh trình đơn -> Code -> Chuyển đổi tệp Java thành Kotlin.

Nếu IDE của bạn nhắc sửa lỗi sau khi chuyển đổi, hãy nhấn vào .

Bạn sẽ thấy mã Kotlin sau:

class User(var firstName: String?, var lastName: String?)

Lưu ý rằng User.java đã được đổi tên thành User.kt. Tệp Kotlin có đuôi .kt.

Trong lớp Java User, chúng ta có 2 thuộc tính: firstNamelastName. Mỗi phương thức có một phương thức getter và setter, làm cho giá trị của phương thức này có thể thay đổi. Từ khóa Kotlin\39; cho các biến có thể thay đổi là var, vì vậy, trình chuyển đổi sử dụng var cho mỗi thuộc tính trong số này. Nếu thuộc tính Java của chúng ta chỉ có phương thức getter, các thuộc tính này sẽ không thể thay đổi và sẽ được khai báo là biến val. val tương tự như từ khóa final trong Java.

Một trong những điểm khác biệt chính giữa Kotlin và Java là Kotlin xác định rõ việc một biến có thể chấp nhận một giá trị rỗng hay không. Lệnh này được thực hiện bằng cách thêm "?` vào phần khai báo loại.

Vì chúng tôi đánh dấu firstNamelastName là có thể có giá trị null, nên trình chuyển đổi tự động đã tự động đánh dấu các thuộc tính là có thể có giá trị null với String?. Nếu bạn chú thích các thành phần Java của mình là không có giá trị null (bằng org.jetbrains.annotations.NotNull hoặc androidx.annotation.NonNull), thì trình chuyển đổi sẽ nhận ra điều này và đặt cả các trường có giá trị null trong Kotlin.

Việc tái cấu trúc cơ bản đã hoàn tất. Nhưng chúng ta có thể viết câu này theo cách riêng hơn. Hãy xem cách thực hiện.

Lớp dữ liệu

Lớp User của chúng ta chỉ lưu giữ dữ liệu. Kotlin có một từ khóa cho các lớp có vai trò này: data. Khi bạn đánh dấu lớp này là lớp data, trình biên dịch sẽ tự động tạo các phương thức getter và setter cho chúng ta. Thao tác này cũng sẽ lấy hàm equals(), hashCode()toString().

Hãy thêm từ khóa data vào lớp User của chúng tôi:

data class User(var firstName: String, var lastName: String)

Kotlin, giống như Java, có thể có một hàm dựng chính và một hoặc nhiều hàm dựng phụ. Ví dụ trong ví dụ trên là hàm dựng chính của lớp User. Nếu bạn đang chuyển đổi một lớp Java có nhiều hàm dựng, thì trình chuyển đổi cũng sẽ tự động tạo nhiều hàm dựng trong Kotlin. Các quảng cáo này được xác định bằng từ khóa constructor.

Nếu muốn tạo một thực thể của lớp này, chúng ta có thể làm như sau:

val user1 = User("Jane", "Doe")

Sự bình đẳng

Kotlin có 2 loại đẳng thức:

  • đẳng thức cấu trúc sử dụng toán tử == và gọi equals() để xác định xem hai thực thể có bằng nhau hay không.
  • Bình đẳng tham chiếu sử dụng toán tử === và kiểm tra xem hai tham chiếu có trỏ đến cùng một đối tượng hay không.

Các thuộc tính được xác định trong hàm khởi tạo chính của lớp dữ liệu sẽ được dùng để kiểm tra đẳng thức cấu trúc.

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

Trong Kotlin, chúng ta có thể chỉ định các giá trị mặc định cho các đối số trong lệnh gọi hàm. Giá trị mặc định được dùng khi đối số này bị bỏ qua. Trong Kotlin, hàm dựng cũng là các hàm nên chúng ta có thể dùng các đối số mặc định để chỉ định rằng giá trị mặc định của lastNamenull. Để làm việc này, chúng ta chỉ cần chỉ định null cho lastName.

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")

Các tham số hàm có thể được đặt tên khi gọi hàm:

val john = User(firstName = "John", lastName = "Doe") 

Trong một trường hợp sử dụng khác, giả sử firstNamenull là giá trị mặc định và lastName thì không. Trong trường hợp này, vì thông số mặc định sẽ đứng trước tham số không có giá trị mặc định, nên bạn sẽ phải gọi hàm có các đối số được đặt tên:

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")

Trước khi tiếp tục, hãy đảm bảo rằng lớp User của bạn là lớp data. Hãy chuyển đổi lớp Repository thành Kotlin. Kết quả chuyển đổi tự động sẽ có dạng như sau:

import java.util.*

class Repository private constructor() {
   private var users: MutableList<User?>? = null
   fun getUsers(): List<User?>? {
       return users
   }

   val formattedUserNames: List<String?>
       get() {
           val userNames: MutableList<String?> =
               ArrayList(users!!.size)
           for (user in users) {
               var name: String
               name = if (user!!.lastName != null) {
                   if (user!!.firstName != null) {
                       user!!.firstName + " " + user!!.lastName
                   } else {
                       user!!.lastName
                   }
               } else if (user!!.firstName != null) {
                   user!!.firstName
               } else {
                   "Unknown"
               }
               userNames.add(name)
           }
           return userNames
       }

   companion object {
       private var INSTANCE: Repository? = null
       val instance: Repository?
           get() {
               if (INSTANCE == null) {
                   synchronized(Repository::class.java) {
                       if (INSTANCE == null) {
                           INSTANCE =
                               Repository()
                       }
                   }
               }
               return INSTANCE
           }
   }

   // keeping the constructor private to enforce the usage of getInstance
   init {
       val user1 = User("Jane", "")
       val user2 = User("John", null)
       val user3 = User("Anne", "Doe")
       users = ArrayList<Any?>()
       users.add(user1)
       users.add(user2)
       users.add(user3)
   }
}

Hãy xem hành động chuyển đổi tự động đã thực hiện như sau:

  • Đã thêm một khối init (Repository.kt#L50)
  • Trường static giờ đây đã trở thành một phần của khối companion object (Repository.kt#L33)
  • Danh sách users có thể có giá trị null vì đối tượng này không được tạo bản sao vào thời điểm khai báo (Repository.kt#L7)
  • Phương thức getFormattedUserNames() hiện là một thuộc tính có tên là formattedUserNames (Repository.kt#L11)
  • Việc lặp qua danh sách người dùng (ban đầu là một phần của getFormattedUserNames() ) có một cú pháp khác với cú pháp của Java (Repository.kt#L15)

Trước khi đi sâu hơn, hãy xử lý mã này một chút. Chúng ta có thể thấy rằng trình chuyển đổi đã tạo users danh sách có thể thay đổi để lưu giữ các đối tượng có thể có giá trị null. Mặc dù danh sách thực sự có thể có giá trị null, nhưng hãy nói rằng danh sách đó không thể chứa người dùng rỗng. Vì vậy, hãy làm như sau:

  • Xóa ? trong User? trong phần khai báo loại users
  • getUsers sẽ trả về List<User>?

Trình chuyển đổi tự động cũng cần chia thành 2 dòng khai báo biến cho các biến người dùng và các biến được xác định trong khối init. Hãy đặt mỗi khai báo biến trên một dòng. Mã của chúng tôi sẽ trông như sau:

class Repository private constructor() {
    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
        return users
    }

    val formattedUserNames: List<String?>
        get() {
            val userNames: MutableList<String?> =
                ArrayList(users!!.size)
            for (user in users) {
                var name: String
                name = if (user!!.lastName != null) {
                    if (user!!.firstName != null) {
                        user!!.firstName + " " + user!!.lastName
                    } else {
                        user!!.lastName
                    }
                } else if (user!!.firstName != null) {
                    user!!.firstName
                } else {
                    "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    companion object {
        private var INSTANCE: Repository? = null
        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE =
                                Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Khối init

Trong Kotlin, hàm dựng chính không thể chứa bất kỳ mã nào, vì vậy mã khởi tạo sẽ được đặt trong các khối init. Chức năng giống nhau.

class Repository private constructor() {
    ...
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Phần lớn mã init xử lý các quá trình khởi tạo thuộc tính. Bạn cũng có thể thực hiện việc này trong khai báo thuộc tính. Ví dụ: trong phiên bản Kotlin của lớp Repository, chúng ta thấy rằng thuộc tính người dùng đã được khởi tạo trong phần khai báo.

private var users: MutableList<User>? = null

Kotlin<39;s static thuộc tính và phương thức

Trong Java, chúng ta dùng từ khóa static cho các trường hoặc hàm để nói rằng chúng thuộc về một lớp nhưng không thuộc về một thực thể của lớp. Đây là lý do chúng tôi đã tạo trường tĩnh INSTANCE trong lớp Repository của mình. Kotlin tương đương với khối companion object này. Tại đây, bạn cũng sẽ khai báo các trường tĩnh và hàm tĩnh. Trình chuyển đổi đã tạo và di chuyển trường INSTANCE tại đây.

Xử lý singleton

Vì chỉ cần một phiên bản của lớp Repository, nên chúng ta đã dùng mẫu singleton trong Java. Với Kotlin, bạn có thể thực thi mẫu này ở cấp trình biên dịch bằng cách thay thế từ khóa class bằng object.

Xóa hàm dựng riêng và đối tượng companion và thay thế định nghĩa lớp bằng object Repository.

object Repository {

    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
       return users
    }

    val formattedUserNames: List<String>
        get() {
            val userNames: MutableList<String> =
                ArrayList(users!!.size)
        for (user in users) {
            var name: String
            name = if (user!!.lastName != null) {
                if (user!!.firstName != null) {
                    user!!.firstName + " " + user!!.lastName
                } else {
                    user!!.lastName
                }
            } else if (user!!.firstName != null) {
                user!!.firstName
            } else {
                "Unknown"
            }
            userNames.add(name)
       }
       return userNames
   }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Khi sử dụng lớp object, chúng ta chỉ cần gọi các hàm và thuộc tính ngay trên đối tượng, như trong ví dụ sau:

val users = Repository.users

Hủy cấu trúc

Kotlin cho phép hủy cấu trúc một đối tượng thành một số biến bằng cách dùng cú pháp có tên là khai báo cấu trúc. Chúng ta tạo nhiều biến và có thể sử dụng chúng độc lập.

Ví dụ: các lớp dữ liệu hỗ trợ việc hủy cấu trúc nên chúng ta có thể định cấu trúc đối tượng User trong vòng lặp for thành (firstName, lastName). Điều này cho phép chúng tôi làm việc trực tiếp với các giá trị firstNamelastName. Hãy cập nhật vòng lặp for như sau:

 
for ((firstName, lastName) in users!!) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } else {
                name = lastName
          }
       } else if (firstName != null) {
            name = firstName
       } else {
            name = "Unknown"
       }
       userNames.add(name)
}

Khi chuyển đổi lớp Repository thành Kotlin, trình chuyển đổi tự động đã đặt danh sách người dùng là có thể có giá trị null, vì nó không được khởi tạo thành một đối tượng khi được khai báo. Tất cả các cách sử dụng đối tượng users sẽ sử dụng toán tử xác nhận không có giá trị !!. Biến này chuyển đổi mọi biến thành loại không có giá trị null và gửi một ngoại lệ nếu giá trị đó là null. Khi sử dụng !!, bạn sẽ có nguy cơ bị loại trừ trong thời gian chạy.

Thay vào đó, hãy xử lý tính chất rỗng bằng một trong những phương thức sau:

  • Kiểm tra biến null ( if (users != null) {...} )
  • Dùng toán tử elvis ?: (có trong lớp học lập trình)
  • Sử dụng một số hàm chuẩn của Kotlin (ở phần sau trong lớp học lập trình)

Trong trường hợp của chúng ta, chúng ta biết rằng danh sách người dùng không cần có thể có giá trị null, vì nó được khởi tạo ngay sau khi đối tượng được tạo, vì vậy chúng ta có thể tạo thực thể đối tượng trực tiếp khi khai báo đối tượng.

Khi tạo phiên bản của các loại bộ sưu tập, Kotlin cung cấp một số chức năng trợ giúp để giúp mã của bạn dễ đọc và linh hoạt hơn. Ở đây chúng tôi đang sử dụng MutableList cho users:

private var users: MutableList<User>? = null

Để đơn giản hóa, chúng ta có thể dùng hàm mutableListOf(), cung cấp loại thành phần danh sách, xóa lệnh gọi hàm ArrayList khỏi khối init, đồng thời xóa phần khai báo loại rõ ràng của thuộc tính users.

private val users = mutableListOf<User>()

Chúng tôi cũng thay đổi var thành val vì người dùng sẽ chứa tham chiếu không thể thay đổi đối với danh sách người dùng. Xin lưu ý rằng tệp đối chiếu không thể thay đổi nhưng danh sách lại có thể thay đổi (bạn có thể thêm hoặc xóa các phần tử).

Với những thay đổi này, tài sản users của chúng tôi hiện sẽ không có giá trị null và chúng tôi có thể xóa tất cả các lần xuất hiện của toán tử !! không cần thiết.

val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
    ...
}

Ngoài ra, vì biến người dùng đã được khởi tạo, chúng ta phải xóa quy trình khởi tạo khỏi khối init:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    users.add(user1)
    users.add(user2)
    users.add(user3)
}

Vì cả lastNamefirstName đều có thể là null, nên chúng tôi cần xử lý biến null khi tạo danh sách các tên người dùng được định dạng. Vì muốn hiển thị "Unknown" nếu thiếu một trong hai tên, chúng ta có thể đặt tên không có giá trị null bằng cách xóa ? khỏi phần khai báo loại.

val name: String

Nếu lastName có giá trị null, name sẽ là firstName hoặc "Unknown":

if (lastName != null) {
    if (firstName != null) {
        name = "$firstName $lastName"
    } else {
        name = lastName
    }
} else if (firstName != null) {
    name = firstName
} else {
    name = "Unknown"
}

Bạn có thể viết toán tử này theo cách đặc trưng hơn bằng cách sử dụng toán tử elvis ?:. Toán tử elvis sẽ trả về biểu thức ở bên trái nếu biểu thức đó không có giá trị null hoặc biểu thức ở bên phải nếu bên trái rỗng.

Vì vậy, trong mã sau đây, user.firstName được trả về nếu không có giá trị null. Nếu user.firstName rỗng, biểu thức trả về giá trị ở bên phải , "Unknown":

if (lastName != null) {
    ...
} else {
    name = firstName ?: "Unknown"
}

Kotlin giúp bạn dễ dàng làm việc với String bằng các mẫu String. Mẫu chuỗi cho phép bạn tham chiếu đến các biến bên trong phần khai báo chuỗi.

Trình chuyển đổi tự động đã cập nhật chuỗi nối của họ và tên để tham chiếu đến tên biến ngay trong chuỗi bằng cách sử dụng biểu tượng $ và đặt biểu thức giữa { } .

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

Trong mã, hãy thay thế Nối chuỗi bằng:

name = "$firstName $lastName"

Trong Kotlin, if, when, forwhile là các biểu thức – chúng trả về một giá trị. IDE của bạn thậm chí còn hiển thị cảnh báo rằng việc bỏ bài tập này khỏi if:

Hãy làm theo đề xuất của IDE\39; và bỏ bài tập này cho cả hai câu lệnh if. Dòng cuối cùng của câu lệnh if sẽ được chỉ định. Như vậy, rõ ràng hơn, mục đích duy nhất của quy tắc chặn này là khởi tạo giá trị tên:

name = if (lastName != null) {
    if (firstName != null) {
        "$firstName $lastName"
    } else {
        lastName
    }
} else {
   firstName ?: "Unknown"
}

Tiếp theo, chúng tôi sẽ nhận được cảnh báo rằng bạn có thể tham gia phần khai báo name với bài tập. Chúng ta cũng áp dụng điều này. Vì loại biến tên có thể được suy luận, nên chúng ta có thể xóa phần khai báo loại rõ ràng. Giờ đây, formattedUserNames của chúng tôi sẽ có dạng như sau:

val formattedUserNames: List<String?>
   get() {
       val userNames: MutableList<String?> = ArrayList(users.size)
       for ((firstName, lastName) in users) {
           val name = if (lastName != null) {
               if (firstName != null) {
                   "$firstName $lastName"
               } else {
                   lastName
               }
           } else {
               firstName ?: "Unknown"
           }
           userNames.add(name)
       }
       return userNames
   }

Hãy cùng tìm hiểu kỹ hơn về phương thức getter trong formattedUserNames và xem cách chúng ta có thể làm cho nó đặc trưng hơn. Hiện tại, mã có chức năng như sau:

  • Tạo danh sách chuỗi mới
  • Lặp lại theo danh sách người dùng
  • Tạo tên được định dạng cho mỗi người dùng, dựa trên họ và tên của người dùng
  • Trả về danh sách mới tạo
val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }

                userNames.add(name)
            }
            return userNames
        }

Kotlin cung cấp danh sách đầy đủ các biến đổi bộ sưu tập giúp phát triển nhanh hơn và an toàn hơn bằng cách mở rộng khả năng của API bộ sưu tập Java. Một trong những hàm đó là hàm map. Hàm này trả về một danh sách mới có chứa kết quả áp dụng hàm chuyển đổi đã cho cho mỗi phần tử trong danh sách ban đầu. Vì vậy, thay vì tạo danh sách mới và lặp lại theo danh sách người dùng theo cách thủ công, chúng ta có thể dùng hàm map và di chuyển logic mà chúng ta đã có trong vòng lặp for bên trong phần nội dung map. Theo mặc định, tên của mục danh sách hiện tại được sử dụng trong mapit, nhưng để dễ đọc, bạn có thể thay thế it bằng tên biến của riêng mình. Trong trường hợp của chúng ta, hãy đặt tên là user:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                val name = if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
                name
            }
        }

Để đơn giản hơn nữa, chúng ta có thể xóa hoàn toàn biến name:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

Chúng tôi thấy rằng trình chuyển đổi tự động đã thay thế hàm getFormattedUserNames() bằng một thuộc tính có tên là formattedUserNames có một phương thức getter tùy chỉnh. Về cơ bản, Kotlin vẫn tạo phương thức getFormattedUserNames() để trả về List.

Trong Java, chúng ta sẽ hiển thị các thuộc tính lớp thông qua các hàm getter và setter. Kotlin cho phép chúng ta phân biệt các thuộc tính của một lớp một cách chính xác hơn, thể hiện bằng các trường và hàm, những thao tác mà một lớp có thể làm, được biểu thị bằng các hàm. Trong trường hợp của chúng ta, lớp Repository rất đơn giản và không thực hiện bất kỳ thao tác nào để lớp đó chỉ có các trường.

Logic được kích hoạt trong hàm getFormattedUserNames() Java hiện được kích hoạt khi gọi phương thức getter của thuộc tính formattedUserNames Kotlin.

Mặc dù chúng ta không có trường nào tương ứng với thuộc tính formattedUserNames, nhưng Kotlin sẽ cung cấp cho chúng ta một trường sao lưu tự động có tên là field mà chúng ta có thể truy cập nếu cần từ các phương thức getter và setter tùy chỉnh.

Tuy nhiên, đôi khi, chúng tôi muốn có thêm một số chức năng mà trường sao lưu tự động không cung cấp. Hãy xem ví dụ dưới đây.

Bên trong lớp Repository, chúng ta có một danh sách người dùng có thể thay đổi đang hiển thị trong hàm getUsers() được tạo từ mã Java:

fun getUsers(): List<User>? {
    return users
}

Vấn đề ở đây là bằng cách trả về users bất kỳ người dùng nào trong lớp Kho lưu trữ có thể sửa đổi danh sách người dùng của chúng ta – đây không phải là một ý tưởng tốt! Hãy khắc phục điều này bằng cách sử dụng thuộc tính hỗ trợ.

Trước tiên, hãy đổi tên users thành _users. Bây giờ, hãy thêm một thuộc tính không thể thay đổi công khai trả về danh sách người dùng. Hãy gọi đó là users:

private val _users = mutableListOf<User>()
val users: List<User>
      get() = _users

Với thay đổi này, thuộc tính _users riêng tư trở thành thuộc tính dự phòng cho thuộc tính users công khai. Bên ngoài lớp Repository, bạn không thể sửa đổi danh sách _users vì những người tiêu dùng của lớp học chỉ có thể truy cập vào danh sách này thông qua users.

Mã đầy đủ:

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

Giờ đây, lớp Repository biết cách tính tên người dùng được định dạng cho đối tượng User. Tuy nhiên, nếu muốn sử dụng lại cùng một logic định dạng trong các lớp khác, chúng ta cần sao chép và dán logic đó hoặc chuyển sang lớp User.

Kotlin cung cấp khả năng khai báo các hàm và thuộc tính bên ngoài lớp, đối tượng hoặc giao diện. Ví dụ: hàm mutableListOf() mà chúng ta dùng để tạo một thực thể mới của List được xác định trực tiếp trong Collections.kt từ Thư viện chuẩn.

Trong Java, bất cứ khi nào bạn cần một chức năng tiện ích nào đó, rất có thể bạn sẽ tạo một lớp Util và khai báo hàm đó dưới dạng một hàm tĩnh. Trong Kotlin, bạn có thể khai báo các hàm cấp cao nhất mà không cần có lớp. Tuy nhiên, Kotlin cũng cho phép bạn tạo hàm mở rộng. Đây là các hàm mở rộng một loại nhất định nhưng được khai báo bên ngoài loại. Như vậy, họ sẽ có chung sở thích với loại đó.

Khả năng hiển thị các hàm và thuộc tính của tiện ích có thể bị hạn chế bằng cách sử dụng từ khóa xác định mức độ hiển thị. Các thao tác này chỉ hạn chế việc sử dụng đối với các lớp cần tiện ích và không làm giảm vùng chứa tên.

Đối với lớp User, chúng ta có thể thêm một hàm mở rộng để tính toán tên được định dạng hoặc chúng ta có thể giữ tên được định dạng trong một thuộc tính của phần mở rộng. Bạn có thể thêm tệp này vào bên ngoài lớp Repository trong cùng một tệp:

// extension function
fun User.getFormattedName(): String {
    return if (lastName != null) {
        if (firstName != null) {
            "$firstName $lastName"
        } else {
            lastName ?: "Unknown"
        }
    } else {
        firstName ?: "Unknown"
    }
}

// extension property
val User.userFormattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName

Sau đó, chúng ta có thể dùng các hàm và thuộc tính của phần mở rộng như thể chúng thuộc lớp User.

Vì tên được định dạng là thuộc tính của người dùng chứ không phải là chức năng của lớp Repository, nên hãy sử dụng thuộc tính tiện ích. Tệp Repository của chúng tôi giờ đây có dạng như sau:

val User.formattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
      get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user -> user.formattedName }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

Thư viện tiêu chuẩn Kotlin sử dụng các hàm mở rộng để mở rộng chức năng của một số API Java; nhiều chức năng trên IterableCollection được triển khai dưới dạng hàm mở rộng. Ví dụ: hàm map mà chúng ta đã dùng trong bước trước đó là hàm mở rộng của Iterable.

Trong mã lớp Repository, chúng ta đang thêm một số đối tượng người dùng vào danh sách _users. Những lệnh gọi này có thể trở nên nổi bật hơn với sự trợ giúp của các hàm phạm vi.

Để chỉ thực thi mã trong bối cảnh của một đối tượng cụ thể mà không cần truy cập vào đối tượng dựa trên tên, Kotlin đã tạo 5 hàm phạm vi: let, apply, with, runalso. Ngắn gọn và mạnh mẽ, tất cả những hàm này đều có bộ nhận (this), có thể có một đối số (it) và có thể trả về một giá trị. Bạn sẽ quyết định dùng cookie nào tùy thuộc vào mục tiêu bạn muốn đạt được.

Sau đây là bản tóm tắt hữu ích giúp bạn ghi nhớ điều này:

Vì chúng ta đang định cấu hình đối tượng _users trong Repository, nên chúng ta có thể làm cho mã này nổi bật hơn bằng cách sử dụng hàm apply:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")
   
    _users.apply {
       // this == _users
       add(user1)
       add(user2)
       add(user3)
    }
 }

Trong lớp học lập trình này, chúng tôi đã đề cập đến những kiến thức cơ bản mà bạn cần để bắt đầu tái cấu trúc mã từ Java sang Kotlin. Việc tái cấu trúc này độc lập với nền tảng phát triển của bạn và giúp đảm bảo rằng mã bạn viết là đặc trưng.

Kotlin đặc trưng giúp mã viết ngắn gọn và dễ hiểu. Với tất cả các tính năng mà Kotlin cung cấp, có rất nhiều cách để mã của bạn an toàn hơn, súc tích hơn và dễ đọc hơn. Ví dụ: chúng ta thậm chí có thể tối ưu hoá lớp Repository của mình bằng cách tạo danh sách _users với người dùng ngay trong phần khai báo, loại bỏ khối init:

private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))

Chúng tôi đã đề cập đến nhiều chủ đề, từ việc xử lý biến null, singleton, chuỗi và bộ sưu tập cho đến các chủ đề như hàm mở rộng, hàm cấp cao nhất, thuộc tính và hàm phạm vi. Chúng ta đã chuyển từ 2 lớp Java thành 2 lớp trong Kotlin, bây giờ có dạng như sau:

User.kt

class User(var firstName: String?, var lastName: String?)

Repository.kt

val User.formattedName: String
    get() {
       return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() = _users.map { user -> user.formattedName }
}

Đây là tệp TL; DR của các chức năng Java và việc liên kết các hàm đó với Kotlin:

Java

Kotlin

Đối tượng final

Đối tượng val

equals()

==

==

===

Lớp chỉ lưu giữ dữ liệu

data lớp

Khởi tạo trong hàm dựng

Khởi chạy trong khối init

static trường và hàm

các trường và hàm được khai báo trong companion object

Lớp Singleton

object

Để tìm hiểu thêm về Kotlin và cách sử dụng Kotlin trên nền tảng của bạn, hãy xem các tài nguyên sau: