前言
Kotlin现在已经被Google列发Android的一级开发语言了,跟java同级,所以,必须要学下Kotlin了,事实上,Kotlin用起来是比java的体感是好太多了,再就是感谢下郭霖老师的直播,本文的内容就是根据郭霖老师的直播内容整理和个人的一些补充。
什么是Kotlin
- Kotlin是一个用于现代多平台应用的静态编程语言,有JetBrains开发。
- Kotlin可以编译成java字节码,也可编译成JavaScript,方便在没有JVM的设备上运行
- Kotlin已经成为Android官方支持的开发语言
为什么Kotlin可以编写Android程序?
- 因为使用java语言编程的代码最终需要编译成 .class 文件才能执行,而使用Kotlin语言编写的代码最终也会编成 .class 文件,对于Android 操作系统而言,它无须关心也无法关心程序是用什么语言开发的,只要最终执行的是一些合法的 .class 文件即可。
为什么我们要用Kotlin开发Android程序?
Google官方推荐和要求及钦点的!听说是因为java是甲骨文的,版权不属于Google
Java的不足
- 每行代码行尾毫无意义的分号
- switch语句只支持int条件,且case最后要加break
- 不是全面向对象的语言,而是半面向对象的语言
- 不支持字符串内嵌表达式,拼接字符串繁琐
Kotlin的内嵌字符串表达式
如我想拼接如下字符串
<a href="http://www.baidu.com">百度</a>
用java写的话就有点麻烦,还要注意转义符,可读性也差
String url = "http://www.baidu.com";
String website = "百度";
String link = "<a href=\""+url+"\">"+website+"</a>";
用kotlin的话
可以使用三引号声明原生字符串
{变量名.方法名()} 表示变量的返回值
var url = "http://www.baidu.com"
var website = "百度"
var link = """<a href = "$url"> $website</a>"""
Kotlin基础知识
快速入手一门新的语言,首先要做的就是找出与自己熟悉的语言同异之处
所以提及的内容都是与java用法相异的地方
使用工具观察Kotlin编辑成的java代码
菜单栏的Tool >> Kotlin >> Show Kotlin Bytecode

点击Decompile就可以查看Kotlin对于的java代码


基本数据类型
Kotlin的基本数据类型有Byte,Short,Int,Long,Float,Double
和Java不同的是开头大写
比较
在 Kotlin 中,三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小(同java的==和equal)。
方法和变量
方法的定义要使用关键字fun,后面加上方法名
fun helloWord(){
}
这个没有返回类型就相当于void
声明返回类型使用 :类型
fun helloWord():String{
return "helloWord"
}
定义带参数的方法使用 参数:类型
fun HelloWord(helloWord: String):String{
return helloWord
}
可变变量定义
var 变量名:类型 = 初始值
在kotlin里var声明的变量需要有初始值
var HelloWord: String= "helloWord"
var HelloWord1 = "helloWord" //也可以不写类型让系统自动推断
不可变变量定义
val 变量名:类型 = 初始值
相当于java中的final修饰的变量
val a:Int = 1
val b =1 //让系统自动推断为int类型
可见性修饰词
在Kotlin里,一共有4种修饰词,public,private,protected,internal
默认是public
其中public,private,protected的用法同Java
而internal则是同一个模块可见:
- 一个ide创建的 Module
- 一个Maven或者Gradle项目
- 通过一次调用Ant任务编译的一组文件
伴生对象 companion object
可以不用被实例化就能被使用,即可以通过其外部类就能直接访问,和static关键字很像,但只是看起来不是,实现上还是实例化了
class KotlinTest {
fun Test(){
println("helloWord")
}
companion object {
fun Test1(){
println("helloWord1")
}
}
}
可以看到能在别的类直接用了

