Kotlin进阶学习-第三篇

Kotlin进阶学习第三篇

读者学习本篇文章前请先学习之前的文章

Kotlin系列已更新:

Kotlin基础学习-入门篇

Kotlin基础学习-第二篇

Kotlin进阶学习-第三篇

Kotlin进阶学习-第四篇

Kotlin进阶学习-第五篇

扩展

在这里插入图片描述

拓展函数

拓展函数:对类的方法进行补充,动态的给类添加方法

笔者之前在百度贴吧做过一段时间iOSiOS的开发语言OC就是支持扩展函数的

Java没有办法对系统类进行拓展,而Kotlin可以对其进行拓展

比如:实现一个统计String中字母个数的函数

若不借助拓展函数,定义StringUtil并实现lettersCount

object StringUtil {
    fun lettersCount(string: String): Int {
        var count = 0;
        for (char in string) {
            if (char.isLetter()) count++
        }
        return count
    }
}

调用如下:

StringUtil.lettersCount("ab2")

若借助拓展函数,可直接将lettersCount()方法添加到String类中,不必再创建StringUtil

创建如下:

创建String.kt,其职责就是对String进行拓展,创建新的文件可使得拓展函数拥有全局访问域,不定义新文件也是可以的,郭霖大佬是建议定义新文件。

fun String.lettersCount(): Int {
    var count = 0;
    for (char in this) {
        if (char.isLetter()) count++
    }
    return count
}

Kotlin访问则可直接使用String

var count = "111asd".lettersCount()

Java中调用需要如下方式:

StringKt.lettersCount("aaa");

拓展属性

Kotlin不仅可以拓展函数,还可以拓展属性,在String.kt加入以下代码,则相当于给String添加了一个值为10intget()是固定语法

val String.value : Int get() = 10

Kotlin中访问如下:

var value = "".value

Java中访问如下:

int value = StringKt.getValue("aaa");

原理

查看String.kt反编译的java代码

public final class StringKt {
  
   public static final int getValue(@NotNull String $this$value) {
       //判断参数是否为空
      Intrinsics.checkNotNullParameter($this$value, "$this$value");
      return 10;
   }

   public static final int lettersCount(@NotNull String $this$lettersCount) {
       //判断参数是否为空
      Intrinsics.checkNotNullParameter($this$lettersCount, "$this$lettersCount");
      int count = 0;
      String var4 = $this$lettersCount;
      int var5 = $this$lettersCount.length();

      for(int var3 = 0; var3 < var5; ++var3) {
         char var2 = var4.charAt(var3);
         boolean var7 = false;
         if (Character.isLetter(var2)) {
            ++count;
         }
      }

      return count;
   }
}

生成两个方法,两个方法都需参数,这也是为什么Java调用必须传参的原因,Kotlin不用传参是因为语法糖自动把调用者传递了

查看调用代码的反编译Java代码

var count = "111asd".lettersCount()  //反编译等价于int count = StringKt.lettersCount("111asd");

var value = "".value //反编译等价于 int value = StringKt.getValue("");

运算符重载

operator的使用

此节不太好理解,但很有趣

运算符重载是拓展运算符的功能,比如对象可使用运算符操作

val obj1 = Obj()
val obj2 = Obj()
val value = obj1 + obj2

运算符在编译时会替换成方法的调用,比如加法会替换成plus方法,Kotlin的每个运算符都有其对应的方法。

而有些对象运算没有实际意义,下面实现一个有意义运算的Money

拓展运算符需要使用operator关键字

Money

class Money(val value: Int) {
    operator fun plus(money: Money): Money {
        val sum = value + money.value
        return Money(sum)
    }
}

使用如下:

fun main() {
    val money = Money(10) + Money(30)
    println(money.value)
    //输出 40
}

若想其可和int直接相加,可重载plus方法

operator fun plus(money: Int): Money {
    val sum = value + money
    return Money(sum)
}

调用此重载方法则可和int相加

fun main() {
    val money = Money(10) + 20
    println(money.value)
    //输出30
}

