晓我课堂

Kotlin,从入门到真香(一)

2021-10-25  本文已影响0人  丨逐风者丨

1.什么是Kotlin

Kotlin是由JetBrains公司开发的一门软件开发语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。除此之外Kotlin还可以编译成二进制代码直接运行在机器上(例如嵌入式设备或iOS)。

推出时间:2011年7月
发布时间:2016年2月
开发公司:JetBrains(不是Google哦)
最新版本:v1.5.31(截止2021年10月25日)
官方网站:Kotlin官网

Kotlin

2.Why Kotlin? (Kotlin官方)

Modern, concise and safe programming language

Easy to pick up, so you can create powerful applications immediately.

3.如何使用Kotlin

Java项目(IntelliJ IDEA)

1.如果没有现存项目,可以新建任意项目,这里以普通Java项目为例
打开IDEA,File->New->Projects->Java
2.在src下创建Package-->com.demo.java和com.demo.kt
3.在com.demo.kt包右键,New->Kotlin Class/File,创建HelloKotlin.kt,此时IDEA会提示我们给项目增加Kotlin依赖,按照提示下一步即可完成
4.Kotlin与Java可以互相调用(目前很少有100%Kotlin代码的项目)

Android项目(Android Studio)

1.打开Android Studio,建议使用3.6及以上版本,Kotlin支持较好。Android Studio 下载文件归档
File->New->New Project...->Empty Activity->language 选择Kotlin

创建Kotlin Android项目
2.根据提示一步一步安装Kotlin插件即可

4.基础语法(Compare with Java)

数值类型

Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于 Java 的是,String不属于数值类型,是一个独立的数据类型。

类型 位宽度 最大值 最小值
Double 64 1.7976931348623157E308 4.9E-324
Long 64 9223372036854775807L -9223372036854775807L - 1L
Float 32 3.4028235E38F 1.4E-45F
Int 32 2147483647 -2147483648
Short 16 32767 -32768
Byte 8 127 -128

注:Kotlin的数值类型都是封装类型,不像Java还分Float和float,int和Integer等
他们都是Number的子类,那我们就看看Number类

public abstract class Number {
    /**
     * Returns the value of this number as a [Double], which may involve rounding.
     */
    public abstract fun toDouble(): Double

    /**
     * Returns the value of this number as a [Float], which may involve rounding.
     */
    public abstract fun toFloat(): Float

    /**
     * Returns the value of this number as a [Long], which may involve rounding or truncation.
     */
    public abstract fun toLong(): Long

    /**
     * Returns the value of this number as an [Int], which may involve rounding or truncation.
     */
    public abstract fun toInt(): Int

    /**
     * Returns the [Char] with the numeric value equal to this number, truncated to 16 bits if appropriate.
     */
    public abstract fun toChar(): Char

    /**
     * Returns the value of this number as a [Short], which may involve rounding or truncation.
     */
    public abstract fun toShort(): Short

    /**
     * Returns the value of this number as a [Byte], which may involve rounding or truncation.
     */
    public abstract fun toByte(): Byte
}

从Number类可以看出,不同的数值类型之间可以相互转换,并且Kotlin为我们提供了toXxx()方法,但是要注意以下几点:
1.如果数值超过了目标类型范围,会返回:Infinity。(比如:Double.MAX_VALUE.toFloat())
2.类型转换后精度会丢失。(比如:将3.14.toInt().toDouble() = 3.0)

变量和常量

[变量]var = variable
var <标识符> : <类型> = <初始化值>

[常量]val = value
val <标识符> : <类型> = <初始化值>

明确初始化值时:
String str = "Java";// 定义一个字符串变量
int i = 0;// 定义一个int变量

final String str = "Java final";// 定义一个字符串常量
static final String STR = "静态常量";
var str:String = "Kotlin var"// 可修改
val str:String = "Kotlin val"// 不可修改

Kotlin会根据赋值自动确定类型,所以这里我们可能简写成:
var str = "Kotlin var"
val str = "Kotlin val"
var i = 0// Int
var d = 0.0// Double
var f = 0.0F// Float
var b = true// Boolean
...
不明确初始化值时:
String str;// 默认为null
int len = str.length();// 抛出NullPointerException
str = "Java";
错误写法-1
var str:String
str.length// 编译不通过,提示:Variable 'str' must be initialized

错误写法-2
var str:String = null// 编译不通过,提示:Null can not be a value of a non-null type String

正确写法√
var str:String? = null// 加上?,表示可以为空
val len = str?.length// Kotlin空安全机制,这里不会像Java那样抛出NullPointerException
str = "Kotlin"
Null 安全类型是 Kotlin 的杀手级功能,在 Kotlin 中,类型默认不可为空。

那么Null安全就一定安全,一定不会抛出NullPointerException吗?

当 Kotlin 代码必须调用 Java 代码时,事情会变得很糟糕,比如库是用 Java 编写的,我相信这种情况很常见。于是第三种类型产生了,它被称为平台类型。Kotlin 无法表示这种奇怪的类型,它只能从 Java 类型推断出来。 它可能会误导你,因为它对空值很宽松,并且会禁用 Kotlin 的 NULL 安全机制。这种情况就会出现NullPointerException。

