Kotlin的不完全使用笔记


其实官网文档很棒棒啊,http://www.kotlincn.net/docs/reference/android-overview.html

我为啥自己还要写个解释不全说不定还不准的渣文呢?好记性不如烂笔头啊

首先,Kotlin是运行在Jvm上的静态语言,源自Scala和java的结合,特性?

空类型安全、Lambda表达式、扩展方法、类型推导、没有分号、不需要new等等...

HelloWorld?

使用IDEA,可以直接新建kotlin的项目,选JVM的项目,一路next,新建一个kotlin文件,怎么写?

fun main(args:Array<out String>){
println("hello World!")
}

这里的out可以省略,默认生成的也没有out

数据类:

data class Main(val id:Int,val name:String){}
//调用
printlin(Main(0,"name"))

data class会直接将Main类toString覆写,所以上面的输出和ToString一样,构造方法在Main类后面的括号内

使用Gradle进行依赖管理

用IDEA新建一个Gradle项目

Java里可以直接访问kotlin的类,关于空指针,kotlin在编译时就会报错,java则会在运行时报错,解决办法是在构造参数的后面加上?号,就像这样

data class Main(val id:Int,val name:String?){}

将Java代码复制到kt文件中IDE会自动提示转换代码, 但是转换出来的代码规范性和可读性都是没有那么好的。

java中自带反射的相关类,但是在kotlin中需要自己添加:

compile "org.jetbrains.kotlin:kotlin-reflect:1.2.20"

在代码中可以这样用

class HelloKotlin{
fun hello(){}
}

调用时

HelloKotlin::class.constructors.map(::println)

会输出构造函数的名字

集合遍历

上面的主函数,参数里那个out,是指泛型的extend,指继承者
.map{}是一个扩展方法,给所有可迭代的类型提供。比如Array、Collection
.map(::println)实际上是这样的,其传入的是一个lambda表达式

args.map{
println(it)
}
//上面也是这个意思
for (arg in args)
    println(arg)

这个it就是每个可迭代的项,::是指方法的引用,在java里是这样的

for(String arg:args){
System.out.println(arg);
}

所以你应该明白了。
另外没有返回值返回的类型是Unit类型所以main方法可以这样

fun main(args:Array<out String>:Unit){
printlin("hello World!")
}

扁平化集合flatmap

还是main方法

fun main(vararg args:String){   
}

这是可变参数vararg,Java中是 ...

接下来从控制台输入点什么,比如A_B_C输出A B C,Java是这样写的

public static void main(String[] args) {
    for (String a:args){
        String[] splits=a.split("_");
        for (String sp:splits){
            System.out.println(sp);
            System.out.println(" ");
        }
    }
}

kotlin是这样的

fun main(vararg args:String){
    args.flatMap {
        it.split("_")
    }.map {
                print("$it ")
            }
}

split中传入的是正则哦,对比下应该就清楚kotlin是啥意思了,flatMap也是一个扩展方法,刚刚是不是提到控制台参数,估计还是有人不知道main怎么传参吧,上图

另外打印中的$是字符串模板,$it可以输出其值,当然你也可以输出一些属性值,不过得加上一些东西

比如${it.length}

关于flatMap,这个在java里也是有的,但是开头说到和Scala有点像是为什么呢?你可以看看这篇文章

scala: map与flatmap的区别

你会发现 kotlin真的很像,但是写法上又更像java

枚举、When表达式

枚举:

enum class Lang{
    ENGLISH,
    CHINESE;
    //伴生对象, 类和伴生对象是一一对应的
    companion object {
        //这里相当于一个静态方法
        fun parse(name:String):Lang{
            return Lang.valueOf(name.toUpperCase())
        }
    }
}

用法?静态方法和枚举该怎么用就怎么用咯,main方法中传参

fun main(args: Array<String>): Unit {
    if (args.size==0)return
    val lang=Lang.parse(args[0]) //直接用 不new
    println(lang) //不要封号
}

