Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
Kotlin 中使用关键字 class 声明类,后面紧跟类名:
class Test{ //类名是Test
//类体
}
//定义空类
class Empty
//在类中构造成员函数
class Test{
fun foo (){ ... } //成员函数
}实例:把小数转换成分数
class Fraction(var number:Float){
private var numerator:Long = 0 //分子
private var denominator:Long = 0 //分母
fun deal(){
var array: List= this.number.toString().split(".")
numerator=array[1].toLong()
denominator=Math.pow(10.0,array[1].length.toDouble()).toLong()
var g=gcd(numerator,denominator);//求最大公约数
println("${numerator/g}/${denominator/g}")
}
fun gcd(a:Long,b:Long):Long{
//a,b算常量不能直接参与运算
var c=a;
var d=b;
var r:Long=1L
if(cvar temp=c
c=d
c=temp
}
r=c%d
while(r!=0L){
var temp=r
r=d%r
d=temp
}
return d
}
}fun main(args: Array
) {
var myClass=Fraction(0.618f);
myClass.deal()
}
类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
class Student{
var name:String = ...
var age:Int = ...
}创建实例
val s = Student() //kotlin中没有new关键字使用属性,只要用名称引用即可
s.name //使用 . 来引用
Kotlin中的类可以有一个主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后:
class Person constructor(firstName: String){ }
如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。
即:class Person(firstName: String){ }getter和setter方法
属性声明的完整语法:
var[: ] [= ]
[]
[]
getter 和 setter 都是可选
如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的。
var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1 // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int? // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1 // 类型为 Int 类型,默认实现 getter
实例:
class Person{
var lastname: String="zhang"
get() = fileld.toUpperCase() //将变量赋值后转换为大写
var num: Int=100
get() = field //后端变量
set(value){
if(value<10){ //如果传入的值小于 10 返回该值
field=value
}else{
field=-1 //如果传入的值大于等于 10 返回-1
}
}
var height: Float = 145.4f
private set
}
fun main(args: Array){
var person: Person = Person()
person.lastName = "wang"
println("lastName:${person.lastName}")
person.num = 9
println("num:${person.num}")
person.num = 20
println("num:${person.num}")
}
输出:lastName:WANG
num:9
num:-1
Kotlin 中类不能有字段。提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,field 关键词只能用于属性的访问器,如以上实例;
非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用 lateinit 关键字描述属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
类的修饰符包括 classModifier 和_accessModifier_:
classModifier: 类属性修饰符,标示类本身特性
abstract //抽象类
final //类不可继承,默认属性
enum //枚举类
open //类可继承,默认是final
annotation //注解类accessModifier: 访问权限修饰符
private //仅在同一个文件中可见
protected //同一个文件和它所有子类中可见
public //都可见,默认类型
internal //同一个模块中可见实例:
// 文件名:example.kt
package foo
private fun foo() {} // 在 example.kt 内可见
public var bar: Int = 5 // 该属性随处可见
internal val baz = 6 // 相同模块内可见java中的访问修饰符,默认default,kotlin没有
public protected default private 同一类中的成员 是 是 是 是 同一包中的成员 是 是 是 否 不同包中的子类 是 是 否 否 不同包中的非子类 是 否 否 否
主构造器:不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
class Person constructor(firstName: String){
init{
println("firstName is $firstName")
}
}
实例:
class Person public constructor(name: String,age: Int)/*主构造函数,和它参数*/{
var name:String //属性
var age :Int
init { //初始化器,主构造函数的函数体,能使用主构造函数的参数
this.name = name
this.age = age
}//成员函数
fun say(){
println("I am ${name} and I was ${age} years old")
}
}
注意:主构造器的参数可以在初始化代码段中使用,也可以在类主体n定义的属性初始化代码中使用。 一种简洁语法,可以通过主构造器来定义属性并初始化属性值(可以是var或val):
//这种var或val修饰参数可以直接初始化
class People(val firstName: String, val lastName: String){
//...
}
如果构造器有注解,或者有可见度修饰符,这时constructor关键字是必须的,注解和修饰符要放在它之前。
实例:
class Person constructor(val name: String){
var age: Int = 18
var country: String = "CN"
var pname = name
init{
println("初始化")
}
fun printTest(){
println("我是类的函数")
}
}
fun main(args: Array){
val person = Person("wang")
println(person.pname)
prinln(person.age)
println(person.country)
person.printTest()
}
输出:初始化
wang
18
CN
我是类的函数次构造器:类也可以有二级构造函数,需要加前缀 constructor;
如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}
如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
class DontCreateMe private constructor () { }
实例:
class Person constructor(val name: String){
var age: Int = 18
var country: String = "CN"
var pname = name
init{
println("初始化")
}
//次构造函数
constructor(name: String, sex: String): this(name){
println("性别:$sex")
}
fun printTest(){
println("我是类的函数")
}
}
fun main(args: Array){
val person = Person("wang", "男")
println(person.pname)
prinln(person.age)
println(person.country)
person.printTest()
}
输出:初始化
性别:男
wang
18
CN
我是类的函数
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。
注意:无需对抽象类或抽象成员标注open注解。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
class Outer{ //外部类
private val bar: Int = 1
class Nested{ //嵌套类,不支持外部类的引用
fun foo() = 2
}
}
fun main(args: Array){
val demo = Outer.Nested().foo() //调用格式:外部类.嵌套类.嵌套类方法/属性
println(demo) //输出:2
}
内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
class Outer{ //外部类
private val bar: Int = 1
var v = "成员属性"
/**嵌套内部类**/
inner class Inner{
fun foo() = bar //访问外部类成员
fun innerTest(){
var o = this@Outer //获得外部类成员变量
println("内部类可以引用外部类的成员,例如:" + o.v)
}
}
}
fun main(args: Array){
val demo = Outer.Inner.foo()
println(demo) //输出:1
val demo2 = Outer().Inner().innerTest()
println(demo2) //输出:内部类可以引用外部类的成员,例如:成员属性
}
为了消除歧义,要访问来自外部作用域的 this,我们使用this@label,其中 @label 是一个 代指 this 来源的标签。
使用对象表达式来创建匿名内部类:
class Test{
var v = "成员属性"
fun setInterFace(test: TestInterFace){
test.test()
}
}
/**
* 定义接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array) {
var test = Test()
/**
* 采用对象表达式来创建接口对象,即匿名内部类的实例。
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式创建匿名内部类的实例")
}
})
}
Kotlin 可以创建一个只包含数据的类,关键字为 data:
data class User(val name:String, val age:Int)
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
equals() / hashCode()
toString() 格式如“User(name="wang", age=18)”
componentN() functions对应于属性,按声明顺序排列
copy()函数如果这些函数在类中已经被明确定义了,或者从超类中继承而来,就不再会生成。
为了保证生成代码的一致性以及有意义,数据类需要满足以下条件:
1.主构造函数至少包括一个参数
2.所有的主构造函数的参数必须标识为val或var
;
3.数据类不可以声明为不可以声明为abstract,open,sealed或inner
4.数据类不能继承其他类但可以实现接口复制:复制使用 copy() 函数,我们可以使用该函数复制对象并修改部分属性, 对于上文的 User 类,其实现会类似下面这样:
fun copy(name: String = this.name, age: Int=this.age)=User(name, age)实例:
data class User(val name:String, val age:Int)
fun main(args: Array){
val jack=User(name="jack", age=18)
val olderJack = jack.copy(age = 2)
println(jack)
println(olderJack)
}
输出:User(name=Jack, age=1)
User(name=Jack, age=2)数据类以及解构声明:组件函数允许数据类在解构声明中使用
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类 的一个子类可以有可包含状态的多个实例。
声明一个密封类,使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
使用密封类的关键好处在于使用 when 表达式 的时候,如果能够 验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了