其上述不仅只有加法运算,Kotlin所支持的可拓展运算符如下:

语法糖表达式实际调用函数
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
a++a.inc()
a–a.dec()
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()
a == ba.equals(b)
a > b
a < b
a >= ba.compareTo(b)
a <= b
a…ba.rangeTo(b)
a[b]a.get(b)
a[b] = ca.set(b, c)
a in bb.contains(a)

原理

Kotlin的语法糖,在编译器可以把加号转换成相对应的方法调用

上述的代码:

fun main() {
    val money = Money(10) + 20
    println(money.value)
    //输出30
}

反编译成java代码为:

public static final void main() {
    //调用对象的plus方法
   Money money = (new Money(10)).plus(20);
   int var1 = money.getValue();
   System.out.println(var1);
}

高阶函数

定义与语法规则

一个函数接收另一个函数作为参数,或者返回值是另一个函数,那此函数则为高阶函数.

高级函数和Lambda紧密相连,之前都是调用Kotlin内置的可传入Lambda的函数,那么程序员怎么定义此种类型的函数呢。

在学习定义函数之前,首先要学会定义函数类型,语法如下:

(String, Int)-> Unit

解释上述规则,既然是函数类型,必不可少则是参数类型和返回类型,括号内的则为参数类型,多参数使用中间使用,隔开,->右边为返回值类型。

将此类型的参数加到方法上,则此方法就是高阶函数,如下:

fun test(func: (String, Int)->Unit) {
    func("123",1)
}

示例

实现一个操作两个Int的函数,可能是加法,可能是减法,等等,其没有实际意义,只是演示所用。

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int)->Unit) = operation(num1, num2)

声明加减方法:

fun plus(num1: Int, num2: Int) = num1 + num2
fun minus(num1: Int, num2: Int) = num1 - num2

调用如下:

fun main() {
    println(num1AndNum2(3, 2, ::plus))
    println(num1AndNum2(1, 2, ::minus))
}
//结果 5,-1

Kotlin中引用方法需使用::

若不定义新方法,则可使用Lambda,借助Lambda使用如下:

fun main() {
    num1AndNum2(1, 2) { num1, num2 -> num1 - num2 }
    num1AndNum2(3, 2) { num1, num2 -> num1 + num2 }
}
//结果 -1,5

模仿实现apply函数

拓展StringBuilder类的方法,给其加上build,使用效果与apply相同

回想applyapply内部可以对调用者本身进行操作,那么意味着在函数式中能拿到调用者的上下文,上述的定义方式显然不能拿到调用者的上下文,此时需借助其他语法,定义如下:

fun StringBuilder.bulid(block: StringBuilder.() -> Unit): StringBuilder {
    block()
    return this
}

在()前加上类名.,表示此函数类型是定义在此类中的,此时则可操作此对象,并返回

调用如下:

fun main() {
    val rs = StringBuilder().bulid {
        append("abc")
    }
    println(rs)
    //结果 abc
}

原理

笔者发现反编译的java文件有问题,此次分析直接分析下述代码的字节码 HelloWorld.kt如下

fun main() {
    var a = num1AndNum2(1, 2) { a,b -> a + b }
}

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int{
    return  operation(num1, num2)
}

完整的字节码如下:

// ================com/hbsd/demo/HelloWorldKt.class =================
// class version 52.0 (52)
// access flags 0x31
public final class com/hbsd/demo/HelloWorldKt {


  // access flags 0x19
  public final static main()V
   L0
    LINENUMBER 10 L0
    ICONST_1
    ICONST_2
    GETSTATIC com/hbsd/demo/HelloWorldKt$main$a$1.INSTANCE : Lcom/hbsd/demo/HelloWorldKt$main$a$1;
    CHECKCAST kotlin/jvm/functions/Function2
    INVOKESTATIC com/hbsd/demo/HelloWorldKt.num1AndNum2 (IILkotlin/jvm/functions/Function2;)I
    ISTORE 0
   L1
    LINENUMBER 13 L1
    RETURN
   L2
    LOCALVARIABLE a I L1 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1