public class Utils {
    public static String format(String text) {
        return text.isEmpty() ? null : text;
    }
}
fun callJava() {
    val str: String = Utils.format("")
    print("str = $str")
}

此时,程序会终止并抛出异常:java.lang.NullPointerException: Utils.format("") must not be null
为什么呢?
因为这里的str是String类型,不可为null,但是Utils.format("")会返回null,
在Kotlin里面是不可以把null赋值给非空类型,所以这里会抛出异常,那么正确的写法是什么呢?

fun callJava() {
    // 错误写法,会抛出NullPointerException
    try {
        val str: String = Utils.format("")
        println("str = $str")
    } catch (e: Exception) {
        e.printStackTrace()
    }

    // 让str2可以为空
    val str2: String? = Utils.format("")
    println("str2 = $str2")

    // str3不可为空,当Utils.format("")返回null的时候,str3="Empty"
    val str3: String = Utils.format("") ?: "Empty"
    println("str3 = $str3")
}

控制台输出内容
java.lang.NullPointerException: Utils.format("") must not be null
    at com.zcs.android.app.easy.AppRunner.callJava(AppRunner.kt:14)
    at com.zcs.android.app.easy.AppRunner$Companion.main(AppRunner.kt:7)
    at com.zcs.android.app.easy.AppRunner.main(AppRunner.kt)
str2 = null
str3 = Empty
延伸:?与!!的区别

var param:Param? = null
在程序某个地方可能会对param进行赋值,当需要使用param的时候,你可以这样做:

param?.xxx 告诉Kotlin,这个param有可能是null,你要帮我检查一下,如果param==null,不会报错,流程也不会被打断

param!!.xxx 告诉Kotlin,我非常确定param不是null,万一param为null,这里就会和Java一样抛出NullPointerException,并终止程序


Kotlin空安全并非绝对的。

lateinit(延迟初始化属性,只能用在var上)
lateinit var str: String// 延迟初始化

fun test() {
    str.length// 编译通过,运行时抛出异常:kotlin.UninitializedPropertyAccessException: lateinit property str has not been initialized
    str = "Hello Kotlin"
}
by lazy(惰性初始化,只能用在val上)

惰性初始化是一种常见的模式,直到第一次访问该属性的时候,才根据需要创建对象的一部分,当初始化过程消耗大量资源并且在使用对象时并不总是需要数据时,这个非常有用。

var isKotlin = true

private val language: String by lazy {
    println("isKotlin = $isKotlin")
    // 这里可以写逻辑,也可以调其他方法
    if (isKotlin)
        "Kotlin"
    else
        "Java"
}

fun test() {
    println("Before --> This language is $language")
    isKotlin = false
    println(" After --> This language is $language")
}

控制台输出
isKotlin = true
Before --> This language is Kotlin
 After --> This language is Kotlin

by lazy(mode) 委托属性可以用于只读属性的惰性加载,但是在使用 lazy(mode) 时经常被忽视的地方就是有一个可选的mode参数:
LazyThreadSafetyMode.SYNCHRONIZED:初始化属性时会有双重锁检查,保证该值只在一个线程中计算,并且所有线程会得到相同的值。
LazyThreadSafetyMode.PUBLICATION:多个线程会同时执行,初始化属性的函数会被多次调用,但是只有第一个返回的值被当做委托属性的值。
LazyThreadSafetyMode.NONE:没有双重锁检查,不应该用在多线程下。

lazy() 默认情况下会指定 LazyThreadSafetyMode.SYNCHRONIZED,这可能会造成不必要线程安全的开销,应该根据实际情况,指定合适的model来避免不需要的同步锁。

函数 - fun
fun <函数名>(<参数名>:<参数类型>):<返回值类型>{
    // 方法体
}

fun testKotlin(str:String, i:Int):Boolean{
    // logic code
    return false
}

或:

fun foo(){
    // logic code
}
条件控制

需求:考试成绩评级,100分S,90分及以上A+,80分及以上A,70分及以上B,60分及以上C,60分以下D

public String getGrade(int score) {
    if (score == 100) {
        return "S";
    } else if (score >= 90) {
        return "A+";
    } else if (score >= 80) {
        return "A";
    } else if (score >= 70) {
        return "B";
    } else if (score >= 60) {
        return "C";
    } else {
        return "D";
    }
}
fun getGrade(score: Int): String {
    return when (score) {
        100 -> "S"
        in 90 until 100 -> "A+"
        in 80 until 90 -> "A"
        in 70 until 80 -> "B"
        in 60 until 70 -> "C"
        else -> "D"
    }
}
或:
fun getGrade(score: Int): String = when (score) {
    100 -> "S"
    in 90..99 -> "A+"
    in 80..89 -> "A"
    in 70..79 -> "B"
    in 60..69 -> "C"
    else -> "D"
}

在Kotlin里面,when非常好用,并且给人的感觉有点像Java的switch语句,但其实并不是,他是if和多个else if的汇总。他集if和switch的优点于一身,可以处理各种各样的复杂逻辑。
注:在Kotlin里没有switch。

