1、类与对象
Kotlin中类的声明和Java类一样,都是用关键字class
来声明类
class Person {
var name = ""
var age = 0
fun introduce() {
Log.e("TAG", "我的名字叫" + name + " 我已经" + age + "岁了")
}
}
Person类已经定义完成,那如何创建Person类的对象呢?
val p = Person()
和Java类似,只是剩去了new
,这样也很好理解:Person()
代表的是构造函数,而只有创建对象时才会调用构造函数,这样编码的意图也很明显就是创建对象。
接下来我们就能使用对象进行一些操作:
fun createPerson() {
val p = Person()
p.age=100
p.name="张丹"
p.introduce()
}
2、继承与构造函数
- 1、在Kotlin中
非抽象类、非接口
默认是不能被继承的,要想某个类能被继承,就需要使用关键字open
。
open class Person{
}
- 2、使用
:
来表示继承关系
class Student :Person() {
}
细心的你肯定也发现了Person后加了一对小括号,这又是为什么呢?这就和Kotlin中的主构造函数和次构造函数
有关系了,
2.1、主构造函数
- 1、Kotlin中每个类都默认有一个无参的构造函数来作为主构造函数,而且主构造函数有且仅有一个,次构造函数的个数不限。
- 2、我们也可以给主构造函数显示的指定参数
class Student(val sno: String, val grade: Int) : Person() {
}
我们将学号和年级在主构造函数中声明了,所以在创建Student
对象的时候就必须传入主构造函数中要求的所有参数。
- 3、主构造函数没有方法体,直接在类名后定义即可。
如果我想在主构造函数中写一些逻辑该怎么办呢?Kotlin给我们提供了init
结构体,所有主构造函数中逻辑可以写在结构体中。
class Student(val sno: String, val grade: Int) : Person() {
init {
//在创建Student对象时就会执行这个结构体
Log.e("TAG","我是主构造函数,学号sno:"+sno+" 年级grade:"+grade)
}
}
- 4、在java中子类的构造函数必须要调用父类的构造函数,在Kotlin中也是如此。上面在继承Person时Person后面加了一对括号的用处就是在创建
Student
对象时指定在Student
的构造函数中调用父类的那个构造函数。
下面我们对Person修改一下,将name和age放在主构造函数中
open class Person(val name: String, val age: Int) {
}
此时Student类肯定会报错,因为在Student类中我们指定调用父类的无参构造,而无参构造已经不存在了,必须给Person类传入name和age。可是在Student中并没有name和age,我们可以在Student中加入name和age
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
}
- 5、注意:在Student类的主构造函数中加入的name和age不能使用val或var修饰,因为在主构造函数中使用val或var修饰的参数将自动变成该类的字段,这就导致和父类中的name、age同名导致的冲突。
2.2、次构造函数
- 1、其实在Kotlin中我们一般用不到次构造函数,因为Kotlin中提供了给函数设定参数默认值的功能,基本可以替代次构造函数。
- 2、一个类只有一个主构造函数,但是可以有多个次构造函数。和主构造函数不同的是,次构造函数是有方法体的。
- 3、Kotlin规定:当一个类中既有主构造函数又有次构造函数时,所有的次构造函数必须调用主构造函数(包含间接调用)
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
init {
Log.e("TAG", "我是主构造函数,学号sno:" + sno + " 年级grade:" + grade)
}
constructor(name:String,age:Int) : this("",0,name,age) {
}
constructor():this("",0){
}
}
- 4、次构造函数是通过关键字
constructor
定义的,这里我们定义了两个次构造函数:第一个次构造函数传入name和age作为参数,然后通过this
关键字调用了主构造函数;第二个次构造函数没有参数,它通过this
关键字调用了第一个次构造函数间接的调用了柱构造函数,这也是合法的。现在我们就有3种方式创建Student
的实例了
val stu1=Student("张无忌",50)
stu1.introduce()
val stu2=Student()
stu2.introduce()
val stu3=Student("10010",5,"尹力",130)
stu3.introduce()
- 5、如果一个类中没有显式的定义主构造函数,而使用关键字
constructor
定义了次构造函数,那么类中是没有主构造函数的
class Teacher : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}
注意这里代码的变化,Teacher
类中没有显式的定义主构造函数并且定义了次构造函数,所以Teacher
类是没有主构造函数,由于没有主构造函数,所以在继承Person
类时也就不需要加扩号,原因很简单:由于Teacher
类没有主构造函数,所以我们不能通过主构造函数来创建Teacher
的实例,即使Kotlin允许此时添加括号,也是无法达到调用父类构造的目的。我们只能通过次构造函数创建实例,我们只能通过super
来调用父类的构造函数。
3、接口
Kotlin中和Java一样都是单继承的,一个类只能继承一个父类,但是可以实现多个接口。和Java一样也是通过interface
声明接口
interface Study {
fun readBook()
fun doHomeWork()
}
Student
类实现Study
的接口,并实现其中的方法
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age),Study {
init {
Log.e("TAG", "我是主构造函数,学号sno:" + sno + " 年级grade:" + grade)
}
constructor(name: String, age: Int) : this("", 0, name, age) {
}
constructor() : this("", 0) {
}
override fun readBook() {
}
override fun doHomeWork() {
}
}
- 1、Kotlin中实现接口和集成一样都是使用:,类和接口间使用,隔开,另外接口的后面不需要加括号,因为接口根本就没有构造可以被调用。
- 2、Kotlin中使用关键字
override
来重写父类的方法或实现接口中的方法,另外如果父类中的方法需要被重新的话,需要在方法前增加关键字open
,否则无法重写。 - 3、Kotlin中的接口可以有默认的实现,接口中有默认实现的方法子类可以不实现。
interface Study {
fun readBook(){
Log.e("TAG","接口中的方法可以默认实现")
}
fun doHomeWork()
}
4、修饰符
在Kotlin中函数的可见性修饰符相比于Java变动还是很大的。直接上表
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认修饰符) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包下的类可见 | 当前类、子类可见 |
default | 当前类、统一包下类可见 | 无 |
internal | 无 | 同一模块下可见 |
5、单例和数据类
5.1、数据类
数据类通常要重写equals()、hashCode()、toString()
几个方法,其中eaquals()
判断两个数据类是否相等,hashCode()
作为eqauls()
的配套方法,也需要重写否则会导致HashMap、HashSet
中相关hash
的操作不能正常工作,toString()
是为了提供了一个清晰的日志输出,否则打印出来就是内存地址。下面举个简单的例子:
public class CellPhone {
String brand;
double price;
public CellPhone(String brand, double price) {
this.brand = brand;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (o instanceof CellPhone) {
CellPhone cellPhone = (CellPhone) o;
return cellPhone.price == price && cellPhone.brand.equals(brand);
}
return false;
}
@Override
public int hashCode() {
return brand.hashCode() + (int) price;
}
@Override
public String toString() {
return "CellPhone:brand:" + brand + " price:" + price;
}
}
在Kotlin的实现上述功能就只需一句代码即可。
data class CellPhone(val brand: String, val price: Double)
神奇的地方就在于data
关键字,在Kotlin中的类声明了data
关键字,那么这个类就是一个数据类,Kotlin中会根据主构造函数中的参数自动生成eqauls()、hashCode()、toString()
等方法并实现相应的逻辑。另外在一个类中没有任何代码时,可以省了大括号
下面我们测试下该类
fun testDataClass() {
val cellPhone1 = CellPhone("xiaomi", 2000.0)
val cellPhone2 = CellPhone("xiaomi", 2000.0)
Log.e("TAG", "cellPhone1:" + cellPhone1.toString()+" cellPhone2:"+cellPhone2.toString()+" cellPhone1.equals(cellPhone2):"+(cellPhone1==cellPhone2))
}
输出:
cellPhone1:CellPhone(brand=xiaomi, price=2000.0) cellPhone2:CellPhone(brand=xiaomi, price=2000.0) cellPhone1.equals(cellPhone2):true
很明显CellPhone已经正常工作了。
5.2、单例类
在Java中的实现
public class SingleInstance {
private static SingleInstance instance;
private SingleInstance() {
}
public static synchronized SingleInstance getInstance() {
if (instance == null) {
instance = new SingleInstance();
}
return instance;
}
}
在Kotlin中创建一个单例类很简单,只需要将class
关键字改成object
关键字即可。
object SingleInstance {
}
现在SingleInstance
就是一个单例类了,我们可以直接在这个类中编写需要的函数,比如加入一个singleInstanceTest()
object SingleInstance {
fun singleInstanceTest() {
Log.e("TAG","singleInstanceTest is called")
}
}
可以看到,Kotlin不需要私有化构造函数,也不需要提供getInstance()
这样的静态函数,只需要将class
修改为object
,Kotlin就帮我们生成了单例类相关的逻辑,它的调用也很简单
SingleInstance.singleInstanceTest()
这种写法看上去像是静态方法的调用,其实Kotlin内部已经帮我们生成了SingleInstance
实例,并且该实例有且仅有一个。