提一提构造方法,控制台传入chinese

fun main(args: Array<out String>): Unit {
    if (args.size==0)return
    val  lang=Lang.parse(args[0])
    println(lang)
    lang.sayHello()
}

enum class Lang(val hello:String){
ENGLISH("Hello"),
CHINESE("你好");
//var是变量 val是常量
var msg:String = ""

//伴生对象, 类和伴生对象是一一对应的
companion object {
    //这里相当于一个静态方法
    fun parse(name:String):Lang{
        return Lang.valueOf(name.toUpperCase())
    }
}

//构造方法的方法体
init {
    //这里是可以访问到hello的
   msg= hello;
}

fun sayHello(){
    println(msg)
    println(hello)
}
}

输出都是一样的。

[扩展方法是一个重要的特性,可以不修改原始类,进行方法添加]如果想给Lang加上扩展方法呢?在普通方法前面加上类名.就好了

fun Lang.sayBye(){
}

那么when怎么用?

fun describe(obj: Any): String =
val res=when (obj) {
 1 -> "One"
 "Hello" -> "Greeting"
 is Long -> "Long"
 !is String -> "Not a string"
 else -> "Unknown"
}
println(res)

从上可见,when可以匹配任意类型,并且是有返回值的,更多的看这里(超乎你想象的强大):

http://www.kotlincn.net/docs/reference/basic-syntax.html#using-when-expression

文件读取?

val text = File(ClassLoader.getSystemResource("input.txt").path).readText()

就是这么简单

Retrofit2在kotlin中的使用

interface  GitHubService{
    @GET("https://api.github.com/repos/bboylin/UniversalToast/stargazers")
    fun getStartGazers(): Call<List<User>>
}

data class User(val login:String,val id:Long,val avatar_url:String)

object Service{
    val gitHubService:GitHubService by lazy {
        Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl("https://api.github.com")
                .build().create(GitHubService::class.java)
    }
}

fun main(args: Array<String>) {
   Service.gitHubService.getStartGazers().execute()
            .body()!!.map {
       println(it)
   }
}

记得加上依赖

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

噢,对了覆写方法应该怎样呢?

data class User(val login:String,val id:Long,val avatar_url:String){
    override fun toString(): String {
        return super.toString()
    }
}

其实差不多嘛,但是上面那个!!是啥呢?如果你去掉后,IDE会这样提示

查了下,是这个意思:

非空断言运算符( !! )将任何值转换为非空类型,若该值为空则抛出异常。

你可以写为?,然后意思是可以为空。用?号时为空的话,如果没数据,就输出空白,用!!则会是异常。

这是Kotlin的另外一个新特性,空安全

尾递归

/*原始方法 会栈溢出
  fun factorail(num:Int): BigInteger {
      if(num==0) return BigInteger.valueOf(1L);
      return BigInteger.valueOf(num.toLong()).times(factorail(num-1))
 }

  fun  main(args:Array<String>){
      print(factorail(10000))
  }
*/
/**
 * BigInteger= BigInteger.valueOf(1L)是当不赋值时默认的空构造函数会初始化value为1
 */class Result(var value:BigInteger= BigInteger.valueOf(1L))

//tailrec是作为了一个标记符,加上后就会自动进行尾递归优化
tailrec fun factorial(num:Int,result: Result){
    if(num==0)
        result.value=result.value.times(BigInteger.valueOf(1L))
    else{
        result.value=result.value.times(BigInteger.valueOf(num.toLong()))
        factorial(num-1,result)
    }
}

/**
 * val常量 var变量
 */fun main(args: Array<String>) {
    val result=Result()
    factorial(10000,result)
    print(result.value)
}

Kotlin中的单例模式

懒加载 线程不安全版 需要的时候再去初始化

