Kotlin,从入门到真香(一)
1.什么是Kotlin
Kotlin是由JetBrains公司开发的一门软件开发语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。除此之外Kotlin还可以编译成二进制代码直接运行在机器上(例如嵌入式设备或iOS)。
推出时间:2011年7月
Kotlin
发布时间:2016年2月
开发公司:JetBrains(不是Google哦)
最新版本:v1.5.31(截止2021年10月25日)
官方网站: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 下载文件归档
创建Kotlin Android项目
File->New->New Project...->Empty Activity->language 选择Kotlin
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 <标识符> : <类型> = <初始化值>
明确初始化值时:
- Java
String str = "Java";// 定义一个字符串变量
int i = 0;// 定义一个int变量
final String str = "Java final";// 定义一个字符串常量
static final String STR = "静态常量";
- Kotlin
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
...
不明确初始化值时:
- Java
String str;// 默认为null
int len = str.length();// 抛出NullPointerException
str = "Java";
- Kotlin
错误写法-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。
- Java
public class Utils {
public static String format(String text) {
return text.isEmpty() ? null : text;
}
}
- Kotlin
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
- Java Solution
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";
}
}
- Kotlin Solution
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。
三目表达式
- Java
String lang = condition ? "Java" : "Kotlin"
- Kotlin(没有三目,可以用if或者when实现)
val lang = if(condition) "Java" else "Kotlin"
三目表达式-嵌套
- Java
String lang = condition1 ? "Java" : condition2 ? "Kotlin" : condition3 ? "Python" : "C++";
三目表达式嵌套,真的是非常糟糕,可读性极低,维护成本高。
- Kotlin
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++"
}
循环控制-基础
- Java
void testFor() {
List<String> list = List.of("Java", "Kotlin", "Python", "C++");
for (String lang : list) {
System.out.println("lang = " + lang);
}
}
- Kotlin
fun testFor() {
val list = listOf("Java", "Kotlin", "Python", "C++")
for (lang in list) {
println("lang = $lang")
}
}
循环控制-withIndex
- Java
void forWithIndex() {
int index = 0;
List<String> list = List.of("Java", "Kotlin", "Python", "C++");
for (String lang : list) {
System.out.println(index + " --> " + lang);
index++;
}
}
- Kotlin
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
- Java
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;
}
}
- Kotlin
class JavaBean {
var name: String? = null// 姓名
var age = 0// 年龄
var address: String? = null// 联系地址
var phoneNumber: String? = null// 联系电话
}
Kotlin会自动为每个属性增加getter和setter
如何使用这个JavaBean?
- Java
JavaBean bean = new JavaBean();
bean.setName("Java");
System.out.println("name is " + bean.getName());
- Kotlin
val bean = JavaBean()
bean.name = "Kotlin"// 这里相当于是setName("Kotlin")
println("name is ${bean.name}")// 这里相当于是getName()
类扩展(杀手锏之一)
Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
- Java
无
- Kotlin
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 协程