前言
Kotlin作为JVM系的语言,起源于Java又不同于Java。通过在语言层面比较两者的区别,可以使得开发者能够快速学习,融会贯通。
匿名对象
有时需要对某个类做轻微的改动并获取它的对象,Kotlin与Java提供了不同的支持。
- Java
在Java中提供了匿名内部类对这一需求的支持,即在初始化类的地方覆写基类的实现。
class Person
{
public void show()
{
System.out.println(“Person”);
}
}
class Main{
public void test(new Person(){ //匿名内部类
@override
show(){
//xxx
}
});
}
匿名内部类首先是内部类,是在类中定义的类,同时匿名表示没有名字,直接覆盖其实现后new出来的。匿名内部类的使用非常广泛,特别是那种需要修改某一个类的实现,且只需要这个实现的一个对象的时候。需要注意的是,在Java中,匿名内部类只能访问外部类的final变量。
Kotlin
在Kotlin中与匿名内部类对应的是对象表达式,即通过object关键字去定义与初始化匿名类。其写法结构为:
object : 基类名(主构造函数参数){
覆盖类的实现
}
例如:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
})
由于object后仅有基类的名称,故覆盖后的类是没有名字的,即匿名的。
若有多个基类,可以通过逗号分隔:
open class A(x: Int) {
public open val y: Int = x
}
interface B { …… }
val ab: A = object : A(1), B {
override val y = 15
}
若仅仅需要一个简单对象,无基类,则可以这样写:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
该匿名对象可以作为私有作用域的对象,也可以作为公有作用域的对象。前者返回的是匿名对象类型,后者返回的是匿名对象声明的基类,若无基类则返回Any类型。
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
单例对象与静态方法
- Java
单例即一个类的唯一实例,在Java中为了获得单例常会使用设计模式中的到单例模式来获得单例。
// 一种单例的实现
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
与单例类似的为静态类,但它们也有区别:
-
单例可以延迟加载或控制,而静态类在被第一次初始化时加载
-
单例可以被override,而静态类不可以
-
单例易于被测试
-
单例与静态类的回收时机不同(待补充)
-
Kotlin
在Kotlin中为了使用单例或类似于静态类,可以使用对象声明。对象声明相比于对象表达式,在写法上主要是在object后添加了类名,并且不能将该声明放在局部作用域中,但是它们可以嵌套到其他对象声明或非内部类中。其例子为:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
}
// 使用该对象,直接通过类名使用
DataProviderManager.registerDataProvider(……)
若对象声明也有基类,在其名称后添加冒号与基类即可:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
}
对于Kotlin的这一设计,是通过对象声明的名称来对方法进行调用,与Java的静态成员相比,还是略有不同。因为Java是通过外部的类名访问静态成员,而Kotlin的对象声明是通过声明的类名来访问成员。为此,Kotlin还有一个新的概念,即伴生对象。
class NumberTest {
companion object Obj {
var flag = false
fun plus(x:int, y:int): Int {... }
}
}
通过在类内部的对象声明前添加companion关键字,即为伴生对象。这个对象是属于外部类的,通过外部类的类名即可调用该对象的成员:
NumberTest.plus(1, 2)
NumberTest.flag
这就与Java中使用通过类名访问静态成员类似。
对于有名称的伴生对象,可以通过外部类名访问,访问语法为:
//外部类类名.半生对象名.方法
NumberTest.Obj.plus(1, 2)
//外部类类名.半生对象名.成员
NumberTest.Obj.flag
对于无名称的伴生对象,可以通过外部类名访问,访问语法为:
class NumberTest {
companion object Obj {
var flag = false
fun plus(x:int, y:int): Int {... }
}
}
//外部类类名.Companion.方法
NumberTest.Companion.plus(1, 2)
//外部类类名.Companion名.成员
NumberTest.Companion.flag
注意,伴生对象成员形如Java类中的静态成员,但实际上在运行时它们是真实对象的成员
对象表达式/对象声明/伴生对象的区别
对象表达式和对象声明之间有一个重要的语义差别:
- 对象表达式是在使用他们的地方立即执行(及初始化)的;
- 对象声明是在第一次被访问到时延迟初始化的;
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
网友评论