7.9 单例模式(Singleton)与伴生对象(companion object)
7.9.1 单例模式(Singleton)
单例模式很常用。它是一种常用的软件设计模式。例如,Spring中的Bean默认就是单例。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。
我们用Java实现一个简单的单例类的代码如下:
1. class Singleton {
2. private static Singleton instance;
4. private Singleton() {}
6. public static Singleton getInstance() {
7. if (instance == null) {
8. instance = new Singleton();
9. }
10. return instance;
11. }
12. }
测试代码:
1. Singleton singleton1 = Singleton.getInstance();
可以看出,我们先在单例类中声明了一个私有静态的Singleton instance变量,然后声明一个私有构造函数private Singleton() {}, 这个私有构造函数使得外部无法直接通过new的方式来构建对象:
1. Singleton singleton2 = new Singleton(); //error, cannot private access
最后提供一个public的获取当前类的唯一实例的静态方法getInstance()。我们这里给出的是一个简单的单例类,是线程不安全的。
7.9.2 object对象
Kotlin中没有 静态属性和方法,但是也提供了实现类似于单例的功能,我们可以使用关键字 object 声明一个object对象:
1. object AdminUser {
2. val username: String = "admin"
3. val password: String = "admin"
4. fun getTimestamp() = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
5. fun md5Password() = EncoderByMd5(password + getTimestamp())
6. }
测试代码:
1. val adminUser = AdminUser.username
2. val adminPassword = AdminUser.md5Password()
3. println(adminUser) // admin
4. println(adminPassword) // g+0yLfaPVYxUf6TMIdXFXw==,这个值具体运行时会变化
为了方便在REPL中演示说明,我们再写一个示例代码:
1. >>> object User {
2. ... val username: String = "admin"
3. ... val password: String = "admin"
4. ... }
object对象只能通过对象名字来访问:
1. >>> User.username
2. admin
3. >>> User.password
4. admin
不能像下面这样使用构造函数:
1. >>> val u = User()
2. error: expression 'User' of type 'Line130.User' cannot be invoked as a function. The function 'invoke()' is not found
3. val u = User()
4. ^
为了更加直观的了解object对象的概念,我们把上面的object User的代码反编译成Java代码:
1. public final class User {
2. @NotNull
3. private static final String username = "admin";
4. @NotNull
5. private static final String password = "admin";
6. public static final User INSTANCE;
8. @NotNull
9. public final String getUsername() {
10. return username;
11. }
13. @NotNull
14. public final String getPassword() {
15. return password;
16. }
18. private User() {
19. INSTANCE = (User)this;
20. username = "admin";
21. password = "admin";
22. }
24. static {
25. new User();
26. }
27. }
从上面的反编译代码,我们可以直观了解Kotlin的object背后的一些原理。
7.9.3 嵌套(Nested)object对象
这个object对象还可以放到一个类里面:
1. class DataProcessor {
2. fun process() {
3. println("Process Data")
4. }
6. object FileUtils {
7. val userHome = "/Users/jack/"
9. fun getFileContent(file: String): String {
10. var content = ""
11. val f = File(file)
12. f.forEachLine { content = content + it + "\n" }
13. return content
14. }
15. }
16. }
测试代码:
1. DataProcessor.FileUtils.userHome // /Users/jack/
2. DataProcessor.FileUtils.getFileContent("test.data") // 输出文件的内容
同样的,我们只能通过类的名称来直接访问object,不能使用对象实例引用。下面的写法是错误的:
1. val dp = DataProcessor()
2. dp.FileUtils.userHome // error, Nested object FileUtils cannot access object via reference
我们在Java中通常会写一些Utils类,这样的类我们在Kotlin中就可以直接使用object对象:
1. object HttpUtils {
2. val client = OkHttpClient()
4. @Throws(Exception::class)
5. fun getSync(url: String): String? {
6. val request = Request.Builder()
7. .url(url)
8. .build()
10. val response = client.newCall(request).execute()
11. if (!response.isSuccessful()) throw IOException("Unexpected code " + response)
13. val responseHeaders = response.headers()
14. for (i in 0..responseHeaders.size() - 1) {
15. println(responseHeaders.name(i) + ": " + responseHeaders.value(i))
16. }
17. return response.body()?.string()
18. }
20. @Throws(Exception::class)
21. fun getAsync(url: String) {
22. var result: String? = ""
24. val request = Request.Builder()
25. .url(url)
26. .build()
27. client.newCall(request).enqueue(object : Callback {
28. override fun onFailure(call: Call, e: IOException?) {
29. e?.printStackTrace()
30. }
32. @Throws(IOException::class)
33. override fun onResponse(call: Call, response: Response) {
34. if (!response.isSuccessful()) throw IOException("Unexpected code " + response)
36. val responseHeaders = response.headers()
37. for (i in 0..responseHeaders.size() - 1) {
38. println(responseHeaders.name(i) + ": " + responseHeaders.value(i))
39. }
40. result = response.body()?.string()
41. println(result)
42. }
43. })
44. }
45. }
测试代码:
1. val url = "http://www.baidu.com"
2. val html1 = HttpUtils.getSync(url) // 同步get
3. println("html1=${html1}")
4. HttpUtils.getAsync(url) // 异步get
7.9.4 匿名object
还有,在代码行内,有时候我们需要的仅仅是一个简单的对象,我们这个时候就可以使用下面的匿名object的方式:
1. fun distance(x: Double, y: Double): Double {
2. val porigin = object {
3. var x = 0.0
4. var y = 0.0
5. }
6. return Math.sqrt((x - porigin.x) * (x - porigin.x) + (y - porigin.y) * (y - porigin.y))
7. }
测试代码:
1. distance(3.0, 4.0)
需要注意的是,匿名对象只可以用在本地和私有作用域中声明的类型。代码示例:
1. class AnonymousObjectType {
2. // 私有函数,返回的是匿名object类型
3. private fun privateFoo() = object {
4. val x: String = "x"
5. }
7. // 公有函数,返回的类型是 Any
8. fun publicFoo() = object {
9. val x: String = "x" // 无法访问到
10. }
12. fun test() {
13. val x1 = privateFoo().x // Works
14. //val x2 = publicFoo().x // ERROR: Unresolved reference 'x'
15. }
16. }
18. fun main(args: Array<String>) {
19. AnonymousObjectType().publicFoo().x // Unresolved reference 'x'
20. }
跟 Java 匿名内部类类似,object对象表达式中的代码可以访问来自包含它的作用域的变量(与 Java 不同的是,这不限于 final 变量):
1. fun countCompare() {
2. var list = mutableListOf(1, 4, 3, 7, 11, 9, 10, 20)
3. var countCompare = 0
4. Collections.sort(list, object : Comparator<Int> {
5. override fun compare(o1: Int, o2: Int): Int {
6. countCompare++
7. println("countCompare=$countCompare")
8. println(list)
9. return o1.compareTo(o2)
10. }
11. })
12. }
测试代码:
1. countCompare()
3. countCompare=1
4. [1, 4, 3, 7, 11, 9, 10, 20]
5. ...
6. countCompare=17
7. [1, 3, 4, 7, 9, 10, 11, 20]
7.9.5 伴生对象(companion object)
Kotlin中还提供了 伴生对象 ,用companion object关键字声明:
1. class DataProcessor {
2. fun process() {
3. println("Process Data")
4. }
6. object FileUtils {
7. val userHome = "/Users/jack/"
9. fun getFileContent(file: String): String {
10. var content = ""
11. val f = File(file)
12. f.forEachLine { content = content + it + "\n" }
13. return content
14. }
15. }
17. companion object StringUtils {
18. fun isEmpty(s: String): Boolean {
19. return s.isEmpty()
20. }
21. }
22. }
一个类只能有1个伴生对象。也就是是下面的写法是错误的:
1. class ClassA {
2. companion object Factory {
3. fun create(): ClassA = ClassA()
4. }
6. companion object Factory2 { // error, only 1 companion object is allowed per class
7. fun create(): MyClass = MyClass()
8. }
9. }
一个类的伴生对象默认引用名是Companion:
1. class ClassB {
2. companion object {
3. fun create(): ClassB = ClassB()
4. fun get() = "Hi, I am CompanyB"
5. }
6. }
我们可以直接像在Java静态类中使用静态方法一样使用一个类的伴生对象的函数,属性(但是在运行时,它们依旧是实体的实例成员):
1. ClassB.Companion.index
2. ClassB.Companion.create()
3. ClassB.Companion.get()
其中, Companion可以省略不写:
1. ClassB.index
2. ClassB.create()
3. ClassB.get()
当然,我们也可以指定伴生对象的名称:
1. class ClassC {
2. var index = 0
3. fun get(index: Int): Int {
4. return 0
5. }
7. companion object CompanyC {
8. fun create(): ClassC = ClassC()
9. fun get() = "Hi, I am CompanyC"
10. }
11. }
测试代码:
1. ClassC.index
2. ClassC.create()// com.easy.kotli.ClassC@7440e464,具体运行值会变化
3. ClassC.get() // Hi, I am CompanyC
4. ClassC.CompanyC.index
5. ClassC.CompanyC.create()
6. ClassC.CompanyC.get()
伴生对象的初始化是在相应的类被加载解析时,与 Java 静态初始化器的语义相匹配。
即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员。而且,还可以实现接口:
1. interface BeanFactory<T> {
2. fun create(): T
3. }
5. class MyClass {
6. companion object : BeanFactory<MyClass> {
7. override fun create(): MyClass {
8. println("MyClass Created!")
9. return MyClass()
10. }
11. }
12. }
测试代码:
1. MyClass.create() // "MyClass Created!"
2. MyClass.Companion.create() // "MyClass Created!"
另外,如果想使用Java中的静态成员和静态方法的话,我们可以用:
@JvmField注解:生成与该属性相同的静态字段 @JvmStatic注解:在单例对象和伴生对象中生成对应的静态方法
