Kotlin专题「二十三」:引用详解 :: (类引用、属性引用、函数引用、绑定引用)

前言:没有谁的幸运,凭空而来,只有当你足够努力,你才会足够幸运。这世界不会辜负每一份努力和坚持,时光不会怠慢执着而勇敢的每一个人!

一、概述

  反射是一组语言和库特性,允许程序在运行时访问程序结构的一类特性。程序结构包括类、接口、函数、属性等语法特性。Kotlin 使函数和属性成为该语言的一级公民,而内省它们(即在运行时学习属性或函数的名称或类型)与简单地使用函数或响应式风格紧密相连。

最基本的反射特性是获得对 Kotlin 运行时的引用。除了访问程序结构之外,对函数、属性和构造函数的引用还可以作为函数类型的实例调用。今天来给大家介绍 Kotlin 中的引用,双冒号 :: 表示引用,有属性引用、函数引用、类引用、构造函数引用、扩展函数引用、绑定引用等等。

1.1 添加 JVM 依赖

在 JVM 平台上,使用反射特性所需要的运行组件在 Kotlin 编译器发行版中作为单独的工作 Kotlin -reflect.jar 分发。这样做是为了减少不使用反射特性的应用程序所需要的运行时库大小。也就是说你要添加这个 jar 包才可以使用反射特性。

  • 1、在 Gradle 中添加:
dependencies {
    
    
  implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.21")
}
  • 2、或者添加 Maven 仓库:
<dependencies>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-reflect</artifactId>
    </dependency>
</dependencies>

另外,如果你不使用 Gradle 或 Maven,请确保在项目的类路径中有 kotlin-reflect.jar 包。在其他支持的请况下(IntelliJ IDEA项目,使用命令行编译器或Ant),它是默认添加的。在命令行编译器和 Ant 中,可以使用 -no-reflect 编译器选项从类路径种排除 kotlin-reflect.jar

二、 成员引用

2.1 为什么使用引用?

Lambda 表达式可以直接把一个代码块作为一个参数传递给函数,但是如果要当做参数传递的代码已经被定义成了函数,此时还需要重复写一个代码块传递过去吗?肯定不是,Kotlin拒绝重复啰嗦的代码,这时候就需要 把函数转换成一个值,这种方式称为 成员引用

    val persons = arrayListOf(Person("Java", 20), Person("Android", 5))
    println(persons.maxBy({
    
     p: Person -> p.age }))

{ p: Person -> p.age } 表达式可以用成员引用的方式 Person::age 替换。成员引用和调用函数的 lambda 具有一样的类型,所以可以相互转换。如下:

    //println(persons.maxBy({ p: Person -> p.age }))
    println(persons.maxBy(Person::age))//成员的引用类型和maxBy()传入Lambda表达式的一致

2.2 成员引用的基本语法

上面这种用法称为成员引用,它提供简明语法,来创建一个单个方法或者单个属性的函数值,使用 :: 运算符来转换。成员引用由类名,双冒号,成员三个元素组成。成员是函数名表示引用函数,成员是属性表示引用属性。
在这里插入图片描述

所有可调用引用的通用超类型是 KCallable<out R> ,其中 R 是返回值类型,可能是属性的属性类型,也可能是构造函数的构造类型。

2.3 函数引用

成员是函数名表示函数引用。这种情况省略了类名称,直接以 :: 开头。当我们这样声明一个命名函数时:

	fun isOld(x: Int) = x > 18

可以将其作为函数类型值使用,将它传递给另一个函数,我们可以使用函数引用:

	val numbers = arrayListOf(10, 20, 30)
	println(numbers.filter(::isOld))//打印:[20, 30]

函数引用 ::isOld 被当做实参传递给 filter() 函数,它会调用相应的函数。这里 ::isOld 是一个函数类型 (Int) -> Boolean 的值。

函数引用属于 KFunction<out R> 子类型之一,这取决于函数计数,例如:KFunction3<T1, T2, T3, R>

下面列出几种函数引用使用的场景:

(1)重载函数引用

函数引用可以与重载函数一起使用,当参数类型从上下文中推断出来时。例如:

    fun isOld(x: Int) = x > 18
	//函数重载
    fun isOld(str: String) = str == "Java" || str == "Android" || str == "Kotlin"

	val numbers = arrayListOf(10, 20, 30)
	println(numbers.filter(::isOld))//调用 isOld(x: Int),打印:[20, 30]  

    val strs = arrayListOf("Java", "HelloWord", "Tonight")
    println(strs.filter(::isOld))//调用 isOld(str: String),打印:[Java]

(2)函数引用变量