NULL检查机制
在Kotlin中,对于声明可为空的参数,在使用是需要进行空判断处理的,意思就是它把空指针异常放到代码层面上来处理了
Kotlin的空处理有2中处理方式,一种是类型后面加!!像Java一样抛出空异常,另一种是类型后面加?可不做处理或者处理返回值为null或者配合?:做空判断处理
var age : String? = "abc" //类型后面加?表示可以为空
var age1 = age?.length //如果age为null,返回null,否则返回age.length
var age2 = age!!.length //如果age为null,抛出空指针异常,否则返回age.length
var age3 = age?.length ?:-1 //age为null返回-1,否则返回age.length
类型检测和自动类型转换
Kotlin里使用is运算符来检测一个表达式是否类型的一个实例(类似instanceof)
Any 相当于java.lang.Object
fun getStringLength(obj:Any):Int ?{
if (obj is String){
return obj.length //做过类型判断后,obj自动被转换为String类型
}
return null //这里的obj还是Any类型的引用
}
当然你也可以这样写
fun getStringLength(obj:Any):Int ?{
if (obj !is String){
return null
}
return obj.length //obj 的类型会被自动转换为 `String`
}
甚至可以
fun getStringLength(obj:Any):Int ?{
if ( obj is String && obj.length > 0) //在&&运算符的右侧,obj被自动转换为String类型
return obj.length
return null
}
区间
for (i in 1..4 ) print(i) //输出1234
for (i in 4..1 ) print(i) //什么也不输出
for (i in 1..4 step 2) print(i) //输出13
for (i in 4 downTo 1 step 2) print(i) //输出42
for (i in 1 until 10) print(i) //for(int i=1;i<10;i++)
类型转换
较小类型并不是较大类型的子类型,较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能把 Byte 型值赋给一个 Int 变量。
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误
我们可以代用其toInt()方法
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK
每种数据类型都有下面的这些方法,可以转化为其它的类型:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
有些情况下也是可以使用自动类型转化的,前提是可以根据上下文环境推断出正确的数据类型而且数学操作符会做相应的重载
val l = 1L + 3 // Long + Int => Long
字符
和 Java 不一样,Kotlin 中的 Char 不能直接和数字操作,Char 必需是单引号 ' 包含起来的。比如普通字符 '0','a'。
fun check(c: Char) {
if (c == 1) { // 错误:类型不兼容
// ……
}
}
数组
组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数
fun main(){
val a = arrayOf(1,2,3) //[1,2,3]
val b = Array(3, { i -> (i * 2) }) //[0,2,4]
//读取数组内容
println(a[0]) // 输出结果:1
println(b[1]) // 输出结果:2
}
除了类Array,还有ByteArray, ShortArray, IntArray,用来表示各个类型的数组,省去了装箱操作,因此效率更高,其用法同Array一样
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
字符串
和 Java 一样,String 是不可变的。方括号 [] 语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环来遍历
for (c in str) {
println(c)
}
条件控制
var a = 0
var b = 1
//当表达式用
val max = if(a > b) a else b
//或者赋值给一个变量
val min = if (a > b){
print(a)
a
}else{
print(b)
b
}
//使用区间
fun test(){
val x = 5
val y = 6
if ( x in 1..8){
print("x在区间内")
}
}
//when表达式
/* when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件
* when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,
* 符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。
*/
fun test(x:Any){
when(x){
1 -> print("x==1")
2 -> print("x==2")
//可以把多个分支条件放在一起,用逗号分隔
3,4 ->{
print("x 可能等于3")
print("x也可能等于4") }
//可以用区间和集合
in 5..7 -> print("x再5~7之间")
//可以判断类型
x is String -> print("x是字符串")
else -> print("x!=1 && x!=2")
}
}
类和对象
Kotlin里面是没有new关键字的
class Test {
var name:String = "abc"
}
val test = Test()
kotlin的getter和setter使用方法和Java是不一样的
class Test(){
var a = 1 //默认实现了getter和setter
var lastName:String = "zhuang" //效果同上
get
set
var firstName:String = "li"
get() = field.toUpperCase() //将变量赋值后转换为大写
set
var no:Int = 100
get() = field //同 get
set(value) {
if (value <10)
field = value
else
field = -1
}
}
fun main(args: Array<String>){
var test:Test = Test()
println("a:${test.a}") //输出a:1
test.lastName = "zhao"
println("lastName${test.lastName}") //输出lastName:zhao
test.firstName = "wang"
println("firstName${test.firstName}") //输出firstName:WANG
test.no = 9
println("no:${test.no}") //输出no:9
test.no = 20
println("no:${test.no}") //输出no:-1
}
关于这个filed关键字,就是后端变量(编译器会检查函数体,如果使用到了它,就会生成一个后端变量,否则就不会生成。我们在使用的时候,用field代替属性本身进行操作),在kotlin的getter和setter是不允许本身的局部变量的,因为属性的调用也是对get的调用,因此会产生递归,造成内存溢出,如我写了个var no:Int,当我写出no = ...时,这个等于号就会被Kotlin认为是调用setter,出现变量的地方就会认为是调用是getter。

