Kotlin(一)掌握基础知识
Kotlin(二)掌握基础知识
这一章我们主要对kotlin中的类做下回顾
1. 可见性修饰符
2. 复合符号( ? ?. ?: !! as? )
3. 类
3.1 默认类
3.2 嵌套类
3.3 内部类
3.4 匿名内部类
3.5 抽象类
3.6 数据类
3.7 密封类
3.8 枚举类
3.9 类的扩展
4.0 类的继承
- public,表示所有地方可见
- protected,受保护的可见性,对本包和所有子类可见
- private,私有的可见性,仅本类可见
- 默认,当不添加任何可见性时,即表示仅对本包可见
kotlin中可见性修饰符有:
- public(默认可见性修饰符),表示所有地方可见
- protected,同一个文件中或子类可见
- private,仅在同一个文件中可见
- internal,模块中可见;
我们该如何理解这里的模块?当用AS开发项目的时候,会新建一个工程,这个工程由很多model组成,各个model独立编译;每一个model就可以理解为一个单独的模块
var coutry: String = "China"
如果需要申明一个Null型的对象,就要使用?标识符了; 另外Kotlin中针对空安全有一套自己的机制,这套机制就可以通过如下复合符号来进行控制
- ? (表明该变量可以为null)
var nullCheck: String? = null;
- ?. (问号+点号来安全访问可能为null的对象),如果coutry为null,则返回null,反之则返回length
var coutry : String? = "China";
var length = coutry?.length
- ?: (称之为null 合并运算符,功能:接受两个运算数,若第一个运算数不为null,运算结果就是第一个运算数;若第一个运算数为null,运算结果就是第二个运算数。)
var coutry : String? = "China";
val result = coutry?.length ?: -1
- !!. (称之为强调调用,作用是:当对象为null的时候主动抛出NullPointException),执行到第2行的时候,此处coutry为null,则会抛出NullPointException异常
var coutry : String? = null;
val result = coutry!!.length
- as? (作用是:安全类型转换,尝试把值转换成给定的类型,如果类型不合适就返回null)
- 定义一个类
- 类的默认构造函数,构造函数
- 类的函数/属性
//1:默认类的可见性为public,即所有地方可见
class NormalClass constructor(name: String, age: Int){
private var name :String? = "";
private var age :Int = 0;
//我们可以通过get和set方法来改变属性的值,其中为了避免get和set的重复调用,kotlin使用了field字段来表示,fidld被称之为幕后属性
private var consumeDate: Long = 0L
get() {
println("获取consumeDate属性值{$field}");
return field;
}
// set方法声明的value是参数名,表示属性实际赋值时的那个对象,约定俗成写做value,可以随意写成其他。
// field指向当前属性,field标识符只能用在属性的访问器内。
// 若想控制setter访问,可以私有化setter。 如
// var setterVisibility: String = "abc"
// 此 setter 是私有的并且有默认实现 private set
set(value) {
field = value;
println("写入{$field}");
}
init {
println("初始化函数$name $age");
this.name = name;
this.age = age;
}
//2:类名后面跟constructor表示这是主构造函数
//3: 一个类也可以有次级构造函数,如果类申明了主构造函数,则次级构造函数则必须主动调用主构造函数
public constructor(name: String, age: Int, school: String):this(name, age){
println("次级构造函数$name $age $school");
}
public fun printMessage(value: NormalClass) {
println("打印类的信息" + value.name + " " + value.age);
}
}
- 通过class来定义一个不可被继承的类,类默认可见性为全局可见
- 主构造函数是指在类名后跟constructor来申明
- 类中init函数表示类的初始化函数,当实例化一个类的对象的时候,该函数便会被执行
- 类可以自定义构造函数,如果类显示的申明了主构造函数,则自定义函数要显示调用主构造函数
- 类的属性我们可以自定义get和set方法,其中有一个field属性(后端属性),其作用是避免在自定义get和set方法的时候无限循环调用自身的get和set,这是因为kotlin中属性的取和调用都会调用自身的set和get
嵌套类即在类内部定义的类
class NormalClass constructor(name: String, age: Int){
public class nestNormalClass{
var name: String? = "嵌套类的名字";
public fun printNestMessage(){
println("打印嵌套类的信息" + this.name);
}
}
}
其调用方法为:
NormalClass.nestNormalClass().printNestMessage()
- 嵌套类有别与内部类的是:嵌套类无法使用外部类对象,即嵌套类不拥有外部类的引用,外部类却不能调用嵌套类当中的属性/方法
- 嵌套类的调用方法为:外部类.嵌套类.嵌套类方法/属性
内部类使用 inner 关键字来表示,内部类可以使用外部类属性/方法,即内部类拥有外部类的引用; 外部类不能调用内部类中的属性/方法
package com.kotline.learning
class NormalClass constructor(name: String, age: Int){
private var name :String? = "";
init {
println("初始化函数$name $age");
this.name = name;
}
// inner拥有外部类的句柄,即可以直接访问外部类的成员和属性
public inner class inlineNormalClass{
var innername: String? = "内部类的名字";
public fun printInnerMessage(){
println("打印内部类的名字 " + this.innername + " 外部类的名字 " + name);
}
}
}
调用方法:
var normalClass= NormalClass("张三", 10);
normalClass.inlineNormalClass().printInnerMessage() // 调用格式:外部类实例.内部类.内部类类方法/属性
匿名内部类也就是没有名字的内部类,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为 Outter$1.class。
在android中我们会看到如下代码
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
setOnClickListener方法传入的参数是一个接口,然后接口是不允许被实例化的,故而我们有两种方式实现setOnClickListener
- 创建一个类实现OnClickListener接口,然后传入这个类的对象,作为setOnClickListener的参数
- 通过匿名内部类的方式来实现,即上面的代码,这里的new不是实例化OnClickListener接口,这里的new View.OnClickListener其内部实现是创建了一个类,其实现了OnClickListener接口,这里的new就是创建一个匿名的类
在kotlin中,我们可以通过两种方式来创建匿名内部类
- 对象表达式
// 我们先定义一个接口
package com.kotline.learning
interface NameInterface {
fun reName()
}
那么我们这样使用他们
var normalClass= NormalClass("张三", 10);
normalClass.setRenameListener(object: NameInterface{
override fun reName() {
}
})
其中object为kotlin中的关键字,用于创建对象表达式和创建一个对象;
通过object我们也可以创建一个对象,这个用法类似于js中创建一个对象
var site = object{
var name: String = "菜鸟教程"
var url: String = "www.runoob.com"
}
println(site.name)
println(site.url)
如果将object使用在类上,即可以创建一个类似java中的单例类
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
/** 在使用类DataProviderManager的方法和属性的时候,只需要使用类名.属性/方法即可完成调用 **/
DataProviderManager.registerDataProvider(……)
- lambda表达式,这里我们举一个实现runable接口的线程例子
Thread{
println("方法三running from lambda: ${Thread.currentThread()}")
}.start()
同理我们也可以使用对象表达式来实现
Thread(object: Runnable{
override fun run() {
}
}).start();
IDE会提示你将上面的代码修改为lambda表达式,则最终效果为:
Thread(Runnable { }).start();
在kotlin中如果参数只有一个lambda,则参数直接可以替换为{}的形式
kotlin中抽象类也和java一样,通过abstract class来定义抽象类,其不能被实例化,在抽象类中可以包含一般方法和抽象方法,抽象方法不能包含函数体abstract class AbstractClass {
public fun printMessage() {
}
public abstract fun printMessage(content: String)
}
- 数据类以data字段来表示是一个数据类,类似java中的javabean,用于保存数据
- 数据类至少包含一个主构函数,其不能继承其他类,但可以实现其他接口
- 当你创建一个数据类的时候,编译器会从构造函数中提取如下四个函数
1)equals: 用于比较两个数据对象是否相等
2)toString
3)一组componentN函数,这里的N与主构造函数中声明的属性数量是相同的,其对应第N个数据
4)copy:完整复制某个数据对象
data class DataClass constructor(var name: String, var age: Int){
public fun printMessage(){
}
}
使用的时候和普通类一样创建即可
var dataClas = DataClass("张三")
dataClas.toString();
println("数据类的component2方法 " + dataClas.component2());
// 输出为:
// 张三
// 数据类的component2方法 10
这里的toString不是Any类中的,是编译器为DataClass提取的函数,对于数据类,我们可以使用类似ES6中的解构解析的方式来赋值变量
var data = dataClas
println("数据类的解构解析 $data");
// 输出为: 数据类的解构解析 DataClass(name=张三, age=10)
此时我们就可以用解构解析的方式来获取属性了
var (name, age) = dataClas
println("数据类的解构解析name $name");
// 输出为:数据类的解构解析name 张三
1 . 使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中,所以我们可以在when表达式中利用密封类来做穷举半段
2 . 密封类子类的子类可以放在任何位置
3 . 它不能直接实例化
sealed class SealedClass{
data class Person(val num1 : Int, val num2 : Int) : SealedClass(){
var number1: Int = 10;
}
fun eval(expr: SealedClass): String = when(expr) {
is Person -> "Person"
}
}
kotlin中枚举类和java枚举一致,通过enum关键字来定义一个枚举类,枚举类和密封了的区别是:枚举类的中的每一个枚举常量都只能存在一个实例。而密封类的子类可以存在多个实例。 如下定义一个普通的枚举类
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
定义了一个枚举类Color,其枚举值分别是RED,BLACK…,如果没有指定枚举值具体指,则其值将从0开始,依次加一
类的扩展是指不通过继承的方法来实现对类的属性或者方法进行扩展,这点和JavaSscript中的原型链有点类似,一般不建议不要扩展函数,我感觉这样会破会类的封装性- 扩展函数,其语法格式为:
fun receiverType.functionName(params){
body
}
其中receiverType为待扩展的对象,functionName为待扩展的函数,params为函数参数,由此可见函数的扩展是静态的,即具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的
/**扩展NormalClass类,为该类新增一个PrintNormalMessage函数**/
fun NormalClass.PrintNormalMessage(){
println("**PrintNormalMessage**")
}
- 扩展属性,其语法格式为:
var receiverType.member: Type
get() {
}
set(value){
}
如
var NormalClass.lastIndex: Int
get() = 20
set(value){
println(value + 20)
}
在扩展函数/属性的时候我们应该注意以下几点:
- 扩展属性可以在kotlin文件或者类中,但是扩展的属性没有后端属性,顾不能显示初始化,需要通过get、set来对其进行取值或者赋值
- 扩展函数可以在kotlin文件或者类中,需要对函数设置可见性
新建一个Base.kt的文件,完整代码如下
import com.kotline.learning.NormalClass
/**扩展NormalClass类,新增lastIndex属性**/
var NormalClass.lastIndex: Int
get() = 20
set(value){
println(value + 20)
}
/**扩展扩展NormalClass类,新增printNormalMessage函数**/
public fun NormalClass.printNormalMessage(){
println("**printNormalMessage**")
}
fun main (args: Array<String>): Unit{
/**扩展属性和函数的调用**/
var normalClass: NormalClass = NormalClass("张三",10)
println("**扩展属性**" + normalClass.lastIndex)
normalClass.printNormalMessage();
}
- 扩展作用域
扩展的作用域在官网当中介绍比较少,让人听上去不是很明白;其实扩展的作用域我们可以简单理解为: 在一个kotlin文件(KotlinLearning.kt)中定义了一个函数printMessage, 那么在其他kotlin文件(KotlinOhter.kt)中就可以通过import的方式将其引入到现在的kotlin文件
// KotlinLearning.kt
package com.kotline.learning
fun printMessage(){
println("**printMessage**")
}
在其他kotlin文件中我们可以使用import来导入这个顶级包下的函数printMessage
package com.kotline.learning
import com.kotline.learning.*
fun main (args: Array<String>): Unit{
printMessage()
}
同理,接口也是可以被扩展的,如在协程里面定义了一个接口CoroutineScope
// CoroutineScope.kt
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
我们可以对其进行扩展来实现一个协程
// Builders.common.kt
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
在java中所有类的超类为Object.java,其内部有equals,toString等方法;而在kotlin中所有类的超类为Any.kt;
package kotlin
public open class Any {
public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}
我们可以使用open来标注一个类,来表示该类可以继承
open class Base(p: Int) // 定义基类
class Derived(p: Int) : Base(p)
在继承的时候,如果
- 子类有主构造函数,则基类必须在主构造函数中立即初始化
open class Person(var name : String, var age : Int){// 基类
}
/**Student继承自Person类,并且Student有主构造函数,
顾其基类Person也需要立即初始化
即此处写为Persion(name,age)**/
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {
}
- 子类没有主构造函,则必须在每一个二级构造函数中用 super 关键字初始化基类
class Student : Person {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}
在继承的时候,函数和属性可以通过使用override关键字来重写;要想使函数和属性可以被重写,我们需要使用open关键字来标记