如果 lambda 要委托给一个接收多个参数的函数,提供函数引用代替它将会非常方便。另外,也可以通过将函数引用存储在显式指定类型的变量中来提供必要的上下文:

	//有多个参数的 lambda 
	val action = {
    
     person: Person, message: String -> sendEmail(person, message) }
	//使用成员引用代替, 将 ::sendEmail 存储在显式指定类型的变量 nextAction 中,并显式指定类型
	val nextAction = ::sendEmail

	//调用
	nextAction(Person(), "msg")

(3)顶层函数引用

还可以引用顶层函数,这种情况省略了类名称,直接以 :: 开头。函数引用 ::salute 被当作实参传递给库函数 run(),它会调用相应的函数:

fun saulte() = println("fun saulte") //顶层函数

fun main(args: Array<String>) {
    
    
    run({
    
     saulte() })
    //使用成员引用简化后
    run(::saulte)//打印:fun saulte
}

(4)构造函数引用

构造函数的引用,当需要函数类型的对象与构造函数相同的形参并返回适当类型的对象时,可以使用它们。通过使用 :: 操作符并添加类名来引用构造函数: ::类名。例如以下函数,它需要一个不带形参的函数形参,并返回类型为 Student

    //没有参数的构造函数
    class Student

    fun constRef(factory: () -> Student) {
    
    
        val student: Student = factory()
    }

使用 Student 类的没有参数构造函数 ::Student,我们可以这样简单地调用它:

	constRef(::Student)

对构造函数的可调用引用类型为 KFunction<out R> 子类型之一,这取决于形参计数。

(5)扩展函数引用

函数引用还可以使用同样的方式引用扩展函数:

// 这是Person的一个扩展函数,判断是否成年
fun Person.isChild() = age > 18

fun main(args: Array<String>) {
    
    
    val isChild = Person::isChild
    println("isChild == " + isChild(Person("Java")))//打印 true
}

注意:即使你用扩展函数的引用来初始化变量,推断出来的函数类型也不会有接收类(它会有一个接收器对象的附加参数)。要使用带 receiver 的函数类型,请明确指定类型:

	val isChildResult: Person.() -> Boolean = Person::isChild

(6)函数组合

看看以下的组合函数:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    
    
    return {
    
     x -> f(g(x)) }
}

它返回传递给它的两个函数的组合:compose(f, g) = f(g(*))。现在,你可以把它应用到可调用引用:

	fun Person.isChild() = age > 18
	fun length(s: String) = s.length
	
	val isOldLength = compose(::isOld, ::length)
	val strings = arrayListOf("View", "TextView", "AppCompatAutoCompleteTextView")
	println(strings.filter(isOldLength))//打印:[AppCompatAutoCompleteTextView]

2.4 属性引用

成员是属性名则表示属性引用,下面列举集中属性引用的使用情况:

(1)访问类成员属性

属性引用访问类成员的属性,也是使用 类名+双冒号+属性 的方式,对其进行限定,将属性引用存储在显式指定类型的变量中。

    class Person(val name: String)

    fun main() {
    
    
        val prop = Person::name
        println(prop.get(Person("Android")))//打印:Android
    }

(2)属性作为类对象

要在 Kotlin 中将属性作为一类对象访问,还可以使用 :: 引用操作符:

    val age = 18

    fun main() {
    
    
		val ageGet = ::age.get()//获取属性的值
        val ageName = ::age.name//获取属性名称
        
        println("::age.get() == $ageGet")
        println("::age.name == $ageName")
    }

打印数据如下:

::age.get() == 18
::age.name == age

表达式 ::age 的计算结果是类型为 KProperty<Int> 的属性对象,它允许我们使用 get() 读取其值或使用 name 属性检索属性名。

对于一个可变 var 属性,例如 var day = 1::day 返回 KMutableProperty<Int> 类型的值,它有一个 set() 方法:

    var day = 0

    fun main() {
    
    
        ::day.set(2)
        println("::day.set() == $day")//打印:::day.set() == 2
    }

(3)扩展属性

对于扩展属性:需要声明在最顶层,否则 lastChar 在使用时会报错,不能同时是成员属性和扩展属性。

	//声明在最顶层
	private val String.lastChar: Char
    	get() = this[length - 1]

    fun main() {
    
    
        println(String::lastChar.get("Kotlin"))//打印:n
    }

属性引用也可以用于期望函数只有一个泛型参数的地方:

	val strings = arrayListOf("View", "TextView", "AppCompatAutoCompleteTextView")
	println(strings.map(String::length))//打印:[4, 8, 29]

2.5 与Java反射的互操作性

在 JVM 平台上,标准库包含了反射类的拓展,这些反射类提供了与 Java 反射对象之间的映射,例如:要找到作为 Kotlin 属性的 getter 后备字段或 Java 方法:

import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter

	class Person(val name: String)

    fun main() {
    
    
        println(Person::name.javaGetter)//打印:public final String Person.getName()
        println(Person::name.javaField)//打印:private final String Person.name
    }