class LazyNotThreadSafe {
//kotlin原版--------------------------------
    companion object{
        val instance by lazy(LazyThreadSafetyMode.NONE) {
            LazyNotThreadSafe()
        }
//JAVA翻译版--------------------------------
        //下面是另一种等价的写法, 获取单例使用 get 方法
        private var instance2: LazyNotThreadSafe? = null

        fun get() : LazyNotThreadSafe {
            if(instance2 == null){
                instance2 = LazyNotThreadSafe()
            }
            return instance2!!
        }
    }
}

java:

双重校验版 线程安全

class LazyThreadSafeDoubleCheck private constructor(){
//----------------原生版
    companion object{
        val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
            LazyThreadSafeDoubleCheck()
        }
//-----JAVA翻译版
        private @Volatile var instance2: LazyThreadSafeDoubleCheck? = null

        fun get(): LazyThreadSafeDoubleCheck {
            if(instance2 == null){
                synchronized(this){
                    if(instance2 == null)
                        instance2 = LazyThreadSafeDoubleCheck()
                }
            }
            return instance2!!
        }
    }
}

java:

内部静态类 线程安全

class LazyThreadSafeStaticInnerObject private constructor(){
    companion object{
        fun getInstance() = Holder.instance
    }

    private object Holder{
        val instance = LazyThreadSafeStaticInnerObject()
    }
}

java:

线程同步懒加载 线程安全 加同步锁,每次都会加锁,浪费资源

class LazyThreadSafeSynchronized private constructor() {
    //伴生对象
    companion object {
        private var instance: LazyThreadSafeSynchronized? = null

        @Synchronized
        fun get(): LazyThreadSafeSynchronized{
            if(instance == null) instance = LazyThreadSafeSynchronized()
            return instance!!
        }
    }
}

java:

懒人写法 类加载的时候初始化实例 会拖慢启动速度

object PlainOldSingleton {
}

java里是这样的

新的枚举

密封类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
官方链接:http://www.kotlincn.net/docs/reference/sealed-classes.html
要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。

扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。

//定义播放状态 枚举 每种类型只有一个实例
enum class State{
 IDLE,PAUSED,PLAYING
}

//播放控制 sealed另外一种限定状态个数的办法
sealed class PlayerCmd{
 //一个实例用object 多个用class
 class Play(val url:String, val position:Long=0):PlayerCmd()
 class Seek(val position:Long):PlayerCmd()
 //因为没有参数所以用Obj就好了
 object Pause:PlayerCmd()
 object Resume:PlayerCmd()
 object Stop:PlayerCmd()
}

//实现类 by
class Player {
 //捕捉状态变化
 private var state: State by Delegates.observable(State.IDLE, { prop, old, new ->
 println("$old -> $new")
 //?使得为空时不会执行
 onPlayerStateChangedListener?.onStateChanged(old, new)
 })

 private fun sendCmd(cmd: PlayerCmd) {
 when (cmd) {
 is PlayerCmd.Play -> {
 println("\nPlay ${cmd.url} from ${cmd.position}ms")
 state = State.PLAYING
 doPlay(cmd.url, cmd.position)
 }
 is PlayerCmd.Resume -> {
 println("\nResume. ")
 state = State.PLAYING
 doResume()
 }
 is PlayerCmd.Pause -> {
 println("\nPause. ")
 state = State.PAUSED
 doPause()
 }
 is PlayerCmd.Stop -> {
 println("\nStop.")
 state = State.IDLE
 doStop()
 }
 is PlayerCmd.Seek -> {
 println("\nSeek to ${cmd.position}ms, state: $state")
 }
 }
 }

 private fun doPlay(url: String, position: Long) {
 TODO("Play function not yet implemented")
 }

 private fun doResume(){
 //todo
 }

 private fun doPause() {
 //todo
 }

 private fun doStop() {
 //todo
 }

 //region api
 interface OnPlayerStateChangedListener {
 fun onStateChanged(oldState: State, new: State)
 }

