kotlin基础语法-06-语法特性-真泛型

本文主要写一些kotlin中的语法特性包含、变量、常量与只读、空安全是如何实现的、内联的特殊情况、kotlin的真泛型

先来学个java命令、jdk 为我们提供的一个工具、 用来反编译一个class 文件的、

javap [optoin] *.class 

-c  输出分解后的代码。 例如: 类中每一个方法内、包含Java字节码的指令 
-verbose 输入栈大小, 方法参数的个数。

具体使用过程很简单、就是找到idea 编译好好的额class 文件、 使用javap -c class 文件路径、 就可以反编译该class文件、 其输出结构就包含了类的声明、方法的声明
类似于这样的格式

public final class com.samuelnotes.kotlin.KotlinBaseKt {
  public static final void main(java.lang.String[]);
    Code:
       0: aload_0
       1: ldc           #9                  // String args
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: invokestatic  #19                 // Method printB:()V
       9: return

不再赘述。

1.变量、常量与只读

声明方式不再赘述 ,

var与val 声明的变量、 val 不可以有setter方法、 由于val 声明的变量是不可变的变量。

那么val 是不是就是常量了?

class User(var birthyear: Int){
    val age : Int 
    get(){
        return Calendar.getInstance().get(Calendar.YEAR) - birthyear
    }
    
    fun onYearsLater(){
        birthyear--
    }
}

fun main(args:Array<String>){
    val user = User(1991)
    println(user.age)
    
    person.onYearsLater()
    
    println(user.age)
    
}

打印结果:

28
29

我们虽然无法直接改变age的值、但是却可以通过get 方法来间接地改变值。所以可以简单看成是final类型的变量。

那么如果我们真的要声明一个常量 如何声明呢?

const val num = 0

const 只能修饰object属性, 或者top-level (写在类外边)变量

const 变量的值必须在编译期间确定下来,所以该类型必须为String或者基本类型

2.kotlin的空安全

在Java中我们晓得当尝试调用空对象的成员变量或者方法时会触发空指针异常。
那么kotlin 如何保证空安全呢,

要么每次调用对象的时候、都去进行对象判空、在运行期对象空指针。

要么通过静态代码检查、编译器插件检查、在编译器避免空指针异常。

kotlin是两者结合进行检查:

当一个对象声明为不可为空时、那么就不需要进行静态检查。

当一个对象声明为可空时,那么在调用时就会进行对象的判空。
上下文推断、如果对这个对象开始调用时判断不为空、那么就在后续的调用中都不会为空。 除非是全局变量、在不同的地方赋值为空,那么就是另外一回事了。

来段代码:

fun main(args:Array<String>) {

    var a : String? = null

    println(getValue(a))
}

fun  getValue(str:String?):String{
    
    return "s"+ str?.length
}

这段代码很简单、就是一个简单的字符串拼接、 打印、 那么通过java decompile 工具反编译后的结果呢

public final class NullTest{
    
    public static void main(@NotNull String[] args){
        
        Intrinsics.checkParameterIsNotNull(args,"args");String a = "";
        String str1 = getValue(a);System.out.println(str1);
    }
    
    public static final String getValue(@NotNull Strng str){
        
        Intrinsics.checkParameterIsNotNull(str,"s");return "s"+s.length();
    }
    
}

看到了吧、 在对象调用的每一次、每个时机、都会冒出来Intrinsics.checkParameterIsNotNull 这个方法、 这就是检测参数是否为空、看一下源码:

// 类很好找、 这是该方法、就是判空、抛异常的处理 。 
    public static void checkParameterIsNotNull(Object value, String paramName) {
        if (value == null) {
            throwParameterIsNullException(paramName);
        }
    }
    
