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