https://www.bilibili.com/video/BV1Ph411C7dG?p=5&spm_id_from=pageDriver
视频在P30之前还是值得看一看的,但是后面讲的太乱没有条理,感觉是想到什么讲点什么,一味的讲怎么用,具体能解决什么问题没有讲,示例也是Kotlin官网的示例,感觉没必要看了。
Kotlin
- P1 Kotlin中的新类型:函数类型
- p4 可空?、if
- p5 关键字 Any is 、遍历数组、when
- P6 函数式编程、lambda表达式、常用的集合操作符 `fifter` `map` `forEach` `any` `all` `find`
- P7构造函数、构造函数中参数和属性的区别
- P8继承
- P9对象 object、伴生对象 companion object
- P10属性赋值、获取是怎么实现的 ?、扩展函数、延迟初始化
- P12 扩展函数的作用域
- P13 数据类 `data class`、解构
- P14 密封类 `sealed class` 泛型 `generice`
- P15 -P18 协变和逆变:
- P19 嵌套类(默认)、内部类(inner)
- P20 对象表达式,可自定义方法,可继承类,可实现接口
- P21对象表达式、对象声明和对象表达式的区别
- P22枚举、委托类、类委托
- P23属性委托、延迟属性`lazy`、非空属性
- P24可观测属性、map属性委托
- P26 函数及其参数,当lambda表达式作为参数时
- P27参数为可变参数vararg
- P28中缀符号infix、内联函数inline
- P29函数式参数的应用,自定义fifter
- P30 lambda表达式就是用来代替匿名函数
- P31带接收者的函数字面值-匿名函数
- P33 list的一部分函数、Range、异常
- P34 throw 表达式、注解
- P35 Kotlin中引用JAVA代码
- P36 Kotlin调用JAVA
- P38 JAVA调用Kotlin(1)
- P39 JAVA调用Kotlin中伴生对象的方法和属性(2)
- P40类型擦除、@JvmName()
- P44反射:函数引用
学习笔记:
P1 Kotlin中的新类型:函数类型
参考视频
kotlin中新增了一种Java不存在的类型–函数类型
函数类型的对象:可以当函数来使用,可以作为函数的参数、函数的返回值、赋值给变量
创建函数类型对象有三种方式:双冒号加函数名、匿名函数、lambda表达式
//函数a的类型:(Int) -> Int
fun a(sum: Int): Int{ return sum}
//创建函数类型对象
//第一种:双冒号加函数名,::a是指向对象的引用,而这个对象具有a函数的功能,但不是a函数本身
val b = ::a
//第二种:匿名函数
val c = fun (sum: Int): Int{ return sum}
//第三种:lambda表达式,lambda表达式其实就是为了简化函数类型的写法,不用像上边的两个例子,只需要一句话就能写完
val d = { sum:Int-> sum }
//那么a这个函数的函数类型是什么呢?(Int) -> Int
//定义一个函数e,参数有两个,第一个是Int类型,第二个是一个函数类型(Int) -> Int,而 a 函数恰好符合这个函数类型
fun e (g: Int, f: (Int) -> Int){ println(f(g)) }
e(1,::a)
//将函数类型对象赋值给变量
val h = b
p4 可空?、if
- int和int?是两种不同的类型
- 在需要int?类型参与运算前需要判断是否为空
- if…else语句不需要return也可有返回值,返回的是语句块的最后一句的值,但是如果只有if的话不能直接写=号来返回,必须要有else。
fun main() {
val a = 1
val b = 2
val max = if (a > b){
a
}else{
b
}
}
- 在if中只有一句执行语句可省略大括号{}
fun main() {
val a = 1
val b = 2
val max = if (a > b) a else b
}
?. ?: as?
//假设一个值为可空类型
val s :String? = null
//错误代码:s为可空类型,不能直接使用,需要做判空处理
s.length
//s为空则返回空
println(s?.length)//输出:null
//s的length为null则返回0
println(s?.length?:0)//输出:0
//类型转换as
val i = s as Int
println(i)//编译不报错,运行时错误
//类型转换as?
val i = s as? Int
println(i)//输出为null
p5 关键字 Any is 、遍历数组、when
知识点:
- Any是顶层类,Any和int/String一样也是一个数据类型,想当于java中的Object,但是和Object不是一会事
- is关键字,相当于java中的instanceof,判断对象的类型
fun text (s: Any) : Any{
return if (s is String) s.toUpperCase() else s
}
fun main() {
val s = text(123)
print(s)
}
- 相同功能用java实现比较繁琐,主要是需要Object强制转换到某一个类型
public class Test {
private Object test(Object s) {
if (s instanceof String) {
String s1 = ((String) s).toUpperCase();
return s1;
}else {
return s;
}
}
public static void main(String[] args) {
Test test = new Test();
String s2;
Object abc = test.test("abc");
s2 = (String) abc;
}
}
知识点:
intArrayof
就是java中的int[]数组,在kotlin中没有基本数字类型,int
也是包装类,但是在JVM中则编译为int类型。in
关键字遍历- 在
array.winhIndex()
中即包含了下标也包含了值 array.indeices
的值是一段0…4
fun main() {
println("-------第一种遍历方法---------")
val array = intArrayOf(1,2,3,4,5)
for (item in array)
println(item)
println("-------第二种遍历方法---------")
for (i in array.indices)
println("array[$i] = ${array[i]}")
println("-------第三种遍历方法---------")
for ((index,value) in array.withIndex())
println("array[$index] = $value")
}
知识点:
when
自动返回最后一行代码的值- 可以有区间
in 2..10
- 在
when
中必须包含参数所有可能的赋值范围所对应的
fun strLength(string: String)=when(string.length){
1 -> "字符串长度是1"
in 2..10 -> "字符串长度是2-10之间"
else -> "字符串长度大于10"
}
P6 函数式编程、lambda表达式、常用的集合操作符 fifter
map
forEach
any
all
find
函数式编程
fun main() {
val array = listOf("hello","world","hallo world","welcome")
array.filter { it.length > 5 }.map { it.toUpperCase() }.sorted().forEach { println(it) }
}
- lambda表达式省略步骤:
maxBy
有迭代器(看源码)
val array = listOf("hallo","world","hallo world","welcome")
val lambda = { str:String -> str.length}
//lambda作为参数传入函数中
array.maxBy(lambda)
//lambda表达式代入函数中
array.maxBy({ str:String -> str.length })
//当lambda表达式时函数的最后一个参数时,可以将lambda表达式移到括号的外面
array.maxBy(){ str:String -> str.length }
//当lambda是唯一参数时,括号也可以省略
array.maxBy{ str:String -> str.length }
//maxBy函数有迭代器,所以传入lambda的参数是array的元素,所以lambda表达式的参数类型可以推导,所以省略
array.maxBy{ str -> str.length }
//lambda参数列表中只有一个参数时,参数也可以省略,可以用it代替
//如果省略参数,表达式中的参数必须用it
array.maxBy{ it.length }
- 常用的函数
fifter
map
forEach
any
all
find
这些函数共同的特点:
- 都是可迭代的(源码)
- 参数都是lambda代码
- 源码中可知这些函数都是Iterable迭代器的扩展函数,而
list
,array
等都是可迭代的
val array = listOf("hallo","world","hallo world","welcome")
//功能:筛选长度大于5的字符串,将这些字符串全部大写,并按字母排序,并打印
array.filter { s: String -> s.length > 5 }.map { s: String -> s.toUpperCase() }.sorted()
.forEach { s: String->println(s) }
//lambda代码简化后可写为
array.filter { it.length > 5 }.map { it.toUpperCase() }.sorted()
.forEach { println(it) }
//是否存在一个元素的长度大于5,返回值为Boolean
array.any{ it.length > 5 }
//是否所有元素的长度大于5,返回值为Boolean
array.all { it.length > 5 }
//查找满足条件的,返回第一个符合条件的元素
array.find { it.length > 5 }
//判断array中是否有元素
val array1 = listOf<String>()
println(array1.any()) //false
P7构造函数、构造函数中参数和属性的区别
类的构造函数:
- 次构造函数必须直接或者间接的调用主构造函数
- 次构造函数在执行时,先调用this中的构造函数,再调用本身
class Person1 constructor(var name: String){
private var age: Int
private var address: String
init {
println(name)
age = 0
address = ""
}
constructor(name: String ,age:Int):this(name){
println("name=$name,age=$age")
this.age = age
}
constructor(name: String,age: Int,address:String):this(name,age){
println("name=$name,age=$age,address=$address")
this.address = address
}
}
fun main() {
val person1 = Person1("zhangsan")
val person2 = Person1("lisi",20)
val person3 = Person1("wangwu",30,"hangzhou")
}
在类中,如果参数前没有val或者var,类认为该变量是一个参数而不是类的属性,在类中无法访问,只能作为参数给父类传值
class Person1 constructor(name: String, var age: Int,var address: String){
fun printInfo(){
println("name=$name, age=$age, address=$address")
}
}
//报错Unresolved reference: name
name和age变量作为参数,传值给父类
open class Person1 (val name: String,val age: Int)
class Student1 (name: String, age: Int,var address: String):Person1(name, age)
P8继承
继承:
- 类可以被继承需要
open
关键字修饰类 - 方法可以被子类改写,需要在父类的方法前加
open
关键字,子类方法前加override
关键字 - 如果子类已经改写了父类的方法,但是不想被孙子类改写,加
final
关键字
//类可以被继承需要open关键字修饰类
open class Person2 (val name:String,val age: Int){
//方法可以被子类改写,需要在父类的方法前加open关键字
open fun printInfo(){
print("name")
}
}
open class Student2 (name: String,age:Int,var address:String):Person2(name,age){
//子类改写父类的方法,子类方法前加override关键字
//加final关键字,孙子类无法改写此方法
final override fun printInfo(){
println("name=$name,age=$age,address=$address")
}
}
class Pupil (name: String,age: Int,address: String,val grade: String): Student2(name, age, address){
}
属性的继承:
- 需要在父类的属性加open关键字,才可在子类中重写
- JVM中
val
关键字修饰该属性只有get
方法,var
修饰的关键字有get
/set
方法,所以继承属性时,var
可以继承val
,val
不能继承var
open class Person2 (open val name:String,val age: Int)
open class Student2 (name: String,age:Int,var address:String):Person2(name,age){
override var name: String = "zhangsan"
}
fun main() {
val student = Student2("wangwu",30,"hangzhou")
println("name=${student.name}")
}
继承时方法重名:
- 子类继承的两个父类中,都有同一个方法,则编译器要求子类必须重写这个重复的方法
- 调用父类中重名的方法,使用
super<父类名>.方法名
interface Parent{
fun method(){
println("parent")
}
}
open class Parent1{
open fun method(){
println("parent1")
}
}
class SonClass:Parent,Parent1(){
override fun method() {
super<Parent1>.method()
super<Parent>.method()
}
}
P9对象 object、伴生对象 companion object
object定义的对象
- kotlin中,
class
关键字声明的是一个类,object
关键字声明的是一个对象 object
对象中不允许有构造函数- 常量用
const val
定义,变量用var
定义 - 通过
类名+属性名
/类名+方法名
访问
object Myobject{
const val name = "tom"
var age = 20
fun method(){
println("object对象")
}
}
fun main() {
Myobject.age = 30
println(Myobject.name)
println(Myobject.age)
Myobject.method()
}
伴生对象 companion object:
由来:在kotlin中没有static关键字,无法通过
static
关键字在类中定义静态属性和方法,所以产生了伴生对象companion object
- 伴生对象的调用和java中的
static
一样,类名.属性名
/类名.方法名
来调用
为什么叫伴生对象?看起来伴生对象和java中的static一样,但是在jvm中伴生对象是类的真实对象的实例成员,可以通过反编译求证。
在JVM中,示例的Mytest
类中,伴生对象有一个实例对象(如果实例对象companion object没有定义名字,则默认的名字是Companion),所以调用时,实际上调用的是MyTest
类中,实例对象Companion的属性和方法。
class MyTest{
companion object{
const val name = "tom"
var age = 20
fun method(){
println("伴生对象的method方法")
}
}
}
fun main() {
println(MyTest.name)
MyTest.age = 30
println(MyTest.age)
MyTest.Companion.method()
}
P10属性赋值、获取是怎么实现的 ?、扩展函数、延迟初始化
属性赋值、获取是怎么实现的 ?
Person3(name: String, address: String)
括号中的叫参数,不是属性。属性是类的成员,参数是用来给属性或者方法传递值的val
只有get属性,var
有get和set属性,所以在继承时var可以继承val,val不能继承varfield
关键字,只能用在这个地方,field
是真是储存属性值的地方
class Person3(name: String, address: String) {
val name: String = name
get() {
println(" name 的get方法")
return field
}
var address: String = address
get() {
println("address的get方法")
return field
}
set(value) {
println("address的set方法")
field = value
}
}
延迟初始化:
lateinit
只能用在类中声明的属性中,不能用在构造函数中- 属性不能自定义
set
、get
- 属性不能为空(不能加?)比如:
lateinit var age: String?
,属性的类型不能是原生类型,比如:lateinit var age: Int
class TheClass{
lateinit var age: String
}
扩展函数:
看上去好像是将扩展函数增加到了类中,JVM中类里面并没有此方法
class ExtensionTest{
fun add(a: Int,b: Int) = a + b
fun sub(a: Int,b: Int) = a - b
}
fun ExtensionTest.multiply(a: Int,b: Int): Int{
return a * b
}
fun main() {
val extensionTest = ExtensionTest()
extensionTest.multiply(2,2)
}
- 扩展函数是静态解析的
- 扩展函数不支持多态,调用只取决于对象的声明类型
下面示例中,虽然BB
继承AA
,但是调用扩展函数时,只取决于fun myPrint(a: AA)
中的参数的声明类型,声明类型为AA
则调用AA
的扩展函数
open class AA
class BB : AA()
fun AA.aa()= println("AA的扩展函数")
fun BB.aa()= println("BB的扩展函数")
fun myPrint(a: AA){
a.aa()
}
fun main() {
myPrint(AA())
myPrint(BB())
}
P12 扩展函数的作用域
- A类的扩展函数写在B类中,A类的实例称为扩展接收者,B类的实例称为分发接收者
- 扩展函数调用的方法在两个类中都有定义,扩展接收者的优先级高于分发接收者
- 虽然代码写在B类中,但是B类中的方法和B类的实例都无法直接调用A类的扩展函数
class AClass{//实例称为扩展接收者
fun method1() {
println("AClass-method1")
}
}
class BClass{//实例称为分发接收者
fun method1() {
println("BClass-method1")
}
fun method3() {
println("BClass-method3")
}
fun AClass.method4(){
method1()//A、B类中都有method1方法,扩展接收者优先级高
this@BClass.method1()//调用分发接收者的method1方法,this@BClass
method3()//即可以直接调用A类中的方法,也可以直接调用B类中的方法
}
fun method5(){
val aClass = AClass()
aClass.method4()//method4虽然定义在B类中,但是在类中也不能直接调用,需要通过A类的实例来调用
}
}
fun main() {
val bClass = BClass()//B类的实例,也无法直接调用A类的扩展函数
bClass.method5()
}
P13 数据类 data class
、解构
- 数据类要满足以下3个条件
- 主构造方法中至少有一个参数
- 主构造方法中的参数必须有val或var属性
- 数据类不能是
abstract
、open
、sealed
、inner
- 数据类的特点:
- 数据类会自动生成
equals()
、hashCode()
、toString()
方法 - 在JVM中,会自动生成主构造方法中属性对应的componentN方法,N是属性在主构造方法中的位置,例如:示例中
name
属性的位置是1,则生成component1()
方法 component
方法的作用:获得属性的值,此方法是用来解构声明的。见示例- JVM中有如下的方法:
fun component1() = name
data class Person4(val name: String, var age: Int, var address: String)
fun main() {
val person = Person4("tom", 20, "beijing")
//component方法的作用
var(name, age, address) = person
println("$name, $age, $address")
}
//结果:tom, 20, beijing
如果给每个属性都赋初值,那么这个类就有了无参构造方法
data class Person5(val name: String = "", var age: Int = 0, var address: String = "")
fun main() {
val person1 = Person5()
println("$person1")
}
//结果:Person5(name=, age=0, address=)
- 解构:
- 根据数据类在JVM中自动创建
componentN
方法的特性实现解构 - 方法前要加
operator
关键字 operator
还用于属性委托中,见P23operator
解释:将一个函数标记为重载一个操作符或者实现一个约定
class Person7(val name: String,val age: Int){
operator fun component1() = name
operator fun component2() = age
}
fun main() {
val (name ,age) = Person7("tom", 20)
println("$name,$age")
}
P14 密封类 sealed class
泛型 generice
密封类 sealed class
- 表现出一种受限的层次结构,像父类和子类的结构
- 密封类及其子类必须在同一个文件中 密封类是
抽象类
,构造方法是私有的
- 个人感觉密封类和when配合才有意义,因为
when()
的条件中会强制要求把所有的可能都包括进去,所以密封类无论有多少子类,在when下都必须处理
sealed class Calculator
class Add: Calculator()
class Sub: Calculator()
class Mul: Calculator()
fun calculator(a: Int, b: Int, cal: Calculator)=when(cal){
is Add -> a + b
is Sub -> a - b
is Mul -> a * b
}
fun main() {
println(calculator(1, 2, Add()))
println(calculator(1, 2, Sub()))
println(calculator(1, 2, Mul()))
}
泛型 generic
,表示变量类型的参数化(使用时才确定参数的类型)
变量类型的参数化理解:
在示例中,T
、M
就是变量类型的形参,实例化类时,传入的Int, String
就是实参,这就是将变量的类型参数化,类型都是通过参数传参的方式来传递,不是原本就定义好的
//T、M就是变量类型的形参
class Generic<T, M>(a: T,b: M)
fun main() {
//传入的`Int, String`就是实参
val generic = Generic<Int, String>(1,"abc")
}
通常使用的泛型所表示的含义
<T> type 类型
<E> element 元素
<K> key 键
<V> value 值
泛型类,泛型接口,泛型方法
//泛型类
class Generic<T>{
fun gen(a: T){//T是类传递过来的
println("这是一个泛型类,但不是一个泛型方法")
}
//**泛型方法**:泛型的形参是在方法名前面自己定义的,而不是由类传递过来的
fun <E> gen1(b: E){//E是方法自己定义的
println("这是一个泛型方法")
}
}
//泛型接口
interface Generic1<T>{
fun gen(t: T)
}
P15 -P18 协变和逆变:
参考第一行代码 P419
为什么会有协变和逆变,他们解决了什么问题?
原因:
在下面的示例中,
Son
是Parent
的子类,在MyData
方法中参数只能传MyClass<Parent>()
实例,那如何才能传入MyClass<Son>()
作为参数呢?
open class Parent
class Son : Parent()
class MyClass<T>
fun MyData(data: MyClass<Parent>){}
fun main() {
MyData(MyClass<Parent>())
MyData(MyClass<Son>())//报错:Kotlin: Type mismatch: inferred type is MyClass<Son> but MyClass<Parent> was expected
}
协变:
假设
A
是B
的子类,现在MyData
方法参数只接收MyClass(A)
,如何能将MyClass(B)
也作为参数传入呢?那么我们就需要MyClass<T>
这个类是协变
//协变,只读(输出get())
class MyClass<out T>(val data: T?){
fun method(): T?{//T只能用在返回值类型
return data
}
}
//简单的说data参数只能是MyClass<Parent>类或者其子类,例如:MyClass<Son>
fun MyData(data: MyClass<Parent>){
TODO("")
}
fun main() {
MyData(MyClass<Parent>())
MyData(MyClass<Son>())
}
逆变:
假设
A
是B
的子类,现在MyData
方法参数只接收MyClass(B)
,如何能将MyClass(A)
也作为参数传入呢?,那么我们就需要MyClass<T>
这个类是逆变
//逆变
class MyClass<in T>{
fun method(t : T){//T只能用在参数里面
.....
}
}
//简单的说data参数只能是MyClass<Son>类或者其父类,例如:MyClass<Parent>
fun MyData(data: MyClass<Son>){}
fun main() {
MyData(MyClass<Parent>())
MyData(MyClass<Son>())
}
P19 嵌套类(默认)、内部类(inner)
嵌套类:
- 示例中
InClass
是嵌套类,相当于JAVA中的static
修饰的静态内部类。 - 访问时通过外部类的
类名.
方式直接访问。 - 嵌套类无法直接访问外部类的属性和方法,需要通过外部类的实例来访问。
class OutClass{
private var str = "out String"
fun outMethod(){
println("out method")
//外部类可访问嵌套类的方法和属性
InClass().method()
}
class InClass{
fun method(){
println("in method")
}
}
}
fun main(){
OutClass.InClass().method()
}
内部类:
- 内部类有
inner
关键字修饰 - 访问内部类的方法和属性,需通过外部类的实例来访问。
- 内部类访问外部类的属性和方法,通过
this@外部类类名.
的方式
class OuterClass{
private var str = "outer class string"
fun outMetod(){
println("out method")
InnerClass().inMethod()
}
inner class InnerClass{
fun inMethod(){
println("in method")
println(this@OuterClass.str)
}
}
}
fun main(){
OuterClass().InnerClass().inMethod()
}
P20 对象表达式,可自定义方法,可继承类,可实现接口
- 对象表达式:
- 代替JAVA的匿名类
- 可同时实现一个或多个接口和抽象类的方法
val myObject = object {
//自定义方法
}
val myObject = object :抽象类名,接口名 {
//实现所有的抽象方法
}
myObject.方法名
myObject.属性名
//对象表达式
interface MyInterface{
fun myPrint(number:Int):Int
}
abstract class MyAbstractClass(val age: Int){
abstract var key : Int
abstract fun myMethod(age: Int)
fun myMethod1(){
println("抽象类中的非抽象方法执行,构造函数中获得的属性值 $age")
}
}
fun main() {
//实现单个抽象方法
val myObject1 = object: MyInterface{
override fun myPrint(number: Int): Int {
println("对象表达式实现接口")
return number
}
}
println(myObject1.myPrint(0))
println("--------------------")
//实现单个抽象类
val myObject2 = object : MyAbstractClass(20){
override var key: Int = 0
get() = field
set(value) {
println("抽象类中抽象属性赋值操作")
field = value}
override fun myMethod(age: Int) {
println("对象表达式实现的抽象类")
}
}
myObject2.age //访问主构造函数的属性
println("抽象类中,抽象属性初始化的值 $myObject2.key")//属性的get方法
myObject2.key = 1 //属性的set方法
println("抽象类中,抽象属性set后的值 $myObject2.key")
myObject2.myMethod(0) //实现的抽象方法
myObject2.myMethod1() //抽象类中的非抽象方法
println("------------------------")
//同时实现抽象方法和抽象类
val myObject3 = object : MyAbstractClass(20),MyInterface{
override fun myPrint(number: Int): Int {
TODO("Not yet implemented")
}
override var key: Int
get() = TODO("Not yet implemented")
set(value) {}
override fun myMethod(age: Int) {
TODO("Not yet implemented")
}
}
}
- 对象表达式实现JAVA的接口:
只有一个方法的接口称为函数式接口,才可以使用lambda表达式
//java的接口:
public interface P20 {
void myPrint();
}
//kotlin中对象表达式实现JAVA的接口
//示例中应用了lambda表达式的简化,仅限于实现JAVA的单接口,并且接口中只有一个方法
val myObject4 = P20 { println("对象表达式实现JAVA的接口") }
myObject4.myPrint()
P21对象表达式、对象声明和对象表达式的区别
- 对象表达式没有继承类或者接口,
myObject2
的类型是Any
- 当没有继承的对象表达式作为类的成员时,必须有private修饰,否则无法正确识别其类型
//对象表达式作为类的成员时,必须有private修饰,否则无法正确识别其类型
class MyClass{
private val myObject2 = object {
fun myPrint(){
println("没有实现抽象类和接口,自己定义的方法")
}
}
fun myTest(){
myObject2.myPrint()
}
}
fun main() {
MyClass().myTest()
}
- 对象表达式可以是变量也可以是方法(对象表达式无敌了)
interface MyInterface1{
fun myPrint(number:Int):String
}
class MyClass{
fun myMethod() = object : MyInterface1{
override fun myPrint(number: Int): String {
return "对象表达式实现的匿名对象"
}
}
}
fun main() {
println(MyClass().myMethod().myPrint(1))
}
对象表达式和对象声明之间的区别:
对象表达式是立刻初始化的。对象声明是延迟初始化,是在首次访问的时候初始化的
P22枚举、委托类、类委托
不懂枚举是干嘛用的
enum class Seaon{
SPRING,SUMMER,AUTUMN,WINTER
}
enum class Seaon1{
SPRING{
override fun getSeaon(): Seaon1 = SPRING
},
SUMMER{
override fun getSeaon(): Seaon1 = SUMMER
},
AUTUMN{
override fun getSeaon(): Seaon1 = AUTUMN
},
WINTER{
override fun getSeaon(): Seaon1 = WINTER
},
;
abstract fun getSeaon():Seaon1
}
fun main() {
}
- 类委托:
重点在委托类是怎么写的
- 与受委托类实现相同的接口
- 参数类型也是该接口
by
后面是该接口类型的参数
interface MyInterface2{
fun myPrint()
}
class MyImpl : MyInterface2 {
override fun myPrint() {
println(this.javaClass)
}
}
/* 1. 与受委托类实现相同的接口
* 2. 参数类型也是该接口
3. `by`后面是该接口类型的参数*/
class MyClass1 (myInterface2: MyInterface2) : MyInterface2 by myInterface2
fun main() {
val myImpl = MyImpl()
MyClass1(myImpl).myPrint()
}
P23属性委托、延迟属性lazy
、非空属性
- 属性委托:
getValue
和setValue
函数前加关键字operator
- 函数加了两个参数:第一个参数是属性所有者的类型或所有者的父类型,第二个参数表示属性本身,这个属性类型为
KProperty
。 - 可以用过
property.name
访问属性名称 val
定义的属性在委托中只有getValue
方法getValue
返回的可以是属性的类型及其子类型val
属性默认实现ReadOnlyProperty
接口,var
属性默认实现ReadWriteProperty
接口
自己的理解:var str by MyDelegate()
,是把属性委托给了个一个类的对象,所以这个对象就是属于这个属性的
class MyClass2{
//属性委托给了个一个类的对象
var str by MyDelegate()
}
class MyDelegate {
operator fun getValue(thisRef: MyClass2,property: KProperty<*>) = "$thisRef,属性本身:${property.name}"
operator fun setValue(thisRef: Any?,property: KProperty<*>,value: String) = println("$thisRef,属性本身:${property.name}")
}
fun main() {
var myClass = MyClass2()
myClass.str = "hello world"
println(myClass.str)
}
- 延迟属性:
- 第一次调用属性时会执行
lazy
的lambda表达式,并给属性赋值 - 以后再调用属性,则只会返回属性的值,不再执行lambda表达式
- 必须是
val
定义的属性(如果定义为var暂时没有找到解决办法)
val myLazy : Int by lazy {
println("这条语句只会执行一次")
100
}
fun main() {
println(myLazy)
println(myLazy)
}
- 非空属性:(感觉和
lateinit var
一样)
- 非空属性在后期必须要赋值,所以类型必须是
var
定义
// 非空属性
var address:String by Delegates.notNull<String>()
lateinit var address : String
P24可观测属性、map属性委托
- 可观测属性
Delegates.observable
作用:发现属性值发生改变后可执行Delegates.vetoable
作用:在属性值放生改变之前,如果lambda表达式返回true
则赋新值;如果返回false
则保留原来的值- 这连个方法由两个参数,第一个参数是属性的初值,第二个参数是lambda表达式
- 必须赋初值
class Person4(age: Int){
var manAge: Int by Delegates.observable(age){
property, oldValue, newValue ->
println("属性:${property.name}由:$oldValue 更改为:$newValue,")
}
var womenAge: Int by Delegates.vetoable(age){
property, oldValue, newValue -> when {
oldValue <= newValue -> {
println("属性:${property.name}由:$oldValue 更改为:$newValue,")
true
}
else -> {
println("属性:${property.name},年龄不能减少")
false
}
}
}
}
fun main() {
val person4 = Person4(30)
person4.manAge = 20
println("new manAge = ${person4.manAge}")
println("------------------------------------")
person4.womenAge = 40
println("1.new womenAge = ${person4.womenAge}")
person4.womenAge = 30
println("2.new womenAge = ${person4.womenAge}")
}
- map属性委托:
Map
的只读属性和读写属性定义:Map<String,Any>
MutableMap<String,Any>
- 在示例的
Student2
的类中,age
定义为val
,map
类型为MutableMap
,不能用student2.age = 30
修改属性值,但是可以用map2["age"] = 30
的方法来修改map
的value
值,也就间接的修改了age
的属性值,所以在Kotlin中一定特别注意只读和可读写两种属性的应用。 - 在源码中可以看到
Map
类型只有get
属性,MutableMap
有set
和get
属性。
//map属性委托
class Student1(map: Map<String,Any>){
val name: String by map
val age: Int by map
val birthday: Date by map
}
class Student2(map: MutableMap<String,Any>){
var name: String by map
val age: Int by map
var birthday: Date by map
}
fun main() {
val map1 = mapOf(
"name" to "tom",
"age" to 20,
"birthday" to Date()
)
val student1 = Student1(map1)
println("name is ${student1.name},age is ${student1.age},birthday is ${student1.birthday}")
println("--------------------------")
val map2 = mutableMapOf(
"name" to "tom",
"age" to 20,
"birthday" to Date()
)
val student2 = Student2(map2)
println("name is ${student2.name},age is ${student2.age},birthday is ${student2.birthday}")
student2.name = "lucy"
//student2.age = 30
map2["age"] = 30
println("name is ${student2.name},age is ${student2.age},birthday is ${student2.birthday}")
}
P26 函数及其参数,当lambda表达式作为参数时
函数:存在于类体之外
方法:存在于类体内
参数式函数类型:
- lambda可以作为实参传入函数
- 如果参数是一个方法,实参用
::方法名
- 如果lambda表达式是最后一个参数,可以将lambda表达式放在括号外面
compute:(x: Int, y: Int) -> Int)
传入的实参要和参数compute
中定义的参数类型、个数、返回值类型一样
fun sub(a: Int, b: Int): Int{
return a - b
}
fun sum(a: Int, b: Int): Int{
return a + b
}
fun test(a: Int, b: Int,compute:(x: Int, y: Int) -> Int): Int{
return compute(a, b)
}
fun main() {
//参数是一个方法,传参用::方法名
var n1 = test(1, 2, ::sub)
var n2 = test(1, 2, ::sum)
//直接传入lambda表达式
val n3 = test(1,2,{x,y-> x * y})
//lambda表达式是最后一个参数时,可以将lambda表达式放在()外面
val n4 = test(1,2){x,y-> x / y}
}
P27参数为可变参数vararg
- 参数为可变参数:
- 形参加
vararg
修饰 - 实参可以为
("a","b","c")
形式 - 实参可以为数组,数组前需加分散运算符
*
fun test(vararg string: String){
string.forEach{println(it)}
}
fun main() {
test("a","b","c")
test(*arrayOf("d","e","f"))
val array = arrayOf<String>("g","h","i")
test(*array)
}
- 可变参数传参示例
fun <T>covertList(vararg element: T): ArrayList<T>{
val result = ArrayList<T>()
element.forEach { result.add(it) }
return result
}
fun main() {
println(covertList("a","b","c",1))
val arrayele = arrayOf("d","e","f",2)
println(covertList("zhangsan","lisi",*arrayele))
}
- 单表达式函数可以省略返回类型
- 函数体只有一条执行语句时可以直接写在等号后面,并且不必声明返回值类型
fun test(a: Int,b: Int) = a + b
- 显式返回类型不可以省略返回类型
- 函数体不止一条执行语句时,执行语句必须写在{}里面,并且必须声明返回值类型
P28中缀符号infix、内联函数inline
- 中缀符号(infix notation):
- 类的成员方法,或者扩展函数使用
- 方法前加关键字
infix
- 只有一个参数
class InfixTest(val a: Int){
infix fun add(b: Int) = a + b
}
fun main() {
val infixTest = InfixTest(2)
//以下两种方式是等价的
println(infixTest.add(5))
println(infixTest add 5)
}
- 内联函数(inline function)
- 在函数前加
inline
关键字 - 相当于把被调用函数的代码直接写到调用函数的地方,不存在调用的关系 ,具体体现在字节码中。
inline fun myCalculate(a: Int,b: Int) = a + b
fun main() {
myCalculate(1, 2)
}
- 高阶函数(high-order function)与 lambda
- lambda表达式:
{参数1:参数类型,参数2:参数类型 -> 执行体}
- 参数的类型可以在lambda表达式中声明,也可以在变量定义的时候声明
- 变量的类型为函数类型
- lambda表达式中有多条执行语句时,用
;
间隔,返回最后一条语句的值
val constant = {a:Int, b:Int -> a+b}
val constant1:(Int,Int) -> Int = {a, b-> a + b }
val constant2:(Int,Int) -> Int = {a, b-> a + b;println(b);a }
P29函数式参数的应用,自定义fifter
- 定义
String
的扩展函数fifiter
,对字符串中的每个字符进行操作
fun String.fifter(predicate: (Char) -> Boolean): String{
val str:StringBuilder = StringBuilder()
val toCharArray = this.toCharArray()
toCharArray.forEach { if (predicate(it)) str.append(it) }
return str.toString()
}
fun main() {
val str = "abc1".fifter {
//字符串中每个字符是否式字母
it.isLetter()
}
print(str)
}
P30 lambda表达式就是用来代替匿名函数
- lambda表达式就是用来代替匿名函数
- lambda表达式非常的简洁
fun main() {
// 虽然可以在main中定义函数,由于函数没有名字,无法调用
fun (x: String) = x.length > 5
val str = arrayOf("hello","world")
//匿名函数的应用
str.filter ( fun (x: String) = x.length > 4 ).forEach ( fun (x: String){ println(x) })
// lambda表达式代替匿名函数
str.filter { it.length > 4 }.forEach { println( it ) }
}
P31带接收者的函数字面值-匿名函数
- 带接收者的函数字面值:(感觉是另一个版本的扩展函数)
- 自己的理解:和扩展函数类似的感觉,扩展函数是给某一个类增加了一个方法。带接收者的函数字面值是给一个类型增加了一个方法
- 调用的时候也和扩展函数相似
fun main() {
//带接收者的函数字面值,用lambda表达式实现
val test1 : Int.(other: Int) -> Int = { other -> this - other }
//用匿名函数实现
val test2 = fun Int.(other:Int) : Int = this + other
//匿名函数多条执行语句
val test3 = fun Int.(other:Int) : Int { val temp = this - other; return temp * 2}
//调用
println(2.test2(3))//5
val test4 : String.() -> Int = { this.length}
println("abd".test4())
}
P33 list的一部分函数、Range、异常
- list的一部分函数
fun main() {
val str = listOf("a","b","c")
val first = str.first()//列表第一个元素
val last = str.last()//列表最后一个元素
val filter = str.filter { it > "a" }//过滤器,返回符合过滤器条件的列表
val requireNoNulls = str.requireNoNulls()//列表中有没有null元素,如果有则返回一个异常,没有则返回字符串本身
val none = str.none()//列表是否为空,返回Boolean
val none1 = str.none { it > "a" } //列表中符合条件的元素集合是否为空,返回Boolean
val firstOrNull = str.firstOrNull()//列表中第一个元素是否为空,返回值String?
}
- Range:
for (a in 1..4){}//a=1;a<=4;a++
for (a in 4..1){}//不会执行的原因:a=4;a<=1,判断表达式不成立,所以不会执行
- 异常:
try()
catch()
会返回最后一行代码的值
//kotlin中的try()catch()会返回最后一行代码的值
fun main() {
val i = "a"
val s = try {
parseInt(i)
}catch (e : NumberFormatException){
null
}
println(s)
}
P34 throw 表达式、注解
- throw 表达式
throw
表达式的类型:Nothing
Elvis
表达式:?:
,如果?:
前的值不为空直接赋值,为空则返回表达式后面的值
fun method(string: String): Nothing{
throw IllegalAccessError(string)
}
fun main{
val s = null
val s1 = s ?: throw IllegalAccessError("值不能为空")
val s2 = s ?: method("值不能为空")
}
- 注解
- 定义注解的关键字
annotation class
@Target
注解的目标,例如:类,方法,属性,表达式,get方法@Retention
是否在运行时、二进制或源代码中保留@MustBeDocumented
生成文档
//Target注解中决定自定义的注解可以在什么位置
@Target(AnnotationTarget.CLASS,AnnotationTarget.FUNCTION,AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.EXPRESSION,AnnotationTarget.PROPERTY_GETTER)
//策略
@Retention(AnnotationRetention.SOURCE)
//必须出现在文档
@MustBeDocumented
//定义注解的关键字annotation class
annotation class MyAnnotation
@MyAnnotation
class MyClass{
var b = 10
@MyAnnotation get() = field
@MyAnnotation
fun method(@MyAnnotation a: Int):Int{
var a= @MyAnnotation 10
return 10
}
}
P35 Kotlin中引用JAVA代码
- Kotlin中引用JAVA
- 在Kotlin中,将来自于JAVA的声明类型称为平台类型
- Kotlin不会在编译器检查
- 在运行期,只要是来自平台类型值的传递,首先生成一个断言,判断是否为
null
,将及时抛出异常,不会将空值的错误蔓延
fun main() {
val person = Person()
person.name = "tom"
person.age = 20
person.marryied = false
// Kotlin引用JAVA时,基本没有什么区别
person.name = null
val name : String = person.name
//在明确的知道person.name为空时也不报错
/*由于Kotlin和JAVA对null值的要求程度不同,所以在Kotlin中,将来自于JAVA的声明类型称为平台类型,
kotlin不会在编译器检查,只要是来自平台类型值的传递,在运行期首先生成一个断言,判断是否为null,
将及时抛出异常,不会将空值的错误蔓延*/
}
P36 Kotlin调用JAVA
- 由于Kotlin和JAVA对null值的严格程度不同,所以在Kotlin中,将来自于JAVA的声明类型称为平台类型
- 来自平台类型的值在Kotlin中传递时,Kotlin编译器不会检查,但是在运行时会生成一个断言,判断是否为null,及时抛出异常,不会将空值的错误蔓延
fun main() {
val person = Person()
person.name = "tom"
person.age = 20
person.marryied = false
// Kotlin引用JAVA时,基本没有什么区别
person.name = null
val name : String = person.name
//在明确的知道person.name为空时也不报错
/*由于Kotlin和JAVA对null值的要求程度不同,所以在Kotlin中,将来自于JAVA的声明类型称为平台类型,
kotlin不会在编译器检查,只要时来自平台类型值的传递,在运行期首先生成一个断言,判断是否为null,将及时抛出异常,不会将空值的错误蔓延*/
}
P38 JAVA调用Kotlin(1)
- JAVA调用Kotlin中的方法和属性:在Kotlin字节码中,创建了一个Kotlin文件名的类,方法和属性都是该类的静态方法,在JAVA中可直接通过
文件名.方法名
和文件名.属性名
直接访问 - 既然在字节码中一个Kotlin文件名的类,JAVA中可以实例化(
new
)吗?不可以,应为在字节码中没有该类的构造方法。
P39 JAVA调用Kotlin中伴生对象的方法和属性(2)
- 将两个Kotlin文件在字节码中合并成一个文件
需要在两个Kotlin文件中加入以下代码:
@file: JvmName("合并后的名字")
@file: JvmMultifileClass
- JAVA中调用Kotlin中伴生对象的属性:
- java调用伴生对象的属性:
类名.Companion.setName("lucy");
类名.Companion.getName();
- 伴生对象的属性加上注解
@JvmField
,属性称为类的public
属性,调用时:类名.属性名
Kotlin代码:
class People{
companion object{
var name = "tom"
@JvmField
var age = 20
}
}
JAVA代码:
public static void main(String[] args) {
People.Companion.setName("lucy");
People.age = 10;
System.out.println(People.age);
}
- JAVA中调用Kotlin中伴生对象的方法:
- java调用伴生对象的方法:
类名.Companion.test1();
- 伴生对象的方法加上注解
@JvmStatic
,在JVM中此方法前会加static
关键字,调用时:类名.方法名
class People{
companion object{
fun test1(){}
@JvmStatic
fun test2(){}
}
}
P40类型擦除、@JvmName()
- 类型擦除:
- 示例中定义的两个
myFilter()
扩展方法,虽然扩展的类型不同,但是在JVM中会忽略类型(类型擦除),所以在JVM中认为这两个方法是重复的。 - 需要通过注解
@JvmName("方法名")
来修改方法名,修改方法名目的只为了在JVM中两个方法名字不同。 - Kotlin中调用这两个方法,还是用原来的方法名,Kotlin会通过类型自动判断应该调用哪一个方法。
fun List<String>.myFilter(): List<String>{
return listOf("hello","world")
}
@JvmName("myFilter2")
fun List<Int>.myFilter(): List<Int>{
return listOf(1,2)
}
fun main() {
val list = listOf(1,2)
list.myFilter()
}
- @JvmName(“方法名”):
- 示例中定义的属性a,在JVM中会生成getA()方法,和方法重名。
- 需要通过注解
@JvmName("方法名")
来修改方法名,修改方法名目的只为了在JVM中两个方法名字不同。 - Kotlin中调用时,还和原来一样。
class MyTest{
val a: Int
get() = 20
@JvmName("getVelue")
fun getA() = 30
}
P44反射:函数引用
- 函数引用:
- 简单来说函数引用就是将一个函数作为参数传递。
- 当函数做为参数传入时,函数引用
::
,见示例第一部分 - 函数也可以赋值给以一个变量,传参时传入变量名,见示例中第二部分
- 类的方法赋值给变量时,变量的类型需要特别注意,不能使用自动推导的变量,见示例中的第三部分
//函数引用
fun multiply(x: Int): Int{
return x * 3
}
fun multiply(string: String): Int{
return string.length
}
fun test(list: Array<String>,m: (String) -> Int){
for (a in list){
println(m(a))
}
}
fun main() {
//示例第一部分
val array = arrayOf(1,2,3,4)
val array1 = arrayOf("hello","world","hello world")
array.map (::multiply).forEach { println( it ) }
test (array1,::multiply)
println("-------------------------")
//示例第二部分
val myReference: (Int) -> Int = ::multiply
val myReference1: (String) -> Int = ::multiply
array.map(myReference).forEach { println( it ) }
test(array1,myReference1)
println("------------------")
//示例第三部分
val myReference2: String.(Int)->Char = String :: get
val string = "welcome"
println(string.myReference2(0))
println("------------------")
//示例第四部分
val myReference3: Test3.()->Int = Test3::multiply
println(Test3(string).myReference3())
}
class Test3(val string: String){
fun multiply(): Int{
return string.length
}
}