简介
Kotlin 是一个基于 JVM 的新的编程语言,由 JetBrains 开发。于2010年首次推出,次年开源。它与Java 100%互通,并具备诸多Java尚不支持的新特性
设计目标
创建一种兼容Java的语言
让它比Java更安全,能够静态检测常见的陷阱。如引用空指针
让它比Java更简洁 如高阶函数、扩展函数等
变量、方法、类的定义
变量常量定义
可变变量定义:
var 关键字
var <标识符> : <类型> = <初始化值>
var age :Int =1
var age =1
不可变变量定义:
val 关键字,只能赋值一次的变量(类似Java中final修饰的变量)
val <标识符> : <类型> = <初始化值>
方法定义
函数定义使用关键字 fun,参数格式为:参数 : 类型
fun sum(a: Int, b: Int): Int { // Int 参数,返回值 Int
return a + b
}
表达式类型确定的值作为函数体,返回类型自动推断
fun sum(a: Int, b: Int) = a + b
fun equals(a: Int, b: Int) = false
无返回值的函数(类似Java中的void):
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
// 如果是返回 Unit类型,则可以省略:
fun printSum(a: Int, b: Int) {
print(a + b)
}
匿名函数
fun main(args: Array<String>) {
val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
println(sumLambda(1,2)) // 输出 3
}
类的定义和实例
Kotlin 中使用关键字 class 声明类,后面紧跟类名:
class Foo { // 类名为 Foo
// 大括号内是类体构成
}
我们也可以定义一个空类:
class Foo
类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
class Foo {
var name: String = ……
var url: String = ……
var city: String = ……
}
属性可以放到构造函数里面
class Foo( var name: String , var url: String , var city: String)
数据类data class 自动获取需要的getters,setters,equals(),hashcode(),toString()和copy()函数
data class Person(var name: String,var age: Int,var height: Float = 1.8f)
等同的java class
public final class Person {
private String name;
private int age;
private float height;
public Person(String name, int age, float height) {
this.name = name;
this.age = age;
this.height = height;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
this.height = 1.8f;
}
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 float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
if (Float.compare(person.height, height) != 0) return false;
return name != null ? name.equals(person.name) : person.name == null
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
result = 31 * result + (height != +0.0f ? Float.floatToIntBits(height) : 0);
return result;
}
}
实例化一个对象,不需要new关键字
var foo = Foo()
字符串模板
$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:
如
val person = Person("小明",12)
val string = "person=[${person.name},${person.age}]"
对比java
Person person = new Person("小明",12);
String string = "person="+"["+person.getName()+","+person.getAge()+"]";
NULL检查机制
Kotlin 在变量定义的时候就指定是否可空
var name:String ="小明"
var city:String?=null//用?表示可空
String name ="小明"
String city =null
在一个方法中
var length =city.length()
在编译期间就会报错,这样写就不会编译出错
var length =city?.length()
如果用java来写
int length =city.length()
在编译期间是无法感知异常的,有可能在项目运行时就crash了
类型检测及自动类型转换
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 做过类型判断以后,obj会被系统自动转换为String类型
return obj.length
}
return null
}
还可以这样
fun getStringLength(obj: Any): Int? {
// 在 `&&` 运算符的右侧, `obj` 的类型会被自动转换为 `String`
if (obj is String && obj.length > 0)
return obj.length
return null
}
对比java 的代码,需要将数据类型强制转换
public Integer getStringLength(Object object) {
if (object instanceof String) {
//需要强转一下
return ((String) object).length();
}
return null;
}
扩展函数
相信大家项目中有很多的util类,如StringUtils
Kotlin有一个聪明的解决方案 , 扩展函数 ,帮助你摆脱所有的util类一劳永逸
fun Context.toast(text: String) = Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
在所有的Context子类中可以直接调用toast("somewords")
基本数据类型也可以这样
fun Int.dp2px():Int {
return ......
}
代码中可以直接调用
var px =12.dp2px()
默认参数
fun Date.format(format: String = "yyyy年MM月dd日 HH:mm"): String {
return SimpleDateFormat(format, Locale.CHINA).format(this)
}
可以在代码中这样使用
var dateString =Date().format()
也可以
var dateString =Date().format(“yyyy-MM-dd HH:mm”)
高阶函数
什么是高阶函数
高阶函数就是以另一个函数作为参数或返回值的函数,Kotlin可以以lambda或参数引用作为参数或返回值,所以,任何以lambda或函数引用作为参数或返回值的都是高阶函数
要使用Kotlin的高阶函数就必须遵循它的函数类型
先来看一个简单的例子,这是一个简单的函数类型申明
val sum = { x: Int, y: Int -> x + y }
之所以能这么写得益于Kotlin的类型推导,它的显示写法是这样的:
var sum:(Int,Int)-> Int = {x , y-> x + y}
高阶函数的使用
将函数类型作为参数,直接看代码
fun getNumResult(result: (Int, Int) -> Int): Int {
return result(1,2)
}
//调用
var value = getNumResult({ a, b -> a + b })
//方法里面最后一个参数是函数的时候可以省略
var value = getNumResult{ a, b -> a + b }
==> value = 3
var value = getNumResult{ a, b -> a * b }
==> value = 2
android 中常用的设置点击事件就可以简单的写成
view.setOnClickListener { v ->
{
//啊!我被点击了
}
}
//,当参数只有一个lambda参数的时候 可以省略
view.setOnClickListener {
//啊!我被点击了
}
正常的java代码
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//啊!我被点击了
}
});
高阶函数在集合中的应用
val list = arrayListOf("aaa", "bb", "c","b")
list.sortBy {
it.length
}
val find = list.find {
it.length == 1
}
val indexOfFirst = list.indexOfFirst {
it.startsWith("b")
}
val filter = list.filter {
it.length == 1
}
等等
运算符重载和迭代器重载
运算符重载
data class Area(var width: Int, var height: Int) {
operator fun plus(other: Area): Area {
return Area(this.width + other.width, this.height + other.height)
}
operator fun compareTo(other: Area): Int {
return this.width * this.height - other.width * other.height
}
}
val area1 = Area(6, 3)
val area2 = Area(4, 5)
print(area1 + area2)
==> Area(width=10, height=8)
print(area1 > area2)
==>false
常用的重载 加减乘除、compareTo等
迭代器重载
迭代器(iterator)是java中我们非常熟悉的东西了,数据结构如List和Set都内置了迭代器,我们可以通过它提供的方法类遍历访问一个聚合对象中的各个元素
data class Book(val name: String)
class Bookcase(val books: List<Book>)
operator fun Bookcase.iterator(): Iterator<Book> = books.iterator()
重载iterator方法后的类可以通过以下方法遍历
val list =ArrayList<Book>()
val case = Bookcase(list)
for (book in case) {
...
}
单例
java中单例的定义
public class DataCenter {
private static DataCenter sInstance;
private DataCenter() {
}
public static DataCenter getInstance() {
if (sInstance == null) {
sInstance = new DataCenter();
}
return sInstance;
}
}
Kotlin 单例实现只需要关键字object
object DataCenter {
}
object 全局声明的对象只有一个
内联函数
当我们使用lambda
表达式时,它会被正常地编译成匿名类。这表示每调用一次lambda
表达式,一个额外的类就会被创建,并且如果lambda
捕捉了某个变量,那么每次调用的时候都会创建一个新的对象,这会带来运行时的额外开销,导致使用lambda
比使用一个直接执行相同代码的函数效率更低。
如果使用inline
修饰符标记一个函数,在函数被调用的时候编译器并不会生成函数调用的代码,而是 使用函数实现的真实代码替换每一次的函数调用。
内联函数如何运作
当一个函数被声明为inline
时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方,下面我们来看一个简单的例子,下面是我们定义的一个内联的函数:
fun inlineFunc(prefix : String, action : () -> Unit) {
println("call before $prefix")
action()
println("call after $prefix")
}
我们用如下的方法来使用这个内联函数:
fun main(args: Array<String>) {
inlineFunc("inlineFunc") {
println("HaHa")
}
}
运行结果为:
>> call before inlineFunc
>> HaHa
>> call after inlineFunc
最终它会被编译成下面的字节码:
fun main(args: Array<String>) {
println("call before $prefix")
println("HaHa")
println("call after $prefix")
}
lambda
表达式和inlineFunc
的实现部分都被内联了,由lambda
生成的字节码成了函数调用者定义的一部分,而不是被包含在一个实现了函数接口的匿名类中。
内联扩展函数之let
let函数通常作用就是避免写一些判断null的操作。
let函数的使用的一般结构
object.let{
it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法
...
}
//另一种用途 判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
it.todo()
}
内联函数之with
with函数使用的一般结构
with(object){
//todo
}
适用于调用同一个类的多个方法或成员时,可以省去类名重复,直接调用类的方法即可
val person =Person()
with(viewHolder){
tvName.setText(person.name)
tvAge.setText(person.age)
}
内联扩展函数之apply
apply函数使用的一般结构
object.apply{
//todo
}
apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值
val person =Person().apply{
age =12
name ="小明"
}
泛型及reified函数
reified 具体化的,对比java和kotlin中的实现
public static <T> T parseObject(String text, Class<T> cls) {
return JSON.parseObject(text, cls);
}
inline fun <reified T> parseObject(text: String?): T {
return JSON.parseObject(text, T::class.java)
}
结合上面的扩展函数还可以这样
inline fun <reified T> String?.parseObject(): T {
return JSON.parseObject(this, T::class.java)
}
调用的时候
val testJson = "{\"age\":22,\"name\":\"小明\"}"
val person: Person = testJson.parseObject()
利用这种特性还可以实现一个函数返回不同的数据类型
inline fun <reified T> Int.times(): T? {
return when (T::class) {
Int::class -> (this * 2) as T
String::class -> ("$this$this") as T
else -> null
}
}
val stringValue: String = 12.times()
==> "1212"
val intValue: Int = 12.times()
==> 24
....