要获得与 Java 类对应的 Kotlin 类,使用 .Kotlin 扩展属性:

	fun getKClass(a: Any): KClass<Any> = a.javaClass.kotlin

	println(getKClass(Person()))//打印:class com.suming.kotlindemo.blog.Person

三、类引用

最基本的反射特性是获得对 Kotlin 类的运行时引用。要获得对静态已知的 Kotlin 类的引用,可以使用类字面量语法:

val quo: KClass<ReferenceActivity> = ReferenceActivity::class//ReferenceActivity类的引用

引用 quoKClass 类型的值, KClass 表示一个类,并提供自检功能。

注意:Kotlin 类引用与 Java 类引用是不同的。要获取 Java 类引用,请在 KClass 实例上使用 .java 属性。

//获取 java 类引用
val referenceJava: Class<ReferenceActivity> = ReferenceActivity::class.java

四、 绑定引用

Kotlin 1.1开始,允许你使用成员引用语法 捕捉特定实例对象上的方法引用。绑定引用由对象实例,双冒号,成员三个元素组成。格式:对象实例 :: 成员

4.1 绑定函数和属性引用

你可以引用一个特定对象的实例方法:

	var numberRegex = "\\d+".toRegex()//将 //d+ 转化为正则表达式 /d+ ,表示匹配一个或多个数字
	println(numberRegex.matches("28"))//打印:true

	//存储numberRegex的引用,引用被绑定到它的接收者
	var isNumber = numberRegex::matches
	println(isNumber("28"))//打印:true

我们不是直接调用方法 matches,而是存储对它的引用。这样的引用被绑定到它的接收者,它可以直接调用(如上面)。也可以在需要函数类型的表达式时使用:

	val nums = arrayListOf("View", "123", "456")
	println(nums.filter(numberRegex::matches))//打印:[123, 456]

比较绑定引用和相应的未绑定引用的类型。绑定的可调用引用有它的接收者“附加”到它上面,所以接收者的类型不再是参数:

	//已绑定
	val isNumber: (CharSequence) -> Boolean = numberRegex::matches
	//未绑定
	val matches: (Regex, CharSequence) -> Boolean = Regex::matches

属性引用也可以绑定:

	val person = Person("Android", 24) //创建实例
	//val personAge = { person.age } //Kotlin1.1之前显式写出 lambda
	val personAge = person::age 

	println("person: age == ${
      
      personAge()}")//打印 person: age == 24

注意:personAge 是一个零函数的参数,在 Kotlin1.1 之前你需要显式写出 lambda { person.age },而不是使用绑定成员引用:person::age

另外:由于在 Kotlin1.2 没有必要显式地指定 this 作为接收者,所以 this::foo::foo 是等价的。

4.2 绑定类引用

你可以使用相同的 ::class 语法来获取特定对象的类引用,方法是将对象作为接收者:

	val view: View = AppCompatTextView(this)
	if (view is AppCompatTextView) {
    
    
		println(view::class.qualifiedName)//打印:androidx.appcompat.widget.AppCompatTextView
	}

不管接收方表达式类型(View)是什么,都可以获得对对象确切类的引用,例如 AppCompatTextViewTextView

4.3 结合构造函数引用

内部类构造函数的可调用引用可以通过提供外部类的实例来获得,格式 外部类实例 :: 外部类

	//外部类
    class Outer {
    
    
        inner class Inner//内部类
    }
    
	val outer = Outer()//外部类实例
	val boundInner = outer::Inner//内部类构造函数的可调用引用

五、 总结

这里来简单总结一下:

  • 1.使用反射特性需要添加 Kotlin -reflect.jar 包;

  • 2.双冒号 :: 表示引用,有属性引用、函数引用、类引用、构造函数引用、扩展函数引用、绑定引用等等;

  • 3.成员引用:使用 :: 运算符,来创建一个单个方法或者单个属性的函数值。成员引用由类名,双冒号,成员三个元素组成。例如:Person :: age

  • 4.类名 ::class 表示获取类的引用,类名 ::class.java 表示获取 Java 类的引用;

  • 5.构造函数引用,通过使用 :: 操作符并添加类名来引用构造函数:::类名

	//成员属性
    val member = "Kotlin"
    //成员方法
    fun method() {
    
    }
    //类
    class Person {
    
    
        val age = 20//类属性
    }

    fun main() {
    
    
        //1.引用函数
        ::method

        //2.引用属性
        ::member

        //3.引用类成员属性
        Person::age

        //4.Kotlin类引用
        Person::class

        //5.Java类引用
        Person::class.java

        //6.绑定引用
        val person = Person()
        val bindAge = person::age
    }

源码地址:https://github.com/FollowExcellence/KotlinDemo-master

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才

我是suming,感谢各位的支持和认可,您的点赞就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !

要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!

猜你喜欢

转载自blog.csdn.net/m0_37796683/article/details/111051521
今日推荐