前言:由于作者有编程基础,所以此文章只为记录一个快速学习kotlin的过程,谢谢支持观看
以下代码开发环境均为eclipse和 IntelliJ IDEA
要想在eclipse上开发kotlin,必须先安装kotlin插件 通过 Help ----> Eclipse Marketplace ,然后搜索 kotlin 安装后会提示重启,如果new无法找到kotlin项目,通过 Window —>Perspective ----> Customize Perspective 找到Shortcuts 勾选Kotlin ,然后 Apply and close 即可。
准备好开发环境,我们就开始创建我们的Hello Kotlin
通过 File —> New —> Kotlin Project 创建一个kotlin 项目定位到kotlin项目的src目录,通过 右键 --> New —> Kotlin File 创建kotlin文件代码如下: fun main(args:Array<String>){ println("Hello Kotlin") }可以看到和java 很明显的区别就是一个语句写完不用写分号结尾
通过var 关键字去表示一个变量,并且当变量初始化后,不能再存储与初始化时不同类型的数据
从代码可以看到:
var 关键字表示的变量必须定义的时候初始化具体类型,不然计算机无法给他分配合适的内存空间存储它。var变量一旦初始化某种类型,不能再继续储存其他类型。我们现在稍微修改2.2小节中的代码:
fun main(args:Array<String>){ //计算机给我一块存储空间,名字叫i 里面存放整数18 var i:Int=18 i=19 /** 智能类型推断i为int类型, 99999999999已经超过了int的最大值 */ // i=99999999999//错误 //计算机给我一块存储空间,名字叫j,存放的数据类型为长整型 var j:Long=99999999999 //s 就是一个存放字符串的空间 var s:String="hello kotlin" //错误,无法只能推断,var变量需要通过等号后面的值去分配当前的类型 var a:Long }从上面代码可以看出, 1.在变量名后面通过:具体类型 指定var 距离类型 2.var变量当没有赋值时,手动指定具体的变量类型也是没有问题的
从上面代码可以看出:
通过val 修饰一个常量通过val 修饰的常量,一旦赋值不能被修改当我们定义如下一段代码:
fun main(args:Array<String>){ } fun printfStrModule(str:String){ //错误 println(" 床前明月光, 疑是地上霜 ") }可以看到kotlin提示println中参数太多,直接报错了 我们稍作修改
发现程序运行成功,我们可以知道 在Kotlin中用三引号来表示特定格式的字符串
从上面我们可以知道通过${}使用在字符串中可以将大括号中的结果输出
在java 中我们都知道 == 是比较 两个String 字符串的地址,而在kotlin中是否也是同样的作用,我们来看看:
fun main(args:Array<String>){ var str1="李四" var str2="张三" //在java中比较的市地址,而在kotlin中 == 与 equals 效果相同,比较的都是字符 println(str1==str2)//false println(str1.equals(str2))//false var str3="李四" var str4="李四" println(str3==str4)//true println(str3.equals(str4))//true var str5="Fay" var str6="Fay" println(str5==str6)//true println(str5.equals(str6))//true var str7="fay" println(str6==str7)//false println(str6.equals(str7))//false //true表示忽略大小写 println(str6.equals(str7,true))//true }从上面代码我们可以知道 1.在kotlin中 == 比较的是两个字符串的字符,所以这里跟java是有区别的,在kotlin 中 == 与equals 是相同的作用 2.当在equals 函数后面加一个布尔值时,该布尔值表示的是否忽略大小比较
通过to+类型的形式可以转换
在kotlin中函数定义格式为: fun(固定格式) + 方法名(参数名字1:参数类型1,参数名字2:参数类型2,…):返回值 如果没有返回值写Unit,也可以忽略不写,没有参数则也不写
题目:写一个参数为Int类型的a和参数为String 类型,返回Long类型的方法
fun study2(a:Int,b:String):Long{ return 100 }在java中,函数的引用参数默认是可以传null 并且编译可以通过,而在kotlin中,在编译时期默认就不允许传入null,很好的规避了我们在代码中的一些低级错误:
fun main(args: Array<String>) { println(printlnStr("I love you")) // printlnStr(null)//错误 } //在Kotlin中 参数默认是非空的类型 fun printlnStr(str:String):String{ return "hello kotlin" + str }那么我们想传入null怎么办呢?kotlin 允许在类型的后面添加 ?问好,表示允许传入非空类型
fun main(args: Array<String>) { println(printlnStr("I love you")) // printlnStr(null)//错误 printlnStr2(null) } //在Kotlin中 参数默认是非空的类型 fun printlnStr(str:String):String{ return "hello kotlin" + str } //加上 ? 表示可以接收非空的类型 fun printlnStr2(str:String?):String{ return "hello kotlin" + str }当函数体只有一行时,我们可以通过函数表达式简写函数
在 java中我们知道 我们通过 switch 开关语句 来表示 判断选择,和case break default 来配合使用,而kotlin 抛弃了switch,产生了更加简单的when
fun main(args: Array<String>) { println(chineseLowerCaseToCapitals("七")) } fun chineseLowerCaseToCapitals(lowerStr:String):String{ var result = when (lowerStr){ "一" -> "壹" "二" -> "贰" "三" -> "叁" "四" -> "肆" "五" -> "伍" "六" -> "陆" else -> "-1" } return result }从上面可以看到when表达式 省略重复繁琐的case break,写起来更加方便快捷
[1,100]
var nums = 1..100//[1,100][1,100)
var nums1 = 1 until 100 //[1,100),不包含100通过上面我们知道通过 知道,在kotlin 中,
通过 .. 可以表示两个数字的闭区间通过 until 关键字 表示两个数字的闭区间,包含头,不包含尾通过step 关键 跳跃式 遍历区间
通过 reversed 函数 反转区间数据
这两者使用起来跟java几乎没有太大区别
类就是用来面向对象的,学习过C++和java之后我们一定对类不陌生 在kotlin中怎么去定义一个类呢,例如一个长高的矩形
class Rect_WT(var width:Float,var height:Float)如何使用呢:
var rect =Rect_WT(1F,2F) println("Rect_WT width=${rect.width},height=${rect.height}")可以看到kotlin 在使用类方面简化了很多。
类里面如何定义函数呢:
class People(var age:Int,var sex:String){ fun println(){ println("println people age:${age},sex:${sex}") } }如何使用函数呢:
fun main() { var people =People(18,"male") people.println() }当我们想类里面的函数不想被外面使用时,使用关键之private ,和java这点是一样的:
class People(var age:Int,var sex:String){ fun println(){ println("println people age:${age},sex:${sex}") } private fun sleep(){ println("people sleep") } }我们首先创建一个Dog.kt
class Dog { var color="白色" fun eat(){ println("${color}的狗正在吃") } }创建BlackDog.kt,继承Dog
class BlackDog : Dog() { }kotlin 中类默认是final ,加入open关键字 表示允许子类操作
open class Dog { var color="白色" fun eat(){ println("${color}的狗正在吃") } }使用:
var blackDog =BlackDog() blackDog.eat() println("这是一只${blackDog.color}的狗")覆盖父类的函数与属性
class BlackDog : Dog() { override fun eat(){ color="黑色" println("${color}的狗正在吃") } }当在上面的基础上写完这段代码,编译期间是不会报错的,这个比较蛋疼, 当想要复写父类的函数时,函数前也需要加入open关键字修饰
open class Dog { var color="白色" open fun eat(){ println("${color}的狗正在吃") } }kotlin和java 中都是通过 abstract 关键字 修饰抽象类,抽象类中可以有抽象方法和非抽象方法,
abstract class Human{ abstract fun eat() fun work(){ println("辛勤的工作") } }继承抽象类和java中也是一样,如果不实现抽象方法的话,在子类上也添加上 abstract 关键字
abstract class Woman:Human() { }实现抽象类:
class Woman:Human() { override fun eat(){ println("矜持的吃") } }在java中我们已经学习了多态,kotlin中的表现其实和java一样,我们在7.3小节的基础上在创建一个man 类:
class Man:Human() { override fun eat() { println("粗鲁的吃") } }多态在kotlin中的使用:
var woman =Woman() var man=Man() var humans= listOf<Human>(woman,man) for (h in humans){ h.eat() }在kotlin 用关键object代理class 即可为一个单例类:
object Father{ fun introduce(){ println("我是小明爸爸") } }在main函数中测试使用:
fun main(array:Array<String>){ Father.introduce() }定义如下:
enum class TestEnumClass { 春天,夏天,秋天,冬天 }使用:
println(TestEnumClass.冬天.ordinal)在kotlin中出现了印章类,用来表示当某些类的子类是指定类型的时候的情况, 我们可以知道现实生活中人只分为女性和男性两种,所以人这个类的子类只有女性和男性,在kotlin中用印章类表示这种情况,通过 sealed 关键字修饰 看到代码:
//印章类 sealed class Human{ open fun eat(){ println("我们会吃饭") } class Male:Human(){ override fun eat(){ println("我是男性会吃饭") } } class Femeal:Human(){ override fun eat(){ println("我是女性会吃饭") } } }使用:
var human1:Human = Human.Femeal(); var human2:Human = Human.Male(); human1.eat() human2.eat()在java 中使用interface关键字修饰接口,在kotlin完全一样:
interface Animal { fun sleep() }使用:
class Cat:Animal { override fun sleep() { println("躺再地上睡") } }在java中我们是没有具体的委托于代理一说,在kotlin中衍生了代理一说,通过关键字 by 修饰
在现实生活工作中,其实我们也有类似的情况,毕竟代码就是用来代理生活中的事情, 在一个公司,有员工,员工上面有主管,主管上面有老板,老板安排一件事情给主管,在这件事情中,我们知道老板的角色是委托,主管既是委托也是代理,员工是代理。
现在我们再次回到代码,在第八章之前我们学习了接口 我们首先顶一个Worker接口
interface Worker { fun work() }普通工人
class Staff:Worker { override fun work() { println("我是普通员工,我工作一天300块") } }主管
class Supervisor:Worker{ override fun work() { Staff().work() println("我是主管,我工作一天3000块") } }老板
class Boss:Worker { override fun work() { println("我是老板,我不用做事也有钱") } }使用一下
fun main(array:Array<String>){ var staff=Staff() var supervisor =Supervisor() var boss =Boss() staff.work() supervisor.work() boss.work() }ok,复现刚才的场景,老板安排主管,主管去安排普通员工,在kotlin中通过by关键字,修改下刚才的代码:
class Supervisor:Worker by Staff(){ /* override fun work() { println("我是主管,我工作一天3000块") }*/ } class Boss:Worker by Supervisor(){ /* override fun work() { println("我是老板,我不用做事也有钱") }*/ }再次运行下: 可以看到最后都是普通员工在做事
在实例中,我们将结合一些第三库来进行实战,因此需要通过创建支持Gradle 项目来进行操作,使用过AndroidStudio的应该很容易上手的,不懂Gradle可以去了解一下。 File -> New -> Project -> Gradle -> Kotlin/JVM 创建项目后大概就是一个这样的结构,创建一个src目录来存放代码, 创建resources来存放需要的资源文件, Gradle 主要是读取build.gradle来配置我们的项目,因为我们需要配置build.gradle: 有时候我们只知道大概的第三库工具的名字,Intellij IDEA 也可以很好支持从Maven 搜索: File -> Project Structrue 最后apply即可,等待sync完成
我们按照10.1章节先创建一个Gradle kotlin项目,然后在build.gradle中导入rxjava库: 我们创建一个文件,复制一段英文到文件中,将文件复制到resources中,以便我们进行接下来的统计操作,
接下来,编写统计代码
import rx.Observable import rx.Observer import java.io.File fun main(args:Array<String>){ println("Hello world") //1.读取文件内容到text中 val text=File(ClassLoader.getSystemResource("Test.txt").path).readText() println(text) /*** 2.1 利用rxajva 的from 函数遍历text ,加载数据源 2.2 利用rxjava filter 函数过滤数据源中的空字符 2.3 利用rxjava groupBy 函数进行对每个字符进行分组 2.4 分组之后,得到统计后每个字符的个数 */ Observable.from(text.toCharArray().asIterable()).filter { !it.isWhitespace() }.groupBy { it }.map{ o -> o.count().subscribe{ println("key=${o.key} -> value=${it}") } }.subscribe(){ } }不懂Retrofit的可以先去 我的文章 第三方框架的学习
按照惯例我们先z在我们的项目导入Retrofit2: build.gradle
implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0"我们实例将要用到的url如下:
https://api.github.com/repos/WTCool666/Audio-SampleConvert-And-BitWidthConvert-And-trackConvert/stargazers
按照Retrofit的使用步骤,我们先创建一个interface来定义我们需要的功能接口函数 解析该url需要的对象
data class User(val login:String,val id:Long,val avatar_url:String) { }data 修饰的类是一个标准的bean,会帮我们自动复写一些基本的函数,让我们容易识别该对象,例如toString函数
interface GitHubService{ @GET("/repos/WTCool666/Audio-SampleConvert-And-BitWidthConvert-And-trackConvert/stargazers") fun getStartGazers(): Call<List<User>>; }接着创建通过Retrofit 去创建 GitHubService 接口对象实例
object Service{ val gitHubServcie:GitHubService by lazy{ Retrofit.Builder().baseUrl("https://api.github.com").addConverterFactory(GsonConverterFactory.create()).build().create(GitHubService::class.java) } }lazy 表示第一次使用的时候初始化该对象 最后使用 GitHubService 对象访问 该网络接口
fun main(args:Array<String>){ Service.gitHubServcie.getStartGazers().execute().body()!!.map(::println) }首先准备两个我们实验中需要解析的json文件 player.json
{ "name": "feiyue", "level": 99, "age": 18, "skillList": [ { "id": 1, "name": "天下无双", "damage": "123" }, { "id": 2, "name": "雌雄难辨", "damage": "182" }, { "id": 3, "name": "暗流涌动", "damage": "89" } ] }data_json2.json
{ "message": "ok", "code": 200, "data": { "name": "feiyue", "level": 99, "age": 18, "skillList": [ { "id": 1, "name": "天下无双", "damage": "123" }, { "id": 2, "name": "雌雄难辨", "damage": "182" }, { "id": 3, "name": "暗流涌动", "damage": "89" } ] } }接着导入gson 框架 build.gradle:
implementation "com.google.code.gson:gson:2.3"按照我们使用Gson的步骤: 先创建好json数据解释后的对象:
data class Player(val name:String,val level:Int,val age:Int,val skillList:List<SkillPlayer>) { } data class SkillPlayer(val id:Int,val name:String,val damage:Int){ }开始使用
val json=File("player.json").readText(); val result = Gson().fromJson(json,Player::class.java)result就是我们解析后的Player对象结果
通过ASM工具查看bytecode我们其实可以知道在运行过程中其实程序是不是知道Player对象的,只能将他识别成Object对象,那么我们是否可以不填写第二个参数呢? 在kotlin中我们复写Gson类的fromjson函数:
import com.google.gson.Gson inline fun <reified T:Any> Gson.fromJson(json:String):T{ return fromJson(json,T::class.java) }inline我们学过C++知道他是编译器会用其函数体来替换掉函数调用,而如果该函数里面有泛型就可能会出现编译器不懂该泛型的问题,所以引入reified,使该泛型被智能替换成对应的类型
接着我们来使用一波:
import com.google.gson.Gson import java.io.File fun main(args:Array<String>){ val json=File("player.json").readText(); val result = Gson().fromJson(json,Player::class.java) val result1:Player = Gson().fromJson(json) println(result) println(result1) }因为这个时候返回值是一个泛型,所以我们的结果值必须指定一个具体类型Player。
前面我们说了通过ASM查看字节码我们知道程序运行过程中不知道其泛型具体类型,那么在java中是否有办法知道呢?接着我们解析准备好的第二个json文件
我们首先要再次创建一个解析后的对象:
data class Data<T>(val message:String,val code:Int,val data:T){ }我们首先先按照常规方式解析:
val json2=File("data_json2.json").readText(); val result2:Data<Player> = Gson().fromJson(json2) println(result2)可以看到即使我们用data修饰了Player类,编译工具也无法将他具体类型打印出来
那么如何操作呢?如下
import com.google.gson.Gson import com.google.gson.reflect.TypeToken import java.lang.reflect.Proxy interface Api{ fun getObjTypeFromJson(json:String):Data<Player> } object ApiFactory{ val api:Api by lazy { Proxy.newProxyInstance(ApiFactory.javaClass.classLoader, arrayOf(Api::class.java)){ proxy,method,args -> val responseType = method.genericReturnType val adapter = Gson().getAdapter(TypeToken.get(responseType)) adapter.fromJson(args[0].toString()) } as Api } }接着解析
val json2=File("data_json2.json").readText(); val result2:Data<Player> = Gson().fromJson(json2) println(result2) val result3:Data<Player> = ApiFactory.api.getObjTypeFromJson(json2) println(result3)从打印可以看到区别我们已经可以在运行过程中将Player对象确认。
我们首先先定义一个Kotlin Person类:
data class Person(var name:String, var age:Int) { }在Java中使用
public class AccessToPerson { public static void main(String[] args) { Person person=new Person("wt",23); System.out.println("before age="+person.getAge()+",name="+person.getName()); person.setName("tw"); person.setAge(24); System.out.println("after age="+person.getAge()+",name="+person.getName()); } }我们知道kotlin中默认是会有空检测,那么
person.setName(null);将在运行过程中抛出异常 当我们不想让kotlin自动生成get set函数时,通过注解@JvmField修饰该属性,例如我们用age举例
data class Person(var name:String,@JvmField var age:Int) { }此时可以看到age的get和set 已经无法调用了,那么这个时候该如何访问age呢?
Person person=new Person("wt",23); System.out.println("before age="+person.age+",name="+person.getName());这个时候我们使用点的形式直接访问age属性
首先创建一个object类
object ObjectHelloWorld { fun test(){ println("I am Hello world") }我们通过上面学习我们知道在kotlin通过object修饰表示一个单例类, 那么在java中如何使用该单例类呢:
public class AccessToObjectClass { public static void main(String[] args) { ObjectHelloWorld.INSTANCE.test(); } }首先用kotlin定义一个默认参数方法
class Overloads { fun overloads(a:Int,b:Int=1,c:Int=2){ println("a=${a},b=${b},c=${c}") } }在Java中调用:
public class AccessToOverloads { public static void main(String[] args) { Overloads overloads=new Overloads(); overloads.overloads(1,2,3); } }此时可以看到三个参数必须全部都填,否则就会报错,那么是不是kotlin中的默认参数在Java中无法使用了了呢?
现在我们来给kotlin中的默认参数函数加上一个@JvmOverloads注释
class Overloads { @JvmOverloads fun overloads(a:Int,b:Int=1,c:Int=2){ println("a=${a},b=${b},c=${c}") } }然后在java中使用: 可以看到之前填写的2个参数不会报错了
定义一个kotlin 包方法
fun helloKotlinPackage(){ println("hello kotlin package fun") } fun main(args:Array<String>){ }我们首先运行下kotlin,让它进行一个编译 在java中使用:
public class AccessTestPackage { public static void main(String[] args) { TestPackageKt.helloKotlinPackage(); } }其实kotlin中的文件在编译后都会在后面加上一个Kt,我们只要通过这种形式调用包方法就可以了。
首先定义一个kotlin扩展方法
fun String.notEmpty():Boolean{ return this !="" } fun main(args:Array<String>){ }我们首先运行下kotlin,让它进行一个编译 在java中使用:
public class AccessToExtensionMethod { public static void main(String[] args) { System.out.println(ExtensionMethodKt.notEmpty("hello")); } }跟我们访问包方法的形式类似,也是kotlin文件在编译后都会在后面加上一个Kt,我们只要通过这种形式调用包方法就可以了。
首先创建一个java类
public class DataClass { private String data; public String getData() { return data; } public void setData(String data) { this.data = data; } }在创建一个Kotlin file去访问这个类
fun main() { val dataClass = DataClass() dataClass.data = "1121" println(""+dataClass.data) }首先在java声明一个类,并声明一个函数返回null:
public class NullSafteyData { public String getData(){ return null; } }在kotlin中使用:
val nullSafteyData=NullSafteyData() val data=nullSafteyData.data println("data="+data)run 起来没什么问题 现在我们给data指定一个类型:
val data1:String=nullSafteyData.datarun 起来直接崩溃了,说明当指定具体类型时,kotlin会进行空检测,从而抛出异常 当我们加上 ? 试试
val data1:String?=nullSafteyData.data发现便没有问题了