前篇系列文章(请同学们按照顺序来来学习):
一起来从零学Kotlin-20170728:
http://blog.youkuaiyun.com/clandellen/article/details/76283527
一起来从零学Kotlin-20170730:
http://blog.youkuaiyun.com/clandellen/article/details/76369434
一起从零学Kotlin-20170801
0.官方API
链接:http://pan.baidu.com/s/1geVxlJ1 密码:4nht
官方API才是学习的王道……
Intellij IDEA(kotlin开发学习IDEA,并且还支持Java)下载
https://www.jetbrains.com/idea/
官网下载慢的可以从我云盘里面下载:
链接:http://pan.baidu.com/s/1i4Au2nb 密码:qato
1.Kotlin中的抽象类与接口
1.1 Kotlin中的接口
Kotlin的接口和Java中的十分相似,但是Kotlin中的接口有些自己的特性,比如下列代码编译器会报错吗?
interface JieKou{
fun A(){
//方法体a
println("接口里面的方法,我是方法体")
}
}
你肯定会回答报错吧,因为接口里面不能有方法的实现,但是却告诉你,这么写是可以的,你没看错,这么写,编译器不会报错,难道Kotlin中的接口中的方法可以自己实现,其实Kotlin中的接口的方法不能自己实现,这么写代表着A()方法默认实现方法体为a,当某个类实现了这个接口,没有对A()方法进行重写,那么就会默认认为这个实现了JieKou的类的A()方法的方法体为a,Kotlin为什么会这么做呢?细心的同学会发现Java当中每次实现接口都要实现其接口的所有方法,而有些方法在类A中实现,在类B中的实现,在类C中的实现,方法体基本一样,每次实现那个接口都要重写那些方法体基本一样的方法,举个例子,学生类,老师类,校长类,有一个”王者荣誉”的接口,”王者荣耀”中的接口封装了一些属性,如下所示:
interface 王者荣耀{
int 等级;
String 用户名;
String 密码;
public void basicPlay(){}//王者荣耀的基本玩法
public void userPlay(){}//玩家自己的玩法(通常由手速和走位来决定)
}
当学生类,老师类,校长类去实现这个接口,那就要实现basicPlay()和userPlay()这两个方法,但是发现王者荣耀的基本玩法的方法体是一样的,学生类,老师类,校长类都要重写这个basicPlay()方法,简直太麻烦了,明明代码都是一样的,咋还要重复又重复,所以kotlin接口中有个默认方法体来避免上述麻烦的事儿。Kotlin中的方法声明除了这一点之外,其他并没什么特别之处,和Java相似,那么接口的属性有如何呢?
接口中的属性
你可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现。在 接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器 不能引用它们。
interface 接口{
val a1:Int get() = 3 //这是访问器的实现,可以进行赋值
var a:Int //这是抽象的,赋值是会报错的
fun A(){
println("接口里面的方法,我是方法体")
}
}
1.2 Kotlin中的抽象类
Kotlin中的抽象类和Java一样,所以我就不罗嗦了。
abstract class A{
var a = 4
var b:Int = 0
fun geta():Int{
return a
}
abstract fun getb():Int
}
2.Kotlin中的继承
上面我们已经把Kotlin中的接口和抽象类说明了,接口和抽象类就是用来被继承使用的,那么Kotlin中的继承又有哪些知识点呢?
Kotlin中的继承:
Kotlin中的继承不在使用extedns关键字,而是通”:”来完成,比如下面的代码:
abstract class A{
var a:Int
get() = a
set(value){a = value}
fun geta() = a
abstract fun getStr():String
}
open interface B{
var b:Int
fun getb() = b
}
class C:A(),B{//类C继承抽象类A和实现接口B
override var b: Int
get() = b
set(value) {b = value}//实现接口B的成员变量b
override fun getStr(): String {
println("对抽象类A中的getStr()方法进行重写")
return (a+b).toString()+""
}
}
子类对父类,父抽象类,接口的方法的重写以及实现都需要通过”override”关键字来完成,可以看看上面的代码。注意的是重写方法的时候有些地方是需要注意的,对于父抽象类中的抽象方法,不添加”open”关键字可以进行重写,但是父抽象类中的被具体实现的方法,如果不在方法前面添加”open”关键字,是不可以对它进行重写的,比如以下的代码:
abstract class D{
var d = 4
abstract fun getd() //这个因为是抽象方法,所以默认是添加open的
fun getd1():Int{return d}//这个具体实现的方法,由于没有添加"open"修饰符,所以子类当中不能对这个方法进行重写
open fun getd2():Int{return d}//这个方法可以在子类当中进行重写,原因我就不罗嗦了
}
当然不仅仅抽象类是这样的,一个具体的类也是这样的。
open class E{
var e = 5
fun gete1():Int{return e}//这个具体实现的方法,由于没有添加"open"修饰符,所以子类当中不能对这个方法进行重写
open fun gete2():Int{return e}//这个方法可以在子类当中进行重写,原因我就不罗嗦了
}
Kotlin中除了对父类以及接口中的方法进行重写之外,还可以对成员变量进行重写?这是怎么回事呢?重写成员变量?
成员变量的覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必override开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有 getter方法的属性覆盖。而且注意的是你要对父类的属性进行重写,那么父类的这个属性必须是open的,这跟方法是一样的。示例代码如下:
你也可以用一个var 属性覆盖一个val属性,但反之则不行。这是允许的,因为一个val 属性本质上声明了一个getter方法,而将其覆盖为 var 只是在子类中额外声明一个setter方法。
请注意,你可以在主构造函数中使用override关键字作为属性声明的一部分。示例代码如下:
3.解决重写冲突
3.1 重写时相同方法名不同的返回值类型的冲突
有些时候我们自定义的类继承一个类或者抽象类,已经多个抽象的接口,对这些方法进行重写的时候,往往会出现一些问题,比如以下代码:
abstract class AA{
abstract fun hello():Unit
}
interface BB{
fun hello():Int
}
你可以发现,抽象类AA类中存在的方法hello()方法返回的类型为unit,而接口中的hello()方法返回的是Int,如果一旦有某个类继承了AA并实现了接口BB,那么hello()方法又该如何处理呢?很头疼吧!无从下手,因为一个类中无法存在两个方法名字一样,返回值不一样的两个方法,这种情况就是”重写的冲突”问题。那么如何解决呢?跟Java中的解决方法一样,就是修改父类或者接口中的方法名字,直到不发生冲突为止。
3.2 调用时发生的冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如:
interface 接口1{
fun mehtod1(){
println("接口1中的方法1")
}
fun method2()
}
interface 接口2{
fun mehtod1(){
println("接口2中的方法1")
}
fun method2(){
println("接口2中的方法2")
}
}
class 类:接口1,接口2{
override fun mehtod1() {
//super.mehtod1()
//这里究竟调用的是接口1中method1()方法还是接口2中的method1()方法
super<接口1>.mehtod1()//解决方法就是在super关键字后面指明要调用的父类的名字即可
super<接口2>.mehtod1()//调用接口2中的method1()方法
}
override fun method2() {
//super.method2()
}
}
4.Kotlin中成员变量的权限修饰符
Kotlin中的权限修饰符在前面我们提到了,我们来看看成员变量修饰符:
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有可见性修饰符。(getter 总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:private 、protected 、internal和 public。 如果没有显式指定修饰符的话,默认可见性是 public。
在顶层中声明的变量的修饰符特性:什么是顶层?就是在类之外
public:随处可见(访问)
private:仅仅在声明它的文件范围内可见(访问)
internal:它会在相同模块内随处可见(访问)
protected:不适用与顶部声明
类中成员变量的修饰符特性:
private:意味着只在这个类内部(包含其所有成员)可见(访问)
protected:和private 一样 +在子类中可见(访问)
internal:能见到类声明的本模块内的任何客户端都可见(访问)其internal成员
public:能见到类声明的任何客户端都可见(访问)其public成员
5.Kotlin中的伴生对象与静态成员
Kotlin当中使用伴生类来替代Java当中静态,写法就是在类中添加 companion object{}代码块,在companion object{}中声明的方法就是从属于类的静态方法,声明的属性就是从属于类的静态变量,示例代码如下:
fun main(args: Array<String>) {
println(Student.a)
println(Student.get())
}
class Student(var name: String,var age: Int) {
companion object {//静态方法和变量都应该写在这里面
var a:Int = 3;//静态变量
fun get():Int{
return a;
}
}
}
6.Kotlin中的方法的重载和默认参数
Kotlin中的方法重载规则基本上Java是一样的,其规则如下:
(1)方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型重载Overloading是一个类中多态性的一种表现。
(2)Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。
调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。
(3)重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准.
默认参数与重载之间的冲突:
默认参数我们已经在上篇文章中已经进行了学习,我们知道在调用默认参数的方法的时候,可以不用再次传递参数给默认参数,那么默认参数究竟会和重载之间发生什么冲突呢?看以下代码,你就会知道了:
class 类A{
fun method1(a:Int=3){//默认参数
println("调用的默认参数方法")
}
fun method1(){
println("调用的非默认参数方法")
}
}
fun main(args: Array<String>) {
var a = 类A()
a.method1() //这究竟调用是method1(Int)还是method1()呢?
a.method1(3)
}
输出:
调用的非默认参数方法
调用的默认参数方法
7.扩展方法和成员
什么是扩展方法呢?就是对已经声明好的类进行扩展,不是在声明好的类的内部在添加方法,而是在需要的位置进行添加,这就是扩展方法。
扩展方法的格式:
fun 类名.扩展方法名(参数列表){方法体}
或者
fun 类名.扩展方法名(参数列表) = 表达式
示例代码如下:
fun main(args: Array<String>) {
var myClass = MyClass()
println(myClass.add(2,3))//调用MyClass的扩展方法add(Int,Int)
}
class MyClass{
var a = 3;
var b = "4"
var c = true
}
fun MyClass.add(a:Int,b:Int) = a+b //为MyClass添加扩展方法add(Int,Int)
要是觉得笔者这段代码没啥意思,那么我们换个有趣的例子,给String类添加一个扩展方法能够达到以下的目的:
为String添加一个乘法,运算效果是这样的,”abc” * 3 后返回的结果是”abcabcabc”,返回的是原字符串的3倍,如何使用扩展方法实现上述的功能呢?代码如下:
fun main(args: Array<String>) {
println("abc" * 5)
}
operator fun String.times(ci:Int):String{//为String添加扩展方法,实现乘法的功能
var stringBuilder = StringBuilder()
for(i in 0 until ci){
stringBuilder.append(this)
}
return stringBuilder.toString()
}
输出:
abcabcabcabcabc
方法可以扩展,那么成员变量可以扩展吗?当然可以啦!
fun main(args: Array<String>) {
var myClass = MyClass()
println(myClass.add(2,3))//调用MyClass的扩展方法add(Int,Int)
println(myClass.d)//调用MyClass的扩展成员变量d
}
class MyClass{
var a = 3;
var b = "4"
var c = true
}
fun MyClass.add(a:Int,b:Int) = a+b //为MyClass添加扩展方法add(Int,Int)
var MyClass.d: String
get() = "sdasda"
set(value){d = value}//MyClass的扩展成员变量d
怎么样,是不是感觉扩展很牛啊!那就就行往后进行学习吧!
8.属性代理
什么是属性代理,就是把成员变量的get和set交给其他的类完成,这么做有什么用呢?搞得这么复杂干嘛?其实好处是非常多的,属性代理可以让你在为成员变量读和写的时候,完成一些你要的操作,这些操作对于读和写的地方而言是被隐藏了,或许我说的你可能不太懂,那么我们上代码来分析以下,有这么一个类Book,其成员变量有书名,作者,书的总页数,书的种类,书店价格等等,如下所示:
fun main(args: Array<String>) {
var book = Book("2017-acd45f","Kotlin从入门到精通","ellen","20170731",350,"IT教育",1)
}
class Book{
var ID = "" // 书籍对应的ID
var name = "" //书名
var author = "" //作家
var date = "" //书的出版日期
var pagers = 0 //书的总页数
var kind = "" //书的种类
var price = 0 //书的价格,单位:毛
constructor(name:String,author:String,date:String,pagers:Int,kind:String,price:Int){
this.ID = ID
this.name = name
this.author = author
this.date = date
this.pagers = pagers
this.kind = kind
this.price = price
}
}
可以发现每次新建一个Book的实例对象,需要让Book实例对象的成员变量对应的书籍资料完
整,每一个都要进行赋值,你可能忽视了一些东西,这些数据可能存在与数据库当中,不用每次所有的成员变量都要进行赋值,什么意思?就是说如果我们一旦对书籍的ID赋值,也就是Book类的ID进行赋值,那么其他的成员属性就应该从数据库中加载值,不用我们每个进行赋值,那么该如何做呢?属性代理就可以完成这样的工作,示例代码如下:
import kotlin.reflect.KProperty
fun main(args: Array<String>) {
var book = Book("2017-acd45f")//只要进行赋值就会启动代理类的setValue方法
println("----------------------------------")
println("外部访问:\n书籍的ID: ${book.ID}")
println("书籍名:${book.name}")
println("书籍作者:${book.author}")
println("书籍日期:${book.date}")
println("书籍页数:${book.pagers}")
println("书籍种类:${book.kind}")
println("书籍价格:${book.price}毛")
}
class Book{
var ID:String by DaiLi(this)// 书籍对应的ID
var ID_Copy : String = ""//ID的副本
var name = "" //书名
var author = "" //作家
var date = "" //书的出版日期
var pagers = 0 //书的总页数
var kind = "" //书的种类
var price = 0 //书的价格,单位:毛
constructor(ID: String){
this.ID_Copy = ID;
this.ID = ID
}
}
class DaiLi(var book:Book){//代理类
operator fun getValue(thisRef:Any?,property:KProperty<*>):String{
return book.ID_Copy
//return book.ID
//这里千万不要添加此语句,因为后果就是每次访问book.ID的时候,都会调用getValue()方法,然后就一直book.ID反复被访问,getValue()被反复调用,直到发生异常StackOverflowError,所以我们需要一个ID属性的副本ID_Copy来完成getValue()的功能。
}
operator fun setValue(thisRef:Any?,property:KProperty<*>,value:String){
println("根据书籍的ID从数据库中加载书名,作者,出版日期,书籍总页数,书的种类及价格信息")
println("书籍ID:${book.ID_Copy}")
//此处是模拟的数据库中取值过程
book.name = "Kotlin从入门到精通"
book.author= "ellen"
book.date = "20170731"
book.pagers = 350
book.kind = "IT教育"
book.price = 1
println("书籍名:${book.name}")
println("书籍作者:${book.author}")
println("书籍日期:${book.date}")
println("书籍页数:${book.pagers}")
println("书籍种类:${book.kind}")
println("书籍价格:${book.price}毛")
}
}
输出结果:
根据书籍的ID从数据库中加载书名,作者,出版日期,书籍总页数,书的种类及价格信息
书籍ID:2017-acd45f
书籍名:Kotlin从入门到精通
书籍作者:ellen
书籍日期:20170731
书籍页数:350
书籍种类:IT教育
书籍价格:1毛
———————————-
外部访问:
书籍的ID: 2017-acd45f
书籍名:Kotlin从入门到精通
书籍作者:ellen
书籍日期:20170731
书籍页数:350
书籍种类:IT教育
书籍价格:1毛
注意的是给变量设置代理是要分情况的,当你给var属性设置代理就需要代理类具有getValue()和setValue()方法,因为var变量是可读可写的,当你给val设置代理就仅仅需要代理类具有getValue()方法,因为val是只读的。
9.数据类,内部类
9.1 数据类
在声明类的时候在类的前面加上修饰符”data”,那么这个类就可以称得上数据类了,数据类是专门存储数据的类,那么数据类有哪些特性呢?
data class User(val name:String,val age:Int)//数据类User
编译器自动从主构造函数中声明的所有属性导出以下成员:
1.equals()/hashCode()
2.toString() 格式为”类名=(属性1=value1,属性2=value2,属性3=value3)”
比如下面的代码:
var myUser = User("ellen",23)
println(myUser.toString())
输出结果:
User=(name=”ellen”,age=23)
3.componentN()函数 按声明顺序对应于所有属性
4.copy()函数
注意的是一个类一旦被data所修饰,这个类不光是数据类,而且它还是final的,并且没有无参构造方法,这将给开发工作带来某些不便,比如你的数据类需要继承与其它的类当中,数据类无法做到,感觉这是坑啊!没关系,Kotlin官方给出了解决方案,使用allOpen和noArg插件即可填坑。如何使用这两个插件,其实就是配置到项目工程当中,Java项目工程
很熟悉的同学,这一点都不难,所以笔者这里不罗嗦了,你可以网上查找到使用教程。
9.2 内部类
内部类,就是类当中还有类的意思,Kotlin的内部类特性基本上和Java当中的类似,内部类最关键的特性就是如何去访问外部类的成员变量已经成员方法了,也就是this关键字的冲突问题以及内部类在外部实例化的问题了,Java当中是这么解决的,代码如下:
class InnerClassDemo{
public int a = 3;
class Inner{
public int b = 4;
public void getA(){
System.out.println(InnerClassDemo.this.a = 5);//通过 外部类名.this来访问外部类的成员变量已经成员方法
System.out.println(this.b);//这个this指向的是当前内部类的实例对象
}
}
}
如果想要在外部初始化Java当中的内部类,那么你必须通过外部类的实例对象类实例化内部类,代码如下:
InnerClassDemo innerClassDemo = new InnerClassDemo();
Inner inner = innerClassDemo.new Inner();//通过外部类实例对象来实例化内部类
上面讲的是Java当中的非静态内部类,还有静态内部类,其实静态内部类很简单,静态内部类不持有当前实例对象,也就是this,那么它持有什么呢?静态内部类持有的是类本身,也就是说,你可以在静态内部类里面去访问外部类的静态属性,以及静态方法。
那么Kotlin中的内部类又有什么不一样呢?
Kotlin声明一个非静态类必须在类的前面加上”inner”关键字,否则这个类将会是静态的,示例代码如下:
class A{
class AB{//A类中的静态类AB
}
inner class AC{//A类中的非静态类AC
}
}
Kotlin中非静态内部类的this关键字冲突问题:
Kotlin当中解决非静态内部类this关键字冲突问题很简单,就是通过”this@外部类类名”来区分示例代码如下:
class A{
var a =3
class AB{//A类中的静态类AB
}
inner class AC{//A类中的非静态类AC
var a = 4
fun getAa():Int{
return this@A.a
}//返回外部类的成员变量a的值
fun getACa():Int{
return this.a
}//返回内部类的成员变量a的值
}
}
那么如何在外部实例化内部类对象呢?示例代码如下:
var ab = A.AB() //实例化A类中的静态内部类AB
var ac = A().AC() //实例化A类中的非静态内部类AC
Kotlin当中的匿名内部类:
fun main(args: Array<String>) {
var b = B(object :接口A{
override fun getA() {
println("匿名内部类")
}
})//通过"object:接口名或者抽象类名{实现体}"来完成匿名内部类
}
interface 接口A{
fun getA()
}
class B(var a:接口A){}
10.枚举,密封类
10.1 枚举
枚举的声明和Java是一样的,都使用”enum”关键字来完成,示例代码如下:
enum class ZiMU{
AA,BB,CC,DD,EE,FF,GG,HH,II,JJ,KK //枚举的成员值
}
其实这和Java中的是一样一样的,那么Kotlin中的枚举可以添加构造函数和方法吗?当然可以啦!
在枚举中添加构造函数,示例代码如下:
enum class ZiMU(var a:Int){
AA(0),BB(1),CC(2),DD(3),EE(4),FF(5),GG(6),HH(7),II(8),JJ(9),KK(10)
}
在枚举中添加方法,示例代码如下:
fun main(args: Array<String>) {
var ziMu = ZiMU.FF
println(ziMu.getValueString())
}
enum class ZiMU(var a:Int){
AA(0),BB(1),CC(2),DD(3),EE(4),FF(5),GG(6),HH(7),II(8),JJ(9),KK(10);//如果枚举中存在方法,那么声明枚举的值的末尾必须添加分号
fun getValueString():String{
return "($a,$name)"
}
}
输出:
(5,FF)
10.2 密封类
密封类?啥意思啊!类被密封了? 枚举是实例有限的类,而密封类就是子类有限的类,称为密封类。
密封类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其他类型 时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量 只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
要声明一个密封类,需要在类名前面添加 sealed修饰符。虽然密封类也可以有子类,但是 所有子类都必须在与密封类自身相同的文件中声明。(在Kotlin1.1之前,该规则更加严格:子类必须嵌套在密封类声明的内部)。示例代码如下:
sealed class MiFengClass{
data class A(var a:Int):MiFengClass()//构造器有参数的子类
data class B(var b:Int):MiFengClass()
object C:MiFengClass()//构造器无参的子类一定使用object来声明
}
class D(var d:Int):MiFengClass() // 在密封类MiFengClass外部为密封类MiFengClass类添加子类 D
这段代码什么意思呢?就是说密封类MiFengClass的子类只有类A,B,C,D 4个子类。要想为密封类MiFengClass再添加子类,都必须在与密封类自身相同的文件中进行声明,否则是不能为它添加子类的。