  // access flags 0x1009
  public static synthetic main([Ljava/lang/String;)V
    INVOKESTATIC com/hbsd/demo/HelloWorldKt.main ()V
    RETURN
    MAXSTACK = 0
    MAXLOCALS = 1

  // access flags 0x19
  // signature (IILkotlin/jvm/functions/Function2<-Ljava/lang/Integer;-Ljava/lang/Integer;Ljava/lang/Integer;>;)I
  // declaration: int num1AndNum2(int, int, kotlin.jvm.functions.Function2<? super java.lang.Integer, ? super java.lang.Integer, java.lang.Integer>)
  public final static num1AndNum2(IILkotlin/jvm/functions/Function2;)I
    // annotable parameter count: 3 (visible)
    // annotable parameter count: 3 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 2
   L0
    ALOAD 2
    LDC "operation"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 17 L1
    ALOAD 2
    ILOAD 0
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ILOAD 1
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    INVOKEINTERFACE kotlin/jvm/functions/Function2.invoke (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; (itf)
    CHECKCAST java/lang/Number
    INVOKEVIRTUAL java/lang/Number.intValue ()I
    IRETURN
   L2
    LOCALVARIABLE num1 I L0 L2 0
    LOCALVARIABLE num2 I L0 L2 1
    LOCALVARIABLE operation Lkotlin/jvm/functions/Function2; L0 L2 2
    MAXSTACK = 3
    MAXLOCALS = 3

  @Lkotlin/Metadata;(mv={1, 5, 1}, k=2, d1={"\u0000\u0016\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0008\n\u0002\u0008\u0003\n\u0002\u0018\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u001a0\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u00032\u0018\u0010\u0006\u001a\u0014\u0012\u0004\u0012\u00020\u0003\u0012\u0004\u0012\u00020\u0003\u0012\u0004\u0012\u00020\u00030\u0007\u00a8\u0006\u0008"}, d2={"main", "", "num1AndNum2", "", "num1", "num2", "operation", "Lkotlin/Function2;", "app_debug"})
  // access flags 0x18
  final static INNERCLASS com/hbsd/demo/HelloWorldKt$main$a$1 null null
  // compiled from: HelloWorld.kt
}


// ================com/hbsd/demo/HelloWorldKt$main$a$1.class =================
// class version 52.0 (52)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function2<Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;>;
// declaration: com/hbsd/demo/HelloWorldKt$main$a$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function2<java.lang.Integer, java.lang.Integer, java.lang.Integer>
final class com/hbsd/demo/HelloWorldKt$main$a$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2 {


  // access flags 0x1041
  public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    ALOAD 0
    ALOAD 1
    CHECKCAST java/lang/Number
    INVOKEVIRTUAL java/lang/Number.intValue ()I
    ALOAD 2
    CHECKCAST java/lang/Number
    INVOKEVIRTUAL java/lang/Number.intValue ()I
    INVOKEVIRTUAL com/hbsd/demo/HelloWorldKt$main$a$1.invoke (II)I
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ARETURN
    MAXSTACK = 3
    MAXLOCALS = 3

  // access flags 0x11
  public final invoke(II)I
    // annotable parameter count: 2 (visible)
    // annotable parameter count: 2 (invisible)
   L0
    LINENUMBER 11 L0
    ILOAD 1
    ILOAD 2
    IADD
   L1
    IRETURN
   L2
    LOCALVARIABLE this Lcom/hbsd/demo/HelloWorldKt$main$a$1; L0 L2 0
    LOCALVARIABLE a I L0 L2 1
    LOCALVARIABLE b I L0 L2 2
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0x0
  <init>()V
    ALOAD 0
    ICONST_2
    INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x19
  public final static Lcom/hbsd/demo/HelloWorldKt$main$a$1; INSTANCE

  // access flags 0x8
  static <clinit>()V
    NEW com/hbsd/demo/HelloWorldKt$main$a$1
    DUP
    INVOKESPECIAL com/hbsd/demo/HelloWorldKt$main$a$1.<init> ()V
    PUTSTATIC com/hbsd/demo/HelloWorldKt$main$a$1.INSTANCE : Lcom/hbsd/demo/HelloWorldKt$main$a$1;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

  @Lkotlin/Metadata;(mv={1, 5, 1}, k=3, d1={"\u0000\n\n\u0000\n\u0002\u0010\u0008\n\u0002\u0008\u0003\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u00012\u0006\u0010\u0003\u001a\u00020\u0001H\n\u00a2\u0006\u0002\u0008\u0004"}, d2={"<anonymous>", "", "a", "b", "invoke"})
  OUTERCLASS com/hbsd/demo/HelloWorldKt main ()V
  // access flags 0x18
  final static INNERCLASS com/hbsd/demo/HelloWorldKt$main$a$1 null null
  // compiled from: HelloWorld.kt
}


// ================META-INF/app_debug.kotlin_module =================
                


com.hbsd.demoHelloWorldKt" * 


分析num1AndNum2(1 2) { a,b -> a + b }的字节码

LINENUMBER 10 L0
ICONST_1
ICONST_2
GETSTATIC com/hbsd/demo/HelloWorldKt$main$a$1.INSTANCE : Lcom/hbsd/demo/HelloWorldKt$main$a$1;
CHECKCAST kotlin/jvm/functions/Function2
INVOKESTATIC com/hbsd/demo/HelloWorldKt.num1AndNum2 (IILkotlin/jvm/functions/Function2;)I
ISTORE 0

num1AndNum2方法需传递三个参数1, 2, HelloWorldKt$main$a$1.INSTANCE

num1AndNum2方法的声明字节码如下

// access flags 0x19
// signature (IILkotlin/jvm/functions/Function2<-Ljava/lang/Integer;-Ljava/lang/Integer;Ljava/lang/Integer;>;)I
// declaration: int num1AndNum2(int, int, kotlin.jvm.functions.Function2<? super java.lang.Integer, ? super java.lang.Integer, java.lang.Integer>)
public final static num1AndNum2(IILkotlin/jvm/functions/Function2;)I

declaration表示此函数的参数,需要三个参数,两个int,一个接口对象,这个接口对象是num1AndNum2(1, 2) { a,b -> a + b }传递的HelloWorldKt$main$a$1.INSTANCE

operation(num1, num2)此行代码的字节码如下(num1AndNum2方法的片段):

LINENUMBER 17 L1
ALOAD 2
ILOAD 0
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ILOAD 1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
INVOKEINTERFACE kotlin/jvm/functions/Function2.invoke (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; (itf)
CHECKCAST java/lang/Number
INVOKEVIRTUAL java/lang/Number.intValue ()I
IRETURN

调用传入参数的valueof拿到Integer对象,最终调用第三个参数的invoke 方法并将上述两个Integer对象传递过去,下面分析第三个参数是谁

在整个文件的字节码中还存在一个内部类

final class com/hbsd/demo/HelloWorldKt$main$a$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2

而第三个参数的声明如下

public final static Lcom/hbsd/demo/HelloWorldKt$main$a$1; INSTANCE

operation(num1, num2)调用的invoke则是HelloWorldKt$main$a$1invoke

HelloWorldKt$main$a$1存在两个invoke方法,此时执行的为需要两个Object的方法,此方法关键代码如下:

ALOAD 0
ALOAD 1
CHECKCAST java/lang/Number
INVOKEVIRTUAL java/lang/Number.intValue ()I
ALOAD 2
CHECKCAST java/lang/Number
INVOKEVIRTUAL java/lang/Number.intValue ()I
INVOKEVIRTUAL com/hbsd/demo/HelloWorldKt$main$a$1.invoke (II)I
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

获取两个Integer对象的int值,执行invoke (II)方法,最终将结果的int包装成Integer返回

invoke(II)中的关键代码

LINENUMBER 11 L0
ILOAD 1
ILOAD 2
IADD

读取两个值,进行加法操作

总结:Lambda会帮助生成一个内部类,此内部类是继承自kotlin/jvm/functions/Functionxx代表Lambda需要几个参数,且内部包含此类的静态变量INSTANCE,类内部还会生成invoke方法,Lambda的逻辑会copyinvoke

当我们执行高阶函数时,Lambda参数会被编译成上述INSTANCE对象,高阶函数内部又会去调用此对象的invoke方法,invoke内部又是Lambda中的逻辑,因此Lambda就被执行了。

内联函数

inline

上述可以看到使用Lambda会生成内部类,肯定会造成额外内存和性能开销

Kotlin提供了某种机制可将Lambda表达式的弊端去除

fun main() {
    var a = num1AndNum2(1, 2) { a,b -> a + b }
}

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
    return  operation(num1, num2)
}

查看其字节码,其反编译的Java代码是正确的,查看其对应的Java代码:

public static final void main() {
    int num1$iv = 1;
    int num2$iv = 2;
    int var10000 = num1$iv + num2$iv;
}

// $FF: synthetic method
public static void main(String[] var0) {
    main();
}

num1AndNum2(1, 2) { a,b -> a + b }直接替换成a + b,避免了内部类的生成,无不感叹Kotlin强大的编译器。

Java中也存在许多内联优化,比如:

java早期final修饰的方法会直接被编译器编译成嵌套方法

final变量在编译时就直接替换

若方法的实现很简单,也会进行方法内联,避免栈帧的生成

但其也存在缺点,调用者越多,复制品越多,越消耗空间。

noinline

假设一个函数有多个函数型参数,若加上inline会使全部的参数参与内联,若某些不想参与内联呢,则需要在参数前加上noinline关键字.

使用如下:

inline fun test(lambda1: () -> Unit, noinline lambda2: () -> Unit) {
    lambda1()
    lambda2()
}

此时参数二将不参与内联

内联不是好处多多吗,为何有时候又不需要内联了吗?

内联的函数被代码替换,其没有真正的参数含义,只能传递给另一个内联函数,而非内联的函数是一个真正的参数。

内联函数可使用return,非内联只能局部return

内联时return会跑到调用者的函数中,return结束的是调用者的函数,非内联上述分析字节码可知结束的是内部类中的方法

crossinine

内联还存在以下问题:

翻译过来为,block可能存在不是本地的return

内联函数的return结束的是调用者,而这么写就会导致return结束的是Runnablerun函数,从而导致冲突。

解决方式有两个:

去掉inline,不使用内联,return@runRunnable结束的还是内部类中的invoke函数

加上crossinine关键字

inline fun runRunnable(crossinline block : () -> Unit) {
    Runnable() {
        block()
    }.run()
}

crossinine像一个契约,保证Lambda一定不使用return,但是仍可使用return@runRunnable,其除了return的特性有些变化,保留了内联函数的其他特性。

高阶函数的最佳实践

上述中我们学习了高阶函数的使用和原理,本节对其使用进行实践:

SharedPreferences使用优化

熟悉安卓的同学都了解过SharedPreferences,其帮助我们持久化键值对数据,使用如下:

//传入文件名和权限,拿到edit对象
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
//添加数据
editor.putString("name", "zjm")
editor.putInt("name", 21)
editor.putBoolean("isBoy", true)
//提交数据
editor.apply()

使用高级函数可对其进行优化:

fun SharedPreferences.open(block: SharedPreferences.Editor.() -> Unit) {
    val editor = edit()
    editor.block()
    editor.apply()
}

使用如下:

getSharedPreferences("data", Context.MODE_PRIVATE).open {
    putString("name", "zjm")
    putInt("name", 21)
    putBoolean("isBoy", true)
}

其实KTX已经支持此函数,其函数名为edit(),我们只是对其进行仿照实现

KTX的导入如下(在创建项目时已经自动导入):

implementation 'androidx.core:core-ktx:1.3.2'

ContentValues使用优化

了解SQLiteDB的同学肯定知道ContentValues,不知道也没有关系,只需要知道使用它需要下面方式:

//初始化ContentValues
val values = ContentValues()
//赋值
values.put("name", "zjm")
values.put("age", 21)
values.put("isBoy", true)
//插入数据库,第一个参数为表名,第二个参数为若第三个参数没有此列的默认值,添加的数据
db.insert("person", null, values)

入门篇中我们了解了mapOf的用法,其传入key to value的方式即可添加一个Map元素,其实在底层A to B这种语法会创建一个Pair对象,其接收一个键值对。

依靠Pair即可对上述代码进行优化:

声明一个顶层方法cvOf

fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues {
    val cv = ContentValues()
    for (pair in pairs) {
        val key = pair.first
        val value = pair.second
        when(value) {
            is Int -> cv.put(key, value)
            is Long -> cv.put(key, value)
            is Short -> cv.put(key, value)
            is Float -> cv.put(key, value)
            is Double -> cv.put(key, value)
            is Boolean -> cv.put(key, value)
            is String -> cv.put(key, value)
            is Byte -> cv.put(key, value)
            is ByteArray -> cv.put(key, value)
            null -> cv.putNull(key)
        }
    }
    return cv
}

为什么需要借助when呢,直接cv.put(key, value)可以吗,不可以,直接使用会越界,put并不接收Any参数,when如果命中is,会触发类型推导自动帮助类型转换

上述使用并没有借助高阶函数,下面使用apply优化,apply会返回调用者,在内部可直接调用调用者的函数,这样我们就能省略很多代码

fun cvOf(vararg pairs: Pair<String, Any?>) = ContentValues().apply {
    for (pair in pairs) {
        val key = pair.first
        val value = pair.second
        when(value) {
            is Int -> put(key, value)
            is Long -> put(key, value)
            is Short -> put(key, value)
            is Float -> put(key, value)
            is Double -> put(key, value)
            is Boolean -> put(key, value)
            is String -> put(key, value)
            is Byte -> put(key, value)
            is ByteArray -> put(key, value)
            null -> putNull(key)
        }
    }
}

使用如下:

val values = cvOf("a" to 1, "b" to "c", "d" to true)
db.insert("person", null, values)

同样的KTX也给我们提供了此函数,函数名为contentValueOf()

到这本篇结束

原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}

👍 点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!}

⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!}

✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
根据提供的引用内容,出现了两个关于Kotlin的错误信息。第一个引用显示了一个无法解析依赖的错误,指出无法下载kotlin-reflect.jar文件。第二个引用显示了一个关于kotlin-gradle-1.8.10.jar (org.jetbrains.kotlin:kotlin-reflect)",这个错误通常是由于Gradle无法找到所需的kotlin-reflect库而引起的。解决这个问题的方法是确保你的项目的Gradle配置正确,并且指定了正确的Kotlin版本。 你可以尝试以下几个步骤来解决这个问题: 1. 确保你的项目的build.gradle文件包含了正确的Kotlin版本和kotlin-gradle-plugin版本。你可以在build.gradle文件找到类似于以下代码的部分: ```groovy ext { kotlin_version = '1.8.10' } dependencies { // ... implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // ... } buildscript { // ... dependencies { // ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // ... } } ``` 请确保kotlin_version变量的值与你想要使用的Kotlin版本一致,并且在dependencies和buildscript的classpath正确引用了kotlin-gradle-plugin。 2. 如果你已经确认了build.gradle文件的配置正确无误,那么可能是因为Gradle无法从远程仓库下载kotlin-reflect.jar文件。你可以尝试清除Gradle的缓存并重新构建项目。在命令行执行以下命令: ```shell ./gradlew clean ``` 然后重新构建项目: ```shell ./gradlew build ``` 这将清除Gradle的缓存并重新下载所需的依赖。 3. 如果上述步骤***切换到其他网络环境来解决这个问题。 希望以上步骤能够帮助你解决问题。如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值