修饰符
访问修饰符
修饰符 | 相关成员 | 评注 |
---|---|---|
final | 不能被重写 | 类中成员默认使用 |
open | 可被重写 | 需要明确什么 |
abstract | 必须被重写 | 在抽象类中使用 |
override | 重写父类或接口中的成员 | 若没有final声明,重写的成员默认是open |
Java
中可以创建任意类的子类并重写任意方法,除非显示声明final
。对基类的修改会导致子类不正确的行为,即脆弱的基类问题。Effective Java
建议“要么为继承做好设计并记录文档,要么禁止。”Kotlin
采用该思想哲学,Java
中类和方法默认是open
的,而Kotlin
中类和方法默认是final
。
open class RichButtion:Clickable{
//默认是final不能被重写
fun disable(){}
//open可重写
open fun animate(){}
//override 方法默认是open
override fun click(){}
}
接口和抽象类默认是open
, 其抽象成员默认是open
abstract class Animate{
//默认是open
abstract fun animate()
//非抽象方法默认是final
fun animateTwice(){}
}
可见性修饰符
修饰符 | 类成员 | 顶层声明 |
---|---|---|
public(默认) | 所有地方可见 | 所有地方可见 |
internal | 模块中可见 | 模块中可见 |
protected | 子类中可见 | —— |
private | 类中可见 | 文件中可见 |
Java
中默认可见性——包私有,在kotlin中并没有。Kotlin
只把包作为命名空间里组织代码的一种方式,并没有将其用作可见性控制。作为替代方案,Koltin
是使用新的修饰符internal
,表示“只能在模块内可见。”internal
优势在于它对模块实现细节提供真正的封装。
接口
接口包含抽象方法的定义和非抽象方法的实现,但是他们都不能包含任何状态。
interface Clickable {
//不支持backing-field,不能存储值
var clickable: Boolean
//默认open,可被重写
fun click()
//默认final,不能被重写
fun showOff() = println("I'm Clickable")
}
由于Koltin 1.0
是Java 6
为目标设计,其并不支持接口中的默认方法,因此会把每个默认方法的接口编译成一个普通接口和一个将方法体作为静态函数的类的结合体,如上面的接口反编译后看到:
public interface Clickable {
boolean getClickable();
void setClickable(boolean var1);
void click();
void showOff();
public static final class DefaultImpls {
public static void showOff(Clickable $this) {
String var1 = "I'm Clickable";
boolean var2 = false;
System.out.println(var1);
}
}
}
构造函数
Kotlin
构造函数相对于Java
做了部分修改,区分主构造函数和从构造函数。初始化块中的代码实际上会成为主构造函数的⼀部分。委托给主构造函数会作为次构造函数的第⼀条语句,因此所有初始化块中的代码都会在次构造函数体之前执⾏。
class Person {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
在大多数场景中,类的构造函数非常简明:要么没有参数,要么直接把参数于对应的属性关联
class User(val nickname:String,val isSubscribed:Boolean=false)
如果类有主构造函数,每个从构造函数需要委托主构造函数,可直接委托或者间接委托。
class User(val nickname: String) {
var isSubscribed: Boolean?=null
constructor(_nickname: String, _isSubscribed: Boolean) : this(_nickname) {
this.isSubscribed = _isSubscribed
}
}
如何该类有父类,应该显式的调用父类的构造方法
//Clickable为接口,没有构造函数
class Buttion:Clickable{
}
//即便没有任何参数,也要显示调用父类构造函数
class RiseButton:Button(){
}
//如果有多级构造函数,可以super关键字调用父类构造
class MyButton: View {
constructor(ctx:Context):super(ctx)
constructor(ctx: Context,attributes: AttributeSet?):super(ctx,attributes)
}
内部类、嵌套类、密封类、数据类·
内部类和嵌套类
Kotlin
中嵌套类不能访问外部类的实例,类似Java
静态内部类;而Kotlin
中的内部类需要用inner
关键字修饰才能访问外部类的实例。
class Outer {
private val bar: Int = 1
//内部类
inner class Inner {
fun foo() = bar
}
}
class Outer2 {
private val bar: Int = 1
//嵌套类,不持有外部类的引用
class Nested {
fun foo() = 2
}
}
val demo = Outer().Inner().foo() // == 1
val demo2 = Outer2.Nested().foo() // == 2
密封类
密封类⽤来表⽰受限的类继承结构:当⼀个值为有限集中的类型、⽽不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在⼀个实例,⽽密封类的⼀个⼦类可以有可包含状态的多个实例。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
⼀个密封类是⾃⾝抽象的,它不能直接实例化并可以有抽象(abstract)成员。
密封类不允许有⾮-private 构造函数(其构造函数默认为 private)。
请注意,扩展密封类⼦类的类(间接继承者)可以放在任何位置,⽽⽆需在同⼀个⽂件中。
数据类
创建⼀些只保存数据的类。 在这些类中,⼀些标准函数往往是从数据机械推导⽽来的。在
Kotlin
中,这叫做 数据类 并标记为 data :
data class User(val name: String, val age: Int)
编译器⾃动从主构造函数中声明的所有属性导出以下成员:
-
equals() / hashCode()
对; -
toString()
格式是 "User(name=John, age=42)" ; -
componentN()
函数 按声明顺序对应于所有属性; - copy() 函数。
为了确保⽣成的代码的⼀致性以及有意义的⾏为,数据类必须满⾜以下要求:
- 主构造函数需要⾄少有⼀个参数;
- 主构造函数的所有参数需要标记为 val 或 var ;
- 数据类不能是抽象、开放、密封或者内部的;
属性与字段
声明一个属性的完整语法为:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可选.
一个只读属性的语法和一个可变的属性的语法有两方面的不同:
- 只读属性用
val
,而可变属性用var
声明 - 只读属性不允许有
setter
方法
默认的属性的声明为:
var name: String = "Kotlin"
get() = field
set(value) {
field = value
}
Object关键字
Object
关键字定义一个类并同时创建一个实体:
- 对象声明:定义单例的方式
- 伴生对象:可持有工厂方法及其他与类相关
- 对象表达式:代替
Java
的匿名内部类
对象表达式和对象声明之间有⼀个重要的语义差别:
- 对象表达式是在使⽤他们的地⽅⽴即执⾏(及初始化)的;
- 对象声明是在第⼀次被访问到时延迟初始 化的;
- 伴⽣对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
对象声明
对象声明将类的声明与该类的单一实例声明结合在一起。与普通类的实例不同,对象声明在定义的时候就创建了实例。
object PayRoll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary(){
}
}
可以反编译看到:
对象声明被编译成通过静态字段来持有它的单一实例的类,字段名始终为INSTANCE
public final class PayRoll {
@NotNull
private static final ArrayList allEmployees;
public static final PayRoll INSTANCE;
@NotNull
public final ArrayList getAllEmployees() {
return allEmployees;
}
public final void calculateSalary() {
}
//构造函数私有
private PayRoll() {
}
static {
PayRoll var0 = new PayRoll();
//静态代码块初始化化实例对象
INSTANCE = var0;
boolean var1 = false;
allEmployees = new ArrayList();
}
}
伴生对象
Java
的static
关键字并不是kotlin
的一部分,作为替代,kotlin
依赖包级别的函数和对象声明,但是顶层函数不能访问类的私有成员, 需要写一个没有类实例情况下调用但需要访问类内部的函数,可以将其写为类中的对象声明的成员。
fun getFacebookName(accountId: Int) = "fb:$accountId"
class User private constructor(val nickname: String) {
companion object {
fun newSubscribingUser(email: String) =
User(email.substringBefore('@'))
fun newFacebookUser(accountId: Int) =
User(getFacebookName(accountId))
}
}
fun main(args: Array<String>) {
val subscribingUser = User.newSubscribingUser("bob@gmail.com")
val facebookUser = User.newFacebookUser(4)
println(subscribingUser.nickname)
}
伴生对象作为普通对象,一样可以实现接口和扩展函数和属性
data class Person(val name: String) {
object NameComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int =
p1.name.compareTo(p2.name)
}
}
class Person(val firstname:String,val lastname:String){
companion object{
//...可空,但不能省略
}
}
fun Person.Companion.fromJson(json:String):String{
return json.substring(4)
}
对象表达式
object不仅可用来声明单例对象,还可以声明匿名对象,替代java内部类的用法
fab.setOnClickListener(
object : View.OnClickListener {
override fun onClick(view: View?) {
//....
}
})
当然,也可以将其存储到一个变量中:
val listener = object : View.OnClickListener {
override fun onClick(p0: View?) {
//....
}
}
Java
匿名内部类只能扩展一类或者实现一个接口,kotlin
的匿名对象可以实现多个接口或者实现不同的接口。