 var onPlayerStateChangedListener: OnPlayerStateChangedListener? = null

 fun play(url: String, position: Long = 0) {
 sendCmd(PlayerCmd.Play(url, position))
 }

 fun resume() {
 sendCmd(PlayerCmd.Resume)
 }

 fun pause() {
 sendCmd(PlayerCmd.Pause)
 }

 fun stop() {
 sendCmd(PlayerCmd.Stop)
 }

 fun seekTo(position: Long) {
 sendCmd(PlayerCmd.Seek(position))
 }
 //endregion
}

fun main(args: Array<String>) {
 val player: Player = Player()
 player.play("http://xx.m4a")
 player.pause()
 player.resume()
 player.seekTo(30000)
 player.stop()
}

泛型

http://www.kotlincn.net/docs/reference/generics.html

注解

说明:

http://www.kotlincn.net/docs/reference/annotations.html

使用:http://www.kotlincn.net/docs/reference/kapt.html

Java访问Kotlin

定义一个数据类

data class Person(var name:String)

然后在Java中调用:

public static void main(String[] args) {
    Person person=new Person("name");
    person.getName();
    person.setName("other");
}

如果改成

data class Person(var name:String,@JvmField var age:Int)

然后在Java中的时候age这种属性就没有了set、get了,因为@JvmField使得属性直接暴露出来了,同时该属性不能声明为private,并且不能自定义get set

 单例?

object Singleton{
    fun printlnHello(){
        println("Hello")
    }
}

编译后看生成的字节码就会知道,已经帮你写好了单例模式,所以是可以直接用的

public static void main(String[] args) {
    Singleton.INSTANCE.printlnHello();
}

kotlin方法的默认参数

class Overloads{
    fun oberloads(a:Int,b:Int=0,c:Int=1){
        print("$a $b $c")
    }
}

在java中此时你是无法享受默认参数的便利的,需要这样改下

class Overloads{
    @JvmOverloads
    fun oberloads(a:Int,b:Int=0,c:Int=1){
        print("$a $b $c")
    }
}

加上注解,就可以了

public static void main(String[] args) {
    Overloads overloads = new Overloads();
    overloads.oberloads(1);
}

上图更直接

Kotlin可以将方法定义在包当中

Kotlin扩展方法的调用

fun String.isEmpty():Boolean{
    return this!=""
}

java中使用:编译后会以类文件名+Kt的方式去生成一个可直接调用的类

public static void main(String[] args) {
    MainKt.isEmpty("");
}

internal关键字

会使java无法访问到kotlin中被此关键字修饰的内容

Kotlin访问Java

korlin访问java中的类属性不再具有get/set,将只会显示属性

空安全问题

fun main(args: Array<String>) {
    val data = Data()
    val s = data.value
    println(s)
}

java:

public class Data {
    public String getValue(){
        return null;
    }
}

此时直接输出null,如果给val s指定类型,比如val s:String=data.value,此时编译就会报错,此时value被称为平台类型,此时编译器会不care,大多数时候需要你自己判断定义,以及觉得加不加?号  还有 !!

泛型:java中的?变成了*,上下限变成了out in,java可选带泛型,kt必须有泛型参数,因此可能无限循环

Kotlin中Any就是Object

线程与同步:Kotlin中方法Synchronized变成了注解方式,代码块变成了方法,同时Volatile也变成了注解

最后,这个笔记实在是做不下去了,以上内容来自视频:

https://github.com/enbandari/Kotlin-Tutorials

其仓库中包含大量kotlin文章,以及其公众号的推送,值得一看,目测是鹅厂大佬

入门,应该大大概概差不多了,毕竟官方文档那么好,而且与java如此相似

声明:TIL|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA[ZH]协议进行授权

转载:转载请注明原文链接 - Kotlin的不完全使用笔记


Life is very interesting. In the end, some of your greatest pains become your greatest strengths.