可以看到编译器直接告诉你是递归了
翻译成java的话就很直观了
int no ;
public int getNo() {
return getNo();// Kotlin中的get() = no语句中出来了变量no,直接被编译器理解成“调用getter方法”
}
public void setNo(int value) {
if (value < 10) {
setNo(value);// Kotlin中出现“no =”这样的字样,直接被编译器理解成“这里要调用setter方法”
} else {
setNo(-1);// 在setter方法中调用setter方法,这是不正确的
}
}
非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用 lateinit 关键字描述属性
lateinit var a: String
一般构造函数的写法
//直接命名就可以了
class Bean(name: String ,age: Int, sex: Boolean){
var name: String = ""
var age: Int = 0
var sex = false
//init关键字是用来处理类的初始化问题的,因为在Kotlin中类的定义也是构造函数,是不能写代码的
init {
this.name = name
this.age = age
this.sex = sex
}
}
使用方法
var bean = Bean("abc",12,true)
var name = bean.name
var age = bean.age
var sex = bean.sex
多重构造函数
Kotlin中可以有一个主构造函数和多个次级构造函数,次级构造函数需要用this来委托
class bean{
var name: String = ""
var age: Int = 0
var sex = false
//主构造函数
constructor(age : Int){
this.age = age
}
//次构造函数
constructor(name: String):this(){
this.name = name
}
constructor(name:String, age: Int, sex: Boolean){
this.name = name
this.age = age
this.sex = sex
}
}
也可以这样写
class bean constructor(age : Int){
var name: String = ""
var age: Int = age
var sex = false
//次构造函数
constructor(name: String,age: Int):this(age){
this.name = name
}
constructor(name: String,age: Int,sex: Boolean):this(name,age){
this.sex = sex
}
}
使用方法同Java
var bean1 = bean(12)
var bean2 = bean("abc",12)
var bean3 = bean("abc",12,false)
继承
Kotlin里面是用 : 来继承的
/在Kotlin里,类的默认类型是final,是不可继承的,想声明该类可继承,需要使用open
open class Preson(var name : String, var age : Int){
}
class Student (name : String, age : Int, var score : Int):Preson(name,age){
}
如果基类存在次级构造函数,继承需要使用到super
open class Preson{
open fun study(){
print("aaa")
}
}
class Student : Preson() {
override fun study() {
print("abc")
}
}
类委托
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的
//创建接口
interface Base{
fun print()
}
//实现此接口的被委托类
class BaseImple(var x: Int) : Base{
override fun print() {
print(x)
}
}
//通过关键字by建立委托类
class Printer (b : Base) : Base by b
fun test(){
val b = BaseImple(10)
Printer(b).print() //输出10
}
可以看到,Printer没有实现接口Base的方法print(),而是通过关键字by,将实现委托给了b
属性委托
语法是val/var + 属性名 + :+ 类型 + by + 表达式
如果是val,只需实现getValue()
class Example{
var property : String by DelegateProperty()
}
class DelegateProperty(){
var temp = "old"
operator fun getValue(example: KotlinTest.Example, property: KProperty<*>): String {
return "DelegateProperty --> ${property.name} --> $temp"
}
operator fun setValue(example: KotlinTest.Example, property: KProperty<*>, s: String) {
temp = s
}
}
fun test(){
val e = Example()
print(e.property) //输出 DelegateProperty --> property --> old
e.property = "new "
print(e.property) //输出 DelegateProperty --> property --> new
}
标准委托
-
属性延迟
简单的就是懒汉式,定义时不进行初始化,把初始化的工作延迟到第一次调用的时候
val TAG: String = "test"
val lazyValue: String by lazy {
Log.d("TAG","Just run when first being used")
"value"
}
fun test(){
Log.d("TAG",lazyValue)
Log.d("TAG",lazyValue)
}
//输出结果
//Just run when first being used
//value
//value
换成java的写法就是
private static final String TAG = "test";
private String lazyValue;
public String getLazyValue(){
if (lazyValue == null){
Log.d(TAG, "getLazyValue: ");
lazyValue = "value";
}
return lazyValue;
}
public void test(){
Log.d(TAG,getLazyValue());
Log.d(TAG,getLazyValue());
}
-
可观察属性
可观察属性就是我们常用的观察者模式
class User{
var name: String by Delegates.observable("初始值"){
prop,old,new ->
println("旧值:$old -> 新值:$new")
}
}
fun main(){
val user = User()
user.name = "第一次赋值"
user.name = "第二次赋值"
}
//旧值:初始值 -> 新值:第一次赋值
//旧值:第一次赋值 -> 新值:第二次赋值
Kotin在Android里控件的使用方法
如我们在layout声明一个TextView
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
那我们直接使用ID就行了,因为在Android Studio里面自动帮我们导入了这个包

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
text.text="abc"
text.setOnClickListener(){
Toast.makeText(this,"click",Toast.LENGTH_LONG).show()
}
}
}
网友评论