    private static void throwParameterIsNullException(String paramName) {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

        // #0 Thread.getStackTrace()
        // #1 Intrinsics.throwParameterIsNullException
        // #2 Intrinsics.checkParameterIsNotNull
        // #3 our caller
        StackTraceElement caller = stackTraceElements[3];
        String className = caller.getClassName();
        String methodName = caller.getMethodName();

        IllegalArgumentException exception =
                new IllegalArgumentException("Parameter specified as non-null is null: " +
                                             "method " + className + "." + methodName +
                                             ", parameter " + paramName);
        throw sanitizeStackTrace(exception);
    }

还有一种情况、 比如在java代码中有两个注解, 一个是NotNull, 一个是Nullable 来段代码:

public static void main(String [] args){
    /// 这里如果调用了不可为空注解的方法、那么传入null 调用该方法的时候、就会被编译器警告、该方法不可以传入空参数。 
    test(null)
}
// 这个是传入参数是不可以为空 
public static void test(@NotNull String str){
    System.out.println(str);
}
// 这个是 放马来吧、 空不空都可以处理 
public static void test2(@Nullable String name){
    System.out.println(name)
}

以上代码,不再赘述。

3. kotlin 内联 inline 与 noinline 和 corssinline

inline 关键字我们上边已经介绍过了, inline关键字会这 编译期就会拆解方法调用为语句调用、平铺。提取lambda表达式内容为调用级函数。

这里说一下内联函数的特殊情况。
在kotlin 中,内部lambda是不允许中断外部函数执行的, 但是如果使用了inline关键字,而在inline修饰的lambda表达式内含有中断语句, 例如return, 这时候就会中断了外部函数的执行。

为之奈何?

crossinline 隆重登场, 这个关键字不允许inline 修饰的lambda表达式中断外部函数的执行

crossinline 修饰的lambda 表达式 不会被平铺、而inline 方法却还会被平铺、所以lambda修饰关键字内部的return 不会影响到外部函数的执行。

稍后会有代码看, 介绍完再说,

还有一个关键字是noinline, 这个用于修饰高阶函数, 返回值为lambda表达式的函数 。 意思是拒绝内联,

例子来了,

fun main(args: Array<String>){

    println(" hello world ")

    var runnable = Runnable { println("runnable print") }

//    test1 {
//        println("hello test1")
//        return
//    }

//    println("hello -------")


    test2 {
        println("hello test2")
        return@test2
    }

    println("hello +++++")



    test3(runnable::run,{
        println("hello test3")
    })

    println("hello ***")

}

inline  fun test1(arg : ()->Unit):Unit{
    arg.invoke()
    return
}

//  在参数前边添加crossinline 可以直接执行 拆解之后后边需要执行的语句
inline  fun test2(crossinline arg : ()->Unit):Unit{
    arg.invoke()
    return
}


  这里的arg1 如果不添加noinline 关键字,拆解之后会认为,main 方法的返回值 为arg1 , 很显然返回值为lambda表达式是不能够直接平铺的。
inline fun test3(noinline arg1:()->Unit, arg2: ()->Unit):()->Unit{

    arg1.invoke()
    arg2.invoke()

    println("hello test3end")

    return arg1
}
/// 当我们返回值是lambda表达式时、则需要把该参数标记为noinline 、就是拒绝内联。

不多说了, 注释已经很清楚了。 我们继续前进。

4.kotlin 泛型 真泛型

这里只说一下kotlin中的泛型与java中不同的地方、
kotlin 中的泛型 是可以限定

/// 这句话就是  T类型 用where 子句来限制 T的类型、 必须同时是Callback 类型接口、 也必须同时实现Runnable 接口。 
class Test<T> where T : Callback, T : Runnable{
    
    fun add(t:T){
        t.run()
        t.callback()
    }
}

调用就不再展示了, 当然如果一个 A 只实现了 Callback ,B实现了Runnable 、 同时又继承了A类, 那就意味着B也是符合where子句的要求的。

我们知道java中的泛型只是在编译层做了限制、 也就是说代码上写的List arr ; 而编译完成之后 就是 List arr ; 了 , 他只是在编译层面对类型进行了限制。
而kotlin 则是真泛型。

看一段代码,

public  <T> T  fromJson(String json, Class<T> classOfT){
    /// .../
}
/// 这里的 reified是真泛型、 只能修饰方法、并不能修饰类。
inline fun <reified T> Gson.fromJson(json:String):T{

    return fromJsono(json,T::class.java)
}

这样我们在调用的时候 就可以只传入json 就可以拿到我们想要的类型了。

再来一段代码:

fun main(args:Array<String>){

    /// 通过kotlin 语法糖简写
    val  b   = View<Presenter>().presenter
    
    println(b)
    
    /// 完整的调用链
    val presenter = View.Companion.invoke<Presenter>().presenter
    
    println(presenter)
    
    
}

class View<T> (val clazz: Class<T>){
    
    val presenter by lazy { clazz.newInstance() }
    
    /// 这里李勇 companion调用 在构造函数之前调用、 在这里重新定义构造函数invoke 、再通过懒加载的方式指定动态代理presenter 
    companion object{
        
        inline  operator fun <reified T> invoke() = View(T::class.java)
    }
}

class Presenter{
    override fun toString(): String {
        return "presenter"
    }
}

5. 总结

多上手敲、多尝试、多思考、 有问题评论区我们共同讨论进步。