三目表达式
String lang = condition ? "Java" : "Kotlin"
val lang = if(condition) "Java" else "Kotlin"
三目表达式-嵌套
String lang = condition1 ? "Java" : condition2 ? "Kotlin" : condition3 ? "Python" : "C++";

三目表达式嵌套,真的是非常糟糕,可读性极低,维护成本高。

val lang = if (condition1) "Java"
           else if (condition2) "Kotlin"
           else if (condition3) "Python"
           else "C++"
当你这样写的时候,Android Studio会提示你:Cascade is should be replaced with when
自动替换后,如下:
val lang = when {
    condition1 -> "Java"
    condition2 -> "Kotlin"
    condition3 -> "Python"
    else -> "C++"
}
循环控制-基础
void testFor() {
    List<String> list = List.of("Java", "Kotlin", "Python", "C++");
    for (String lang : list) {
        System.out.println("lang = " + lang);
    }
}
fun testFor() {
    val list = listOf("Java", "Kotlin", "Python", "C++")
    for (lang in list) {
        println("lang = $lang")
    }
}
循环控制-withIndex
void forWithIndex() {
    int index = 0;
    List<String> list = List.of("Java", "Kotlin", "Python", "C++");
    for (String lang : list) {
        System.out.println(index + " --> " + lang);
        index++;
    }
}
fun forWithIndex() {
    val list = listOf("Java", "Kotlin", "Python", "C++")
    for ((index, lang) in list.withIndex()) {
        println("$index --> $lang")
    }
}
类继承

Kotlin 中所有类都继承自 kotlin.Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类
Any 默认提供了三个函数:
注意:Any 不是 java.lang.Object。
Kotlin和Java一样,一个类只能有一个父类,可以有多个接口。

equals()
hashCode()
toString()
抽象类

在Kotlin中,所有类默认都是final的,不可被继承,如果你需要写一个基类,那么有两种方式:
1.open class ClassName
2.abstract class ClassName
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。
注意:无需对抽象类或抽象成员标注open注解。

内部类

内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。

class Outer {
    private val bar: Int = 1
    var param = "外部类属性"

    // 内部类
    inner class Inner {
        fun foo() = bar  // 访问外部类成员
        fun innerTest() {
            val o = this@Outer //获取外部类的成员变量
            println("内部类可以引用外部类的成员,例如:${o.param}")
        }
    }
}
实体对象Entity
public class JavaBean {
    private String name;// 姓名
    private int age;// 年龄
    private String address;// 联系地址
    private String phoneNumber;// 联系电话

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}
class JavaBean {
    var name: String? = null// 姓名
    var age = 0// 年龄
    var address: String? = null// 联系地址
    var phoneNumber: String? = null// 联系电话
}

Kotlin会自动为每个属性增加getter和setter

如何使用这个JavaBean?

JavaBean bean = new JavaBean();
bean.setName("Java");
System.out.println("name is " + bean.getName());
val bean = JavaBean()
bean.name = "Kotlin"// 这里相当于是setName("Kotlin")
println("name is ${bean.name}")// 这里相当于是getName()
类扩展(杀手锏之一)

Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

1.新建一个kt文件例如AppExtension.kt
(kt分普通file和class,普通文件可以不存在class,从理论上来讲,你的整个项目甚至可以只有一个kt文件)

// 扩展JavaBean,新增say()方法
fun JavaBean.say() {
    println("My name is $name")
}
// 扩展String类,新增testExtension()方法
fun String.testExtension(): String = "String is --> $this"

===== 如何使用? =====

fun main(args: Array<String>) {
    val s = "Kotlin"
    println(s.testExtension())

    val javaBean = JavaBean()
    javaBean.name = "Kotlin"
    javaBean.say()
}

===== 控制台输出:=====
String is --> Kotlin
My name is Kotlin

5.尾声

一边是稳健老练的 Java,一边是新生代 Kotlin,为什么谷歌钦点后者作为 Android 官方编程语言?谷歌产品管理总监斯蒂芬妮·卡特伯森(Stephanie Cuthbertson)给出了五个理由:

第一,Kotlin 是一门漂亮的现代编程语言,它利用了开发人员已经熟悉的许多最佳实践;
第二,Kotlin 完全可以与 Java 互操作,允许开发人员在不同语言之间来回调用;
第三,当谷歌决定采用 Kotlin 时,它已经诞生五年,并且已经有了稳定的版本;
第四,Kotlin 和 Android Studio 的底层平台都来自于 JetBrains,Kotlin 可以获得 Android 的全部支持;
第五,Kotlin 可以解决社区的痛点,利用一种语言在不同平台之间共享代码。

其实最根本的原因大家都心知肚明:Kotlin 不是 Oracle 的,毕竟谷歌和 Oracle 这两家巨头之间的版权战打了十年之久。从 Go 语言到 Dart,再到支持 Kotlin,谷歌在编程语言上的探索从未停止脚步。
不论如何,Kotlin 的确是一门优秀的编程语言,但要在 Android 编程上完全取代 Java,任重而道远。

下期预告:Kotlin 协程

上一篇下一篇

猜你喜欢

热点阅读