开始学习Kotlin,将学习过程中遇到的,看到的知识点记录下来。
配置Kotlin环境
》在项目根目录build.gradle配置
buildscript {
ext.kotlin_version = '1.2.71'
repositories {
maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
》然后在app 的build.gradle中
apply plugin: 'kotlin-android'
还需要在依赖中
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"//不做检测会出现问题,找不到库
这时,就可以使用Kotlin开发了,而且可实现java,kotlin互调
》如果想简化findViewById过程,可添加
apply plugin: 'kotlin-android-extensions'
》如果Kotlin代码里面有使用到注解(@JvmStatic,@JvmField等),可添加
apply plugin: 'kotlin-kapt'
》》》构造函数
》》》泛型
在 Kotlin 中使用泛型,其中引入了 in 和 out
Out (协变)
如果你的类是将泛型作为内部方法的返回,那么可以用 out:
interface Product<out T> {
fun produce(): T
}
In(逆变)
如果你的类是将泛型对象作为函数的参数,那么可以用 in:
interface IPresenter<in V: IBaseView> {
fun attachView(mRootView: V)
fun detachView()
}
Invariant(不变)
如果既将泛型作为函数参数,又将泛型作为函数的输出,那就既不用 in 或 out。
》》》》修饰符
在Kotlin中,所有的类默认都是final的。如果需要允许类可以被继承,那么需要使用open声明。
在Kotlin中,abstract的用法几乎和java一致。当使用abstract修饰符时,可以忽略open,因为被abstract修饰的类默认具有open属性。
对于接口类来说,基本上用不上final,open,abstract,因为接口类默认是open,且不能被声明为final,如果接口类的方法没有函数体,那么其为abstract,但是不需要明确指出。
》》》Kotlin中属性的set,get
语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
格式就如上所示, set和get可写也可不写, 不写的话会有默认的实现, 需要注意的是val修饰的变量是没有set方法的, 也不允许重写set方法。
var no : Int = 0
get() = field
set(value) {
if (value < 10) {
field = value
} else {
field = -1
}
}
field的用法, field被大神们翻译成Backing Fields(后端变量), 它的作用就类似于java里的this.属性名。但是不能直接使用this.属性名,会造成递归调用内存溢出的, 因为在set和get中是不允许有本身的局部变量的 (例如如果属性名是name, 在set/get里是不能有这个变量的), 因为属性的调用也涉及到了set/get会造成递归调用, 所以要解决引用自身的问题, kotlin发明了field(后端变量)来解决这个问题。
注:不是set/get里不允许有局部变量, 是不允许有和属性本身相同名字的局部变量。
一、
Java中,所有的代码都依托于类而存在。但是在有些情况下,我们发现有些地方可能不是属于某一个特定类,有些属性也不是属于某一个特定类。所以我们就创建了很多的Java工具类 和 属性的常量类。如:
Java代码
public class Constant{
public static final String BASE_URL = "http://baobab.kaiyanapp.com/api/";
}
public class AppUtils{
public static String dateToString(Data date){
//...
return "";
}
}
其实上面的类只是为了承载我们的静态属性 和 方法,作为了静态方法 和 属性的容器,这就是我们目前遇到的问题,一大堆无用的容器类。
那么在Kotlin中,如何解决?
在Java中,类处于顶层,类包含属性 和 方法。在Kotlin中,函数站在了类的位置,我们可以直接把函数放在代码文件的顶层,让它不属于任何类。
Kotlin代码
package com.hazz.kotlinmvp.util
fun dateToString(date: Date): String{
//...
return ""
}
调用上面方法
import com.hazz.kotlinmvp.util
fun main(args: Array<String>){
dateToString(Date())
}
除了可以把函数直接放在文件顶层,还可以把属性直接放在顶层。
package com.seven.config
val REQUEST_URL = "http://localhost:8080/"
二、
在顶层声明的时候
1.变量定义
立即初始化
var num: Int = 10
推导出类型
var num = 5
没有初始化的时候,必须声明类型
var num : Float (这里的Float不能省略)
2.
val 用于声明不可变变量,也就是表示可读但不可写,相当于final修饰的变量
var 用于声明可变变量,可读可写
在类中声明 以及 声明可空变量
上面讲述的是变量的基础定义,而且只有在顶层声明的情况下 可以不用实例化。但是在实际开发中,一般都是在一个类中去定义变量,这种情况被成为声明类的属性。
特点如下:必须初始化,如果不初始化,需要使用 lateinit 关键字.
后期初始化 和 延迟初始化
1)后期初始化
声明后期初始化属性的特点:
》使用 lateinit 关键字
》必须是可读且可写的变量,即用 var 声明的变量
》不能声明于可空变量
》不能声明于基本数据类型
》声明后,在使用该变量前必须赋值,不然会抛出UninitializedPropertyAccessException
异常
如:
lateinit var a: Int //会报错,因为是基本数据类型
声明组件: lateinit var mTextView: TextView
赋值: mTextView = findViewById(R.id.text)
2)延迟初始化
所谓延迟初始化:是指当程序在第一次使用到这个变量(属性)的时候再初始化。
声明延迟初始化属性的特点:
使用 lazy{} 高阶函数,不能用于类型推断,且该函数在变量的数据类型后面,用 by 连接。
必须是只读变量,即用 val 声明的变量。
如: private val mStr: String by lazy { "延迟初始化。。。 "}
声明常量
Kotlin中只用 val 修饰还不是常量,只是一个不能修改的变量,还需要加上const。
特点: const只能修饰 val,不能修饰 var。
声明常量三种方式:
1)在顶层声明
2)在 object 修饰的类中声明,在Koltin中称为对象声明,相当于java中的单例类
3)在伴生对象中声明
object Constants {
val REQUEST_BASE_URL = "http://v.juhe.cn/"
val KEY = "1be865c0e67e3"
}
》》》》@JvmStatic @JvmField
如果纯用Kotlin编码,这两个注解没什么用处。这两个注解的用处都是在 Kotlin 与 Java代码互操作时。
@JvmStatic只能用在object类中 或者 companion类中。
通过java代码进行调用时,通过@JvmStatic声明的变量或者函数可以直接使用,未声明的变量或者函数不能直接调用。
在《Android Kotlin 指南》的文档中有提到:
伴生函数:
在"companion object"中的公共函数必须使用@JvmStatic 注解 才能暴露为静态方法。
如果没有这个注解,这些函数仅可用作静态Companion字段上的实例方法。
伴生属性:
在companion object中的公共、非const的属性实际上为常量,必须用@JvmField注解才能暴露为静态字段。
如果没有这个注解,这些属性只能作为静态Companion字段中奇怪命名的'getters'实例。而只使用@JvmStatic
而不是@JvmField的话,会将奇怪命名的'getters'移到类的静态方法中,但仍然是不正确的。
示例:
companion object {
//初始化fragment
fun instance(tagId: Int): TagListFragment {
val fragment = TagListFragment()
val bundle = Bundle()
bundle.putInt(BUNDLE_KEY_TAGID, tagId)
fragment.arguments = bundle
return fragment
}
}
在java中代码中调用
fragment = TagListFragment.Companion.instance(tagId);
如果给instance方法加上@JvmStatic注解,那么在java中调用(这时候调用的方式跟Java调用静态方法一致)
fragment = TagListFragment.instance(tagId);
》》》函数
1.with
定义:fun <T,R> with(receiver:T, block:T.() -> R):R
功能:将对象作为函数的参数,在函数内可通过this指代该对象。返回值为函数的最后一行 或 return表达式。
实例:
var paint = Paint()
paint.color = Color.BLACK
paint.strokeWidth = 1.0f
paint.textSize = 18.0f
paint.isAntiAlias = true
改为使用with:
var paint = Paint()
with(paint) {
color = Color.BLACK
strokeWidth = 1.0f
textSize = 18.0f
isAntiAlias = true
}
仅在不可空的对象上,且不需要返回值时使用。
2.takeIf 和 takeUnless
takeIf
定义:fun T.takeIf(predicate:(T) -> Boolean):T?
功能:传递一个函数参数,如果函数结果为true,返回T对象,否则返回null.
takeUnless
定义:fun T.takeUnless(predicate:(T) -> Boolean):T?
功能:与takeIf相反,参数函数返回false时返回T对象,否则返回null。
3. run
定义:1》fun run(block:() -> R):R 2》 fun <T,R> T.run(block:T.() -> R):R
功能:run函数返回值为函数体最后一行,或return表达式。
如果需要计算某个值或想要限制多个局部变量的作用域,可以使用run,如果需要将显示参数转换为隐式,请使用run。
4. repeat
定义: fun repeat(times:Int, action:(Int) -> Unit)
功能:重复执行action函数times次,times从0开始
实例:
repeat(5){ println("count:$it") }
等价于
for(i in 0..4) { println("count:$i") }
5. let
定义:fun <T,R> T.let(block:(T) -> R):R
功能:调用对象(T)的let函数,则该对象为函数的参数。在函数内可以通过it指代该对象。返回值为函数的最后一行或指定return表达式。
以下几种情况适合使用let
>判断某个值不为null时,执行代码;
>将可空对象转换为另一个可空对象
>限制单个局部变量的作用域
注意:如果判断一个值非空时处理一段逻辑,但为空时也有另一段逻辑需要处理,这时候不适合用let。
6. apply
定义:fun T.apply(block:T.() -> Unit):T
功能:调用对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象。
它跟run函数的区别,run返回的是最后一行,apply返回的是对象本身。
如果代码块中没有访问对应变量的任何功能,并且还想返回相同的值,请使用apply(),最常见的用法就是初始化对象时。
7. also
定义: fun T.also(block:(T) -> Unit):T
功能:调用对象的also函数,在函数块内可以通过 it 指代该对象,并返回该对象本身。
》》》构造函数
Java的构造函数:
》多个构造函数,构造函数之间是重载的
》可以不声明构造函数,编译器会自动生成一个无参的构造函数
Kotlin中构造函数:
》可以有一个主构造函数 和 多个次构造函数
》可以只有主构造函数 和 次构造函数
》主、次构造函数同时存在的时候,次构造函数必须直接或间接的委托到主构造函数
》没有声明主构造函数,会自动生成一个无参数的主构造函数,这点与java一样
》在主构造函数中可以设置默认值
class Person public constructor(var name: String){}
》》》数据类
声明实体类只需一行代码
data class User(val id: String, var name: String = "")
属性的set/get方法,toString方法自动生成。
可以这么使用:User(name = "zhangsan", id = "111") //可以打乱顺序
》》》
【强制】在java和kotlin混编时,kotlin中定义的能被java代码访问到的方法,其方法参数必须定义为可空类型,防止在java中调用该方法时,因为传入了null导致崩溃。除非你定义的方法中,参数如果为空需要主动抛出异常。
》》》字符串截取
因为在Kotlin
中,字符串的截取的函数subString()
是调用了Java
中的subString()
函数。
在Kotlin
中除了调用subString()
函数外,还可以调用subSequence()
函数
》》》》
Nullable变量的使用
- “?”符号的使用
Nullable变量进行操作时要带“?”,当变量为null时,不会出现异常,而是返回结果null:
var name: String? = null
var len = name?.length
print(len == null) //输出:true
- “?:”符号的使用
这个符号的作用是当它左边的结果为null时,进行右边的操作。
左边结果不为null:
var a: String? = "hello"
var b = a?.length ?: 100 //很明显左边不为null
println(b) //输出: 5
左边结果为null:
var a: String? = null
var b = a?.length ?: 100 //左边为null,返回右边的100
println(b) //输出: 100
》》》》
字符串拼接
字符串拼接像java一样可以使用"+"后者使用字符串模板
val s = "abc" + 1
val i = 10
println("i = $i") // 输出“i = 10”
//用花括号括起来的任意表达式
val s = "abc"
println("$s.length is ${s.length}") // 输出“abc.length is 3”
》》》》》》》》》》》》》》》》》位运算
shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与 &
or(bits) – 或 ||
xor(bits) – 异或
inv() – 反向