kotlin与java语言一样,编译成字节码后,在JVM虚拟机中运行。kotlin语言的特性依赖于kotlin的语法编译器。与kotlin类似的语言还有:
2019年谷歌宣布Kotlin成为安卓第一开发语言,安卓程序员由java转Kotlin已经迫在眉睫。
语言分为解释型和编译型:
C和C++
JS、Python
java准确来说属于半编译半解释的混合型语言,但更偏向于解释型。
Java通过编译器javac先将源程序编译成与平台无关的Java字节码(.class),再由JVM解释执行字节码。
JVM只负责对字节码文件.class解释执行,而不管字节码文件是怎么产生的,Kotlin就是此原理,运行前会先编译成class,再供java虚拟机运行。
Kotlin不仅能够,还能脱离虚拟机层,直接编译成可以在Windows、Linux和MacOS平台上运行的原生二进制代码,被称为Android领域的Swift
Kotlin应用入口是主函数main,主函数可以不带任何参数,没有返回值。
fun main() {
var str:String = "Hello World"
println(str)
}
可以没有参数,也可以带参数列表,但程序入口是没有返回值的。
fun main() {
}
fun main(args: Array<String>) {
}
kotlin中取消了Java中原有的基本数据类型,全部使用对象类型。
kotlin提供了两种方式用于对象的比较
==
或者equals
)===
fun main() {
//sampleStart
val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
val b: Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedA === anotherBoxedA) // true
println(boxedB === anotherBoxedB) // false
//sampleEnd
}
由于 JVM 对 -128 到 127 的整数(Integer)应用了内存优化,因此,a 的所有可空引用实际上都是同一对象。但是没有对 b 应用内存优化,所以它们是不同对象(===
的结果为false)。
另一方面,它们的内容仍然相等:
fun main() {
//sampleStart
val b: Int = 10000
println(b == b) // 输出“true”
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB == anotherBoxedB) // 输出“true”
//sampleEnd
}
kotlin中较小的类型不能隐式转换为较大的类型,这意味着把Byte
类型赋值给一个Int
变量必须显式转换:
fun main() {
val b : Byte = 1
val i1: Int = b.toInt();
}
所有数字类型都支持转换为其他类型:
很多情况都不需要显式类型转换,因为类型会从上下文推断出来, 而算术运算会有重载做适当转换,例如:
val l = 1L + 3 // Long + Int => Long
Kotlin 提供了浮点类型 Float 与 Double 类型,这两个类型的大小不同,并为两种不同精度的浮点数提供存储:
Double
类型:val pi = 3.14 // Double
// val one: Double = 1 // 错误:类型不匹配
val oneDouble = 1.0 //Double
Float
类型,请添加f
或者F
后缀,如果这样的值包含多于6~7位十进制数,则会将其舍入:val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float,实际值为 2.7182817
fun main() {
fun printDouble(d: Double) { print(d) }
val i = 1
val d = 1.0
val f = 1.0f
printDouble(d)
// printDouble(i) // 错误:类型不匹配
// printDouble(f) // 错误:类型不匹配
}
Kotlin对于整数类型Int
与Long
提供了一组位运算。
Any是非空类型的根类型,与Object作为Java类层级结构的顶层类似,Any类型是Kotlin中所有非空类型的超类。
在Kotlin函数中使用Any,它会编译成Java字节码中的Object。
Any?类型是Kotlin所有类型的根类
在Kotlin类型层级结构的最底层是Nothing类型。类型结构图如下:
Unit
的作用类似于Java中的void
,但是void的含义是没有返回值,kotlin面对对象更加彻底,没有返回值的返回值也应该是一种对象,于是就有了Unit,这个概念
如果没有用return
明确指定返回值,那么编译器会自动加上返回Unit
:
fun returnUnit() : Unit {
return Unit
}
Unit
的含义这个函数是没有返回值的。
而Nothing
,则是提示开发者这个函数不会返回,例如它可能是抛异常
,不会返回,或者拥有无限循环的函数。
作用1:抛异常的函数的返回值
fun throwOnNameNull() : Nothing {
throw NullPointerException("姓名不能为空!")
}
除了上边抛异常这个用法,==Nothing对象还是所有对象类型的共同子类型。==这样一来,实际上Nothing是多重继承的,即Kotlin中扩展了继承规则,类不允许多重继承,Nothing除外
,因为Nothing的构造器是私有的,它不会有任何实例对象,所以他的多重继承是不会带来任何实际风险的。
基于Nothing是所有类型的子类,可以用它结合泛型,构建通用的空List,如下:
作用3:Nothing使得kotlin在语法层面更加完整。
在kotlin的下层逻辑中,throw是有返回值的,它的返回值类型就是Nothing
因此下边的写法是合法的:
val nothing: Nothing = throw RuntimeException("抛异常!"
因为Nothing是所有类型的子类,所有下面的写法才能够成立:
var _name: String? = null
val name: String = _name ?:throw NullPointerException("_name 运行时不能为空!")
kotlin使用关键字var
声明可变变量,变量名与变量类型间用冒号:
隔开,这种脚本语言与Python一样,不需要;
结尾。
var b = 1
val
:不可变变量,对应Java的final
变量
val a = 1
上边的声明过程中并未使用冒号:
指明类型,因为Kotlin存在类推导机制,上述的a
和b
会根据赋值结果默认视为Int
主要是非final与final的区别。
fun 关键字用于声明方法:
下面是一个接收2个Int参数,返回Int的方法。
fun sum(a: Int, b: Int): Int {
return a + b;
}
方法主体可以是一个表达式,它的返回值可以被推断出来:
fun sum (a: Int, b: Int) = a + b
方法可以没有返回值,或者说返回一个无意义的值(Unit)
fun printSum(a: Int, b:Int): Unit {
println("sum of $a and $b is ${a + b}")
}
其中Unit也可以忽略不写;
fun printSum(a: Int, b:Int) {
println("sum of $a and $b is ${a + b}")
}
Kotlin支持函数存在默认值,使用如下:
fun main() {
myPrint(1)
myPrint(1, "lalala")
}
fun myPrint(value : Int, str : String = "hello") {
println("num is $value, str is $str")
}
若value想为默认值,则会保存,因为在使用时传入第一个参数必须是Int型。传入字符串类型会不匹配:
fun main() {
myPrint("zjm")//报错
}
fun myPrint(value: Int = 100, str: String) {
println("num is $value, str is $str")
}
Kotlin的函数传参支持与Python一样的键值对传参,从而可以改变参数的顺序:
fun main() {
myPrint(str = "zjm") //正确调用
}
fun myPrint(value: Int = 100, str: String) {
println("num is $value, str is $str")
}
定义一个类
类的属性可以放在定义中或者类里边,比如下边:
class Rectangle(var height: Double, var length: Double) {
var perimeter = (height + length) * 2
}
var rectangle = Rectangle(5.0, 2.0)
println("The perimeter is ${rectangle.perimeter}")
对于kotlin 1.4.30版本,类的继承用冒号:
来表示,类默认都是final的,不可继承,为了使得类可以被继承,用open
关键字,放在class前面:
open class Rectangle(var height: Double, var length: Double) {
var perimeter = (height + length) * 2
}
使用$
可以帮助定义字符串模板,转译字符串变量。
直接使用变量用$
,要使用表达式,需要大括号${}
下边是例子:
var a = 1
val S1 = "a is $a"
println(S1)
a = 2;
val S2 = "${S1.replace("is", "was")}, but now is $a"
println(S2)
结果:
a is 1
a was 1, but now is 2
fun getMax(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
上边的if也可以写成一个表达式,类似三目运算符:
fun getMax(a: Int, b: Int) = if (a > b) a else b
in
关键字val items = listof("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
for (index in item.indices) {
println("item at $index is ${items[index]}")
}
// 闭区间打印[1, 3]
// 输出 1,2,3
for (i in 1..3) {
println(i)
}
// 从7到0,降序,步长为2
// 输出 7,5,3,1
for (i in 7 downTo 0 step 2) {
println(i)
}
for- in
的区间默认是双闭区间,如果想使用左闭右开呢,需要借助关键字until
fun main() {
for (i in 0 until 10) {
}
}
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index++]}")
}
结果:
item at 0 is apple
item at 1 is banana
item at 2 is kiwifruit
when表达式看起来有点像java中的swich case。
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a String"
else -> "Unknown"
}
Kotlin中所有异常类继承自Throwable
类,使用throw
表达式抛出异常:
fun main() {
//sampleStart
throw Exception("Hi There!")
//sampleEnd
}
使用try … catch 表达式捕获异常:
try {
// 一些代码
} catch (e: SomeException) {
// 处理程序
} finally {
// 可选的 finally 块
}
可以有零到多个 catch 块,finally 块可以省略。 但是 catch 与 finally 块至少需有一个。
可以为null的变量,后边需要一个问号?
。
例如下边这个方法,返回Int或者null
fun parseInt(str: String) : Int? {
...
}
?.
该运算符运行你把一次null检查和一次方法调用合并为一个操作。例如
s?.toUpperCase()
等同于下边的写法:
if (s!=null) {
s.toUpperCase
} else {
null
}
安全调用不光可以调用方法,也能用来访问属性。
如果你的对象中有多个可空类型的属性,通常可以在同一个表达式中方便地使用多个安全调用。使用该运算符,不需要额外的检查,就可以在一行代码中访问到更深层次的属性。如下:
class Address(val streetAddress:String,val code:Int,val city:String)
class Company(val name:String,val address: Address?)
class Person(val name:String,val company: Company?)
fun Person.cityName():String{
val city = this.company?.address?.city
return if (city != null) city else "unKnown"
}
?:
Elvis 运算符接收两个运算数,如果第一个运算数不为 null,运算结果就是第一个运算数,否则运算结果就是第二个运算数。
例如下边,如果s不为空,则str为s,否则str为空字符串对象
fun foo(s: String) {
val str: String = s?:""
}
Elvis运算符经常和安全调用运算符一起使用,用一个值代替对null对象调用方法时返回的null。下面就对之前的代码进行简化:
fun Person.cityName():String {
val city = this.company?.address?.city
return city ?: "unKnown"
}
在 kotlin 中有一种场景 Elvis 运算符非常适合,像 return 和 throw 这样的操作其实是表达式,因此可以把它们写在 Elvis 运算符的右边。当运算符左边的值为null,函数立即抛出一个异常。
fun printPersonName(person : Person) {
val name = person.name? : throw IllegalArgumetException("no name")
println(name)
}
as?
as是用来转换类型的Kotlin运算符,和java类型一样,如果被转换的值不是你试图转换的类型。就会抛出ClassCastException异常。
as?
运算符尝试把值转换成指定的类型,如果值不是合适的类型则返回 null。我们可以将该运算符与 Elvis 结合使用,如实现 Person 类的 equals 方法
class Person(val name:String, val company: Company?) {
override fun equals(other: Any?): Boolean {
val otherPerson = other as? Person ?: return false
return otherPerson.name && other.company == company
}
}
!!
非空断言是kotlin 提供的最简单直接的处理可空类型值的工具。使用双感叹号表示,可以把任意值转换成非空类型。如果对 null 做非空断言,则会抛出异常。
fun ignoreNull(s:String){
val notNull = s!!
println(notNull.length)
}
如果该函数中s为null,则会在运行时抛出异常。抛出异常的位置是非空断言所在的那一行,而不接下来试图使用值的那一行。
因为异常调用栈跟踪的信息只表明异常发生在哪一行,为了让跟踪信息更加明确,最后避免在同一行中使用多个!!
断言。
let函数可以让处理可空表达式变的更加容易和安全调用运算符一起使用,它允许你对表达式求值,检查求值结果是否为null,并把结果保存为一个变量,所有的这些操作都在同一个简单的表达式中。
let函数所作的事情就是把一个调用它的对象变成lambda表达式的参数,结合安全调用语法,能有效地把调用let函数的可空对象转变为非空类型,换言之,安全调用的let只在表达式不为null时才执行lambda。
s?.let {
print(s.length)
}
如果要实现上边的效果,Java代码如下:
public static final void ignoreNull(@Nullable String s) {
if (s != null) {
System.out.print(s.length());
}
}
kotlin通常要求在构造方法中初始化所有属性,如果某个属性是非空属性,就必须提供非空的初始化值,否则就必须使用可空类型,如果这样做,该属性每一次访问都需要null检查
或者!!
断言。
class MyService {
fun performAction():String = "foo"
}
class MyTest{
private var myService:MyService?=null
fun test() {
myService!!.performAction()
}
}
可以使用lateinit
来延迟初始化myService,延迟初始化 的属性都是var,尽管是非空类型,但也不需要在构造方法中去初始化它。
class MyService{
fun performAction():String = "foo"
}
class MyTest{
// 声明一个不需要初始化器的非空类型的属性
private lateinit var myService:MyService
fun test(){
myService.performAction()
}
}
Kotlin标准库中定义了String的两个扩展函数isEmpty
、isBlank
isNullOrBlank
,判断是否为空字符串或者是否只包含空白字符,这种扩展函数可以允许接收者为null的调用:
fun verifyInput(input:String?){
if (input.isNullOrBlank()){
print("it is not ok")
}
}
不需要安全访问,因为函数内部会处理可能的null值:
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrBlank(): Boolean {
contract {
returns(false) implies (this@isNullOrBlank != null)
}
return this == null || this.isBlank()
}
Kotlin中所有泛型类和泛型函数的类型参数默认都是可空的,这种情况下,使用类型参数作为类型的声明都允许为null,尽管类型参数T没有用问号结尾。
fun <T> printSomething(t:T){
// 因为 t 可能为 null,所以必须使用安全调用
print(t?.hashCode())
}
// 传入 null 进行调用
printSomething(null)
在该方法中,类型参数 T 推导出的类型是可空类型 Any? ,因此尽管没有使用问号结尾,实参 t 仍然允许使用 null。
要是类型参数非空,必须要为它指定一个非空的上界,那样泛型会拒绝可空值作为实参。
// 这样的话,T 就不是可空的
fun <T:Any> printSomething1(t:T){
print(t.hashCode())
}
用is
来检查某个对象是不是某个类型,如果确定某个不可变的变量的类型,那后边使用它的时候不用再显式转换:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 这里obj已经转为String类型
return obj.length
}
return null
}
或者用!is
反过来写:
fun getStringLength(obj: Any): Int? {
if (obj !is String) return null
return obj.length
}
创建一个Person类:
class Person {
var name = ""
var age = 0
fun printInfo() {
println("name is $name, age is $age")
}
}
Kotlin中默认的类是不可被继承的(即被final修饰),如果想让这个类能够被继承,需要在class
前使用open
关键字。
open class Person {
var name = ""
var age = 0
fun printInfo() {
println("name is $name, age is $age")
}
}
声明一个Student类继承Person类,kotlin中继承使用:
后接父类的构造:
class Student : Person() {
var number = ""
var grade = 0
fun study() {
println("$name is studying")
}
}
构造分为主构造和次构造
主构造直接写在类后边
class Student(val number: String, val grade: Int) : Person() {
fun study() {
println("$name is studying")
}
}
创建一个Student对象:
val student = Student("1234", 90)
因为父类Person还有name和age属性,因此给父类Person也构建一个主构造:
open class Person(val name : String, val age : Int) {
fun printInfo() {
println("name is $name, age is $age")
}
}
此时Student报错,因为继承Person时,后边使用的是Person()无参构造,而上边我们修改了Person的有参构造,则就不存在无参构造了。
class Student(name: String, age: Int, val number: String, val grade: Int) : Person(name, age){
fun study() {
println("$name is studying")
}
}
此时构造一个Student类;
val student = Student("zjm", 20, "1234", 90)
如果构造时需要特殊处理怎么办?kotlin提供了init
结构体,主构造的逻辑可以在init中处理,如:
open class Person(val name : String, val age : Int) {
init {
println("name is $name")
println("age is $age")
}
}
上述修改都为主构造
,如果类需要多个构造怎么办?此时需要借助次构造
次构造,使用constructor
关键字声明构造器,:
后边接this()
,即次级构造器的本质是调用主构造
实现下边两个次构造:
class Student(name: String, age: Int, var number: String, var grade: Int) : Person(name, age){
fun study() {
println("$name is studying")
}
constructor(name: String, age: Int, number: String) : this(name, age, number, 0) {
}
constructor() : this("", 0, "", 0) {
}
}
// 调用
val student = Student("lzy", 23, "1234", 90)
val student1 = Student("lzy", 23, "121");
val student2 = Student()
Java和kotlin的不同如下表:
kotlin引入internal
,摒弃了default
数据类只处理数据相关,与Java Bean
类似,通常需要实现其get
,set
,hashcode
,equal
,toString
等方法。
下面实现UserBean
,包含:id
、name
、pwd
属性。
Java:
public class UserBean {
private String id;
private String name;
private String pwd;
public UserBean() {
}
public UserBean(String id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserBean userBean = (UserBean) o;
return Objects.equals(id, userBean.id) && Objects.equals(name, userBean.name) && Objects.equals(pwd, userBean.pwd);
}
@Override
public int hashCode() {
return Objects.hash(id, name, pwd);
}
@Override
public String toString() {
return "UserBean{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
Kotlin编写此类将变的非常简单,新建一个kt
文件,选择如下:
一行代码即可搞定,kotlin会自动实现上述方法。
Shift连按两次调出工具搜索
,搜索show kotlin ByteCode
,查看kotlin字节码:
确实是创建了相应的Java Bean 类
目前Java使用最广的单例模式(静态内部类)实现如下:
public class SingleInstance {
private SingleInstance() {
}
private static class SingleHolder {
private static final SingleInstance INSTANCE = new SingleInstance();
}
public static SingleInstance getInstance() {
return SingleHolder.INSTANCE;
}
public void test() {
}
}
在Kotlin
中创建单例类需选择Object
生成的代码如下:
object SingleInstance {
fun test () {
}
}
其对应的java
文件如下,和上述使用最多的java
单例实现类似:
public final class Singleton {
@NotNull
public static final Singleton INSTANCE;
public final void test() {
}
private Singleton() {
}
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
使用如下:
fun main() {
Singleton.test() //对应的java代码为Singleton.INSTANCE.test();
}
枚举类最基本的应用场景是实现类型安全的枚举:
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
每个枚举常量都是一个对象。枚举常量以逗号分隔。
因为每一个枚举都是枚举类的实例,所以可以这样初始化:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
每个枚举常量也都具有这两个属性:name 与 ordinal, 用于在枚举类声明中获取其名称与(自 0 起的)位置:
enum class RGB { RED, GREEN, BLUE }
fun main() {
//sampleStart
println(RGB.RED.name) // prints RED
println(RGB.RED.ordinal) // prints 0
//sampleEnd
}
定义一个接口:
interface Study {
fun study()
fun readBooks()
fun doHomeWork()
fun exercise()
}
继承接口只需在类的后边用,
,并实现Study
声明的全部函数:
class GoodStudent(name: String, age: Int, var ID: String, var grade: Int) : Person(name, age) , Study{
override fun study() {
TODO("Not yet implemented")
}
override fun readBooks() {
TODO("Not yet implemented")
}
override fun doHomeWork() {
TODO("Not yet implemented")
}
override fun exercise() {
TODO("Not yet implemented")
}
}
Kotlin支持接口方法的默认实现,JDK1.8以后也支持此功能,方法有默认实现,则继承类就不是必须实现此方法。
interface Study {
fun study() {
println("study")
}
fun readBooks()
fun doHomework()
}
实现多个接口时,可能会遇到同一方法继承多个实现的问题:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
上例中,接口 A 和 B 都定义了方法 foo() 和 bar()。 两者都实现了 foo(), 但是只有 B 实现了 bar() (bar() 在 A 中没有标记为抽象, 因为在接口中没有方法体时默认为抽象)。 现在,如果实现 A 的一个具体类 C,那么必须要重写 bar() 并实现这个抽象方法。
然而,如果从 A 和 B 派生 D,需要实现从多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。
只有一个抽象方法的接口称为函数式接口或 单一抽象方法(single abstract method , SAM)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
例如,有这样一个 Kotlin 函数式接口:
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
如果不使用 SAM 转换,那么你需要像这样编写代码:
// 创建一个类的实例
val isEven = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}
通过利用 Kotlin 的 SAM 转换,可以改为以下等效代码:
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
val isEven = IntPredicate { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}