共计 12388 个字符,预计需要花费 31 分钟才能阅读完成。
Kotlin 是一种在 Java 虚拟机上执行的静态型别编程语言,它主要是由俄罗斯圣彼得堡的 JetBrains 开发团队所发展出来的编程语言。该语言有几个优势
- 简洁——它大大减少你需要写的样板代码的数量。
- 安全——避免空指针异常等整个类的错误。
- 通用——构建服务器端程序、Android 应用程序或者在浏览器中运行的前端程序。
- 互操作性——通过 100% Java 互操作性,利用 JVM 既有框架和库。
在我们的 AndroidStudio 开发工具中,要想使用 Kotlin 这个优秀的开发语言,我们需要安装插件,直接在安装插件界面搜索 Kotlin 然后安装。之后再 gradle 文件增加如下配置
apply plugin:'kotlin-android'
apply plugin:'kotlin-android-extensions'
dependencies {compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"}
项目 gradle 文件
buildscript {
ext.kotlin_version = '1.1.1'
repositories {jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
完成上面的配置后,我们就可以愉快的玩耍了。
首先我们还和以前一样,创建一个 Android 项目,自动创建一个 Activity 之后我们再创建一个 java 类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {return true;}
return super.onOptionsItemSelected(item);
}
}
public class Test {
private static String str = null;
public static void main(String[] args) {
str = "Code4Android";
System.out.println(str);
}
}
那上面的代码如果用 kotlin 实现是什么样子呢。尽管现在我们还不能写出 Kotlin 代码,但是在安装插件后 AS 中提供了自动转换 Kotlin 代码的功能
转换后的 Kotlin 代码
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar)
val fab = findViewById(R.id.fab) as FloatingActionButton
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.action_settings) {return true}
return super.onOptionsItemSelected(item)
}
}
object Test {
private var str: String? = null
@JvmStatic fun main(args: Array<String>) {
str = "Code4Android"
println(str)
}
}
注意:AS 提供的 java 代码自动转换功能,我们不要轻易使用,更不要转化我们成熟的项目,如果需要就需要我们自己去重构实现。否则会有意向不到的事情等着你,毕竟转换不是那么智能。上面的代码只是让你先简单熟悉下 Kotlin 代码时什么样子的,接下来我们先去学习一下 Kotlin 的基本语法。相信很容易上手。
Hello World!
我们由一个简单的”Hello World!”输出程序开始。与新建 java 文件类似,如下图,我们选择 Kotlin File/Class. 创建一个 Kotlin 文件。
package com.learnrecord
/**
*Created by Code4Android on 2017/4/21.
*/
var str: String = ""
fun main(args: Array<String>) {
str = "Hello World!"
println(str)
}
上述代码就是简单的输出一个字符串“Hello World”,package 后面跟的是包名,我们看出了和 java 文件的区别,在包名后面没有以分号“;”结尾。在 Kotlin 语法中,语句结尾都不在有分号“;”。
在 Kotlin 中变量声明有两种类型,val 修饰变量是只读变量即只能赋值一次,再次赋值时就会编译错误,如果我们需要多次修改值就需要使用 var。在上面的 var str: String =“”中,str 是变量名,:String, 表明该变量是 String 类型变量,后面就是赋值语句。我们也可以这样写 var str=“”省略了生命变量类型,它可以根据赋的值而自动推断出类型。如果我们使用下面赋值语句 str=null,发现 null 是不能赋值的,这就是 Kotlin 的特性,如果我们想赋值 null, 可以修改为 var str:String?=””。
fun 就是函数生命,而这个 main 函数就和我们 java 中的 main 方法一样,是程序执行的入口。println 就是一个打印输出。
在 Kotlin 中有如下几种 Number 类型,他们都是继承自 Number 抽象类。
Float(32 位),Double(64),Int(32),Byte(8),Short(16),Long(64, 类型用大写 L,如 12L),Any(任意类型),数组类型 Array<T> 根据传入的泛型数据自动匹配类型,Kotlin 还提供了指定类型的 Array, 如 ByteArray,CharArray,ShortArray,IntArray,LongArray,FloatArray,DoubleArray,BooleanArray。在数组类型中都提供了 get(index),set(index,value)及 iterator()方法供我们使用。
val iArray: IntArray = intArrayOf(1, 2, 3)
val sArray: Array<String> = Array<String>(3, { i -> i.toString() })
val anyArray: Array<Any> = arrayOf(1, "2", 3.0, 4.1f) // 可将类型进行混排放入同一个数组中
val lArray: LongArray = longArrayOf(1L, 2L, 3L)
我们先来实现一个简单的数值求和的函数,通用实现方法如下
fun sum(a: Int, b: Int): Int {return a + b}
传入两个 Int 型数值,sum 是函数名,括号后面的:Int 表示该函数返回 Int 的值,函数体中对两个数字相加并返回。在 Kotlin 中表达式也可以作为函数体,编译器可以推断出返回类型,可以简化为
fun sum(a: Int, b: Int) = a + b
为了更好理解表达式可以作为函数体,我们可以创建一个函数获取两个数的最大值,如下:
fun max1(a:Int,b:Int)=if (a>b) a else b
需要注意的是若 if 后有多个表达式, 如下
fun max1(a:Int,b:Int)= if (a > b) {println(a)
a
} else {println(b)
// 如果我们将 println(b)放到 b 的下面, 运行会返回 kotlin.Unit 为无类型,// 返回值总是最后一个表达式的返回值或值
b
}
println(max1(1,3))
括号中的表达式顺序决定了返回的值及其类型。
如果我们的方法体仅仅是打印字符串,并不返回值则
fun printInt(a: Int): Unit {println(a)
}
Unit 就类似我们 java 中的 void,即没有返回值,此时我们可以省略
fun printInt(a: Int) {println(a)
}
对于函数体,方法或者类等和 java 一样也有一些修饰符,如下
- abstract // 抽象类标示
- final // 标示类不可继承,默认属性
- enum // 标示类为枚举
- open // 类可继承,类默认是 final 的
- annotation // 注解类
- private // 仅在同一个文件中可见
- protected // 同一个文件中或子类可见, 不可修饰类
- public // 所有调用的地方都可见
- internal // 同一个模块中可见, 若类不加修饰符,则默认为该修饰符,作用域为同一个应用的所有模块,起保护作用,防止模块外被调用。
直接上代码如下
// 操作符 shl 下面对 Int 和 Long
var a: Int = 4
var shl: Int = a shl (1) //Java 中的左移运算符 <<
var shr: Int = a shr (1) //Java 中的右移运算符 >>
var ushr: Int = a ushr (3) // 无符号右移,高位补 0 >>>
var and: Int = 2 and (4) // 按位与操作 &
var or: Int = 2 or (4) // 按位或操作 |
var xor: Int = 2 xor (6) // 按位异或操作 ^
print("/nshl:" + shl + "/nshr:" + shr + "/nushr:" + ushr)
print("/nand:" + and + "/nor:" + or + "/nxor:" + xor)
输出信息为
shl:8
shr:2
ushr:0
and:0
or:6
xor:4
在上面的部分操作符可达到逻辑操作符,当我们使用 Boolean 时,or() 相当于 ||,and() 相当于 &&,xor() 当操作符两边相反时为 true,否则为 false,not()时取反。
遍历数组
fun forLoop(array: Array<String>) {
// 第一种方式直接输出字符(类似 java foreach)for (str in array) {println(str)
}
//Array 提供了 forEach 函数
array.forEach {println(it)
}
//array.indices 是数组索引
for (i in array.indices) {println(array[i])
}
//(类似 javafor(int i=0;i<arry.length;i++))
var i = 0
while (i < array.size) {println(array[i++])
}
}
使用 when 判断类型
fun whenFun(obj: Any) {when (obj) {25 -> println("25")
"Kotlin" -> println("Kotlin")
!is String -> println("Not String")
is Long -> println("Number is Long")
else -> println("Nothing")
}
}
is 和 java 中 instanceof 是一个作用判断是否为某个类型。!is 即判断不是某个类型。
//@定义 label,一般用在内层循环中跳到外层循环:i in 0..2 等价于 java 中 for(int i=0;i<=2;i++)效果
loop@ for (i in 0..2) {for (j in 0..3) {println("i:" + i + "j:" + j)
if (j == 2)
//continue@loop// 跳到外层循环,继续往下执行
break@loop // 跳到外层循环 label 处,跳出改层循环
}
}
倒序输出是 downTo
// 倒序输出 5 4 3 2 1 0
for (i in 5 downTo 0) {println(i)
}
// 设置输出数据步长
for (i in 1..5 step 3) print(i) // 输出 14
//step 和 downTo 混合使用
for (i in 5 downTo 1 step 3) print(i) // 输出 52
/*** constructor: 构造函数
* constructor 无修饰符 (如:private) 时,constructor 可以省略:* 当是无参构造函数时,整个构造函数部分也可以省略,省略的构造函数默认是 public 的
* 由于 primary constructor 不能包含任何代码,因此使用 init 代码块对其初始化,
* 同时可以在初始化代码块中使用构造函数的参数
*/
open class People private constructor(var id: String, var name: String) {
// 可以类中初始化属性:var customName = name.toUpperCase() // 初始化属性
// 次构造函数,使用 constructor 前缀声明,且必须调用 primary constructor,使用 this 关键字
constructor(id: String, name: String, age: Int) : this(id, name) {println("constructor")
}
init {println("初始化操作,可使用 constructor 参数")
}
// 需要 open 修饰,子类才可以
open fun study() {print("study")
}
//People 前的冒号 ":" 是继承的意思,实现类接口的时候也是冒号
class Student(id: String, name: String) : People(id, name) {
var test: Number = 3
private var name1: String?
get() {return name1}
set(value) {name1 = value}
//override 修饰的方法,默认是可以被继承的。若希望不被继承,可以使用 final 关键词修饰
override fun study() {super.study()
}
}
}
数据类用来保存数据,类似于 POJO 类,使用 data 关键词进行定义,编译器默认会为数据类生成以下四个方法
- equals()
- hashCode()
- toString()
- copy() 通过数据类你会看到 Kotlin 的简洁性,我们创建一个 Staff 类,有 String 类型的 name,position 和泛型 T(使用泛型仅仅是为了在 Kotlin 中接触以下泛型)
java 实现代码:
public class StaffJ<T> {
private String name;
private String position;
private T age;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getPosition() {return position;}
public void setPosition(String position) {this.position = position;}
public T getAge() {return age;}
public void setAge(T age) {this.age = age;}
}
Kotlin 数据类:
data class Staff<T>(var name: String, val position: String,var age:T)
通过对比我们就看出了优点了,一行代码就实现了, 具体使用
var staff = Staff("code4Android","Android 工程师","22") // 实例化对象
要获取某个属性如获取名字 staff.name, 赋值就是 staff.name=”code4Android2″,既然说了这样可以赋值,但是动手的小伙伴说为什么我敲的报错啊,如下
staff.position="前端"
编译报错了,在前面我们说过 val 修饰的属性只能赋值一次,那在这里 val 修饰的属性我们是不能再次赋值的。
fun main(arg:Array<String>){var staff = Staff("code4Android","Android 工程师","22") // 实例化对象
staff.name="code4Android2"
var staff1=staff.copy()
// 使用 copy 的时候可以指定默认值,可以指定任意个用逗号 "," 隔开
var staff2=staff.copy(name="ccc",position = "kotlin")
println("name:${staff2.name} position:${staff2.position} age ${staff2.age}")
//staff.position="Kotiln" //val 不能再次赋值
var anotherStaff= Staff("code4Android","Android 工程师",22) // 实例化对象
println("staff toString(): ${staff.toString()}")
println("anotherStaff toString(): ${anotherStaff.toString()}")
println("staff hashCode(): ${staff.hashCode()}")
println("anotherStaff hashCode(): ${anotherStaff.hashCode()}")
println("staff is equals another staff ? ${staff.equals(anotherStaff)}")
}
上面使用了字符模板,在 Kotlin 中有两种字符模板形式,/$< 变量 >、${< 变量 >}
var name:String="Code4Android"
println("我的名字叫 $name")
println("我的名字叫 ${name}")
/**
* java 允许使用匿名内部类;kotlin 也有类似的概念,称为对象表达式 -object expressions
*/
open class KeyBord{open fun onKeyEvent(code:Int):Unit = Unit
}
/** 匿名内部类 **/
var key=object :KeyBord(){override open fun onKeyEvent(code:Int):Unit = Unit
}
枚举
enum class Color{RED,BLACK,BLUE,GREEN,WHITE}
fun display(){
var color:Color=Color.BLACK
// 转换指定 name 为枚举值,若未匹配成功,会抛出 IllegalArgumentException
Color.valueOf("BLACK")
Color.values() // 已数组的形式,返回枚举值
println(color.name)//// 获取枚举名称
println(color.ordinal)// 获取枚举值在所有枚举数组中定义的顺序,0 开始
}
在 Kotlin 中枚举还支持方法。
/**
* fun receiverType.functionName(params){
*body
*}
* receiverType : 待扩展的类名
* .:修饰符为扩展符
* functionName:为自定义的扩展函数名,
* params:为自定义的扩展函数参数,可为空
* 扩展函数作用域,受函数的 visibility modifiers 影响
* 扩展函数并没有对原类做修改,而是为被扩展类的对象添加新的函数。* 有一条规则,若扩展函数和类原有函数一致,则使用该函数时,会优先使用类本身的函数。*/
class Employee(var name: String) {fun print() {println("Employee")
}
}
// 扩展函数
fun Employee.println() {print("println:Employee name is $name")
}
/**
* 可以扩展一个空对象
*/
fun Any?.toString1(): String {if (this == null)
return "toString1:null"
else {return "toString1" + toString()
}
}
/**
* 扩展属性
* 由于扩展属性实际上不会向类添加新的成员,
* 因此无法让一个扩展属性拥有一个后端域变量. 所以, 对于扩展属性不允许存在初始化器.
* 扩展属性的行为只能通过明确给定的取值方法与设值方法来定义,也就意味着扩展属性只
* 能被声明为 val 而不能被声明为 var. 如果强制声明为 var,即使进行了初始化,* 在运行也会报异常错误,提示该属性没有后端域变量。*/
val Employee.lastName: String
get() {return "get:"+name}
使用
fun main(arg: Array<String>) {var employee = Employee("Code4Android")
employee.print()
employee.println()
println(employee.toString1())
println(employee.lastName)
}
**
* 被代理接口
*/
interface Base {fun display()
}
/**
* 被代理类
*/
open class BaseImpl : Base {override fun display() = print("just display me.")
}
/**
* 代理类,使用: 以及关键词 by 进行声明
* 许代理属性和其他继承属性共用
* class Drived(base: Base) : Base by base,BaseImpl()
*/
class Drived(base: Base) : Base by base
// 使用
fun show() {var b = BaseImpl()
var drived = Drived(b)
drived.display()}
**
* 代理类型:* 懒加载:Lazy
* 观察者:Delegates.observable()
* 非空属性:Delegates.notNull<>()
*/
class Water {public var weight:Int by Delegates.notNull()
/**
* 代理属性
*/
public val name: String by lazy {println("Lazy.......")
"Code4Android"
}
public var value: String by Delegates.observable("init value") {
d, old, new ->
println("$d-->$old->$new")
}
}
fun main(arg: Array<String>) {show()
var water = Water()
println(water.name)
println(water.name)
water.value = "11111"
water.value = "22222"
water.value = "33333"
println(water.value)
println(water.value)
// 必须先赋值否则 IllegalStateException: Property weight should be initialized before get.
water.weight=2
print(water.weight)
}
val String.lastChar: Char
get() = this[this.length - 1]
class A(val p: Int)
//1, 反射得到运行时的类引用:
val c = Student::class
//2, 函数引用
fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))
//3,属性引用(在此引用 main 函数主体外声明的变量)
println(::x.get())
::x.set(2)
println(x)
//4,::x 表达式评估为 KProperty<Int> 类型的属性
// 它允许我们使用 get() 读它的值或者使用名字取回它的属性
val prop = A::p
println(prop.get(A(1)))
//5,对于扩展属性
println(String::lastChar.get("abc"))
//6,与 java 反射调用
println(A::p.javaClass),
var f: Array<Field> = A::p.javaClass.declaredFields
伴生对象 (companion object) 类似于 java 中的静态关键字 static。在 Kotlin 没有这个关键字,而是伴生对象,具体用法
open class People constructor(var id: String, var name: String){
// 可以类中初始化属性:var customName = name.toUpperCase() // 初始化属性
// 使用 constructor 前缀声明,且必须调用 primary constructor,使用 this 关键字
constructor(id: String, name: String, age: Int) : this(id, name) {println("constructor")
}
init {println("初始化操作,可使用 constructor 参数")
}
//,Kotlin 的 class 并不支持 static 变量, 所以需要使用 companion object 来声明 static 变量,
// 其实这个 platformStatic 变量也不是真正的 static 变量, 而是一个伴生对象,
companion object {val ID = 1}
}
使用的话直接 People.ID。
单例模式
在 Kotlin 中使用 object 修饰类的时候,。该类是单例对象。
/**
* 使用 object 定义类,该类的实例即为单例,访问单例直接使用类名,* 不能通过构造函数进行访问,不允许有构造函数
* Place.doSomething() // 访问单例对象
*/
object Singleton {fun doSomething() {println("doSomething")
}
}
/**
* 实例化的时候,单例是懒加载,当使用的时候才去加载;而对象表达式是在初始化的地方去加载。*
* 当在类内部使用 object 关键词定义对象时,允许直接通过外部类的类名访问 * 内部对象进
* 而访问其相关属性和方法,相当于静态变量
* 可以使用 companion 修饰单例,则访问其属性或方法时,允许省略单例名
* MyClass.doSomething() // 访问内部单例对象方法
*/
class MyClass {
companion object Singleton {fun doSomething() {println("doSomething")
}
}
}
好了,今天就介绍到这里,文中若有错误欢迎指出,Have a wonderful day。示例源码传送门