Kotlin与Java互操作笔记

Kotlin语言基础笔记

Kotlin流程控制语句笔记

Kotlin操作符重载与中缀表示法笔记

Kotlin扩展函数和扩展属性笔记

Kotlin空指针安全(null-safety)笔记

Kotlin类型系统笔记

Kotlin面向对象编程笔记

Kotlin委托(Delegation)笔记

Kotlin泛型型笔记

Kotlin函数式编程笔记

Kotlin与Java互操作笔记

Kotlin协程笔记

Kotlin官方一直以100% interoperable with Java™作为第一要素,他不是像Scala一样把类库都自己实现一遍,而是通过扩展函数、函数编程等特性对现有的Java进行增强,同时保持对Java的100%兼容。正是这个特性,我们可以在一个项目中同时使用Java和Kotlin,一个大型的项目,如果换一种语言来实现的话,这个代价是非常大的。但是对于一个Java项目,你可以某一部分使用Kotlin来实现,然后慢慢地一步步的把整个项目所有代码都改成Kotlin实现。这样风险就会小非常多。

1. Kotlin调用Java

1.1 Kotlin使用Java的集合类

    val kotlinList = listOf(1, 2, 3, 4)

    //Java原生的ArrayList
    val javaList = ArrayList<Int>()

    for (item in kotlinList) {
        javaList.add(item)
    }

操作Java原生的集合类跟Kotlin中使用类没有什么区别。

1.2 调用Java类的getter和setter

假如我们有这样一个Person类。

package com.dengyin2000.java;

public class Person {
    private String name;
    private Long id;
    private boolean isFemale;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public boolean isFemale() {
        return isFemale;
    }

    public void setFemale(boolean female) {
        isFemale = female;
    }
}

那么在Kotlin中要怎样使用呢?

    val person = Person()
    person.id = 1
    person.name = "Denny"
    person.isFemale = false

    println("Person(id=${person.id}, name=${person.name}, isFemale=${person.isFemale})")

println函数中的person类调用的是Person类的getter,上面属性设置实际上调用的是Person类的setter方法。

1.3 空安全

Java声明的类型在Kotlin中会被特别对待并称为平台类型(platform types),对于这种类型的空检查会放宽,这样做就使得跟Java的调用方式一致,但是Java中的任何引用都可能为null,这样我们使用Kotlin调用来自Java的对象的时候就有可能会出现空安全的问题。例如:

    val person = Person()
    person.id = 1
    person.isFemale = false

    println(person.name.substring(1))

person.name运行时为null,当然调用的时候会person.name.substring(1)时会发生异常。


异常

当然为了避免null的问题,我们可以使用Koltin的安全调用:

    println(person.name?.substring(1))  //打印null

1.4 平台类型

平台类型不能在程序中显式表述,因此在语言中没有相应语法。 然而,编译器和 IDE 有时需要(在错误信息中、参数信息中等)显示他们,所以我们用一个助记符来表示他们:

  • T! : 表示 T 或者 T?
  • (Mutable) Collection<T>! : 表示 “可以可变或不可变、可空或不可空的 T 的 Java 集合”
  • Array<(out) T>! : 表示“可空或者不可空的 T(或 T 的子类型)的 Java 数组”

1.5 Kotlin与Java中的类型映射

Kotlin 特殊处理一部分 Java 类型。这样的类型不是“按原样”从 Java 加载,而是 映射 到相应的 Kotlin 类型。 映射只发生在编译期间,运行时表示保持不变。怎么理解这句话呢?就是说在Kotlin中加载这些Java的类型时,编译器会转成对应的Kotlin类型,这样就能用到Koltin中对Java的增强功能(扩展函数等)。

1.5.1 Java的原生类型映射到对应的Kotlin类型
Java的原生类型映射到对应的Kotlin类型
1.5.2 Java中的一些内置类型也会做相应的映射
Java中的一些内置类型也会做相应的映射
1.5.3 Java的基本类型的包装类对应到可空额Kotlin类型
Java的基本类型的包装类对应到可空额Kotlin类型
1.5.4 类型参数的Java类型映射到Kotlin中的平台类型

例如:List<java.lang.Integer> 在Kotlin会变成List<Int!>
集合类型在 Kotlin 中可以是只读的或可变的,因此 Java 集合类型作如下映射: (下表中的所有 Kotlin 类型都在 kotlin.collections包中):


类型参数的Java类型映射到Kotlin中的平台类型
1.5.5 Java的数组映射
Java的数组映射

1.6 Kotlin中使用Java泛型

Kotlin 的泛型与 Java 有点不同。当将 Java 类型导入 Kotlin 时,我们会执行一些转换:
Kotlin中使用Java泛型

和 Java 一样,Kotlin 在运行时不保留泛型,即对象不携带传递到他们构造器中的那些类型参数的实际类型。

1.7 Java可变参数

假如我们有一个这样的类,有一个可变参数的静态方法:

package com.dengyin2000.java;

public class StringUtils {

    public static String connect(String... strings) {
        StringBuilder sb = new StringBuilder();
        for (String string : strings) {
            sb.append(string).append(",");
        }
        return sb.toString();
    }
}

因为Kotlin并没有可变类型,所以我们需要使用*来传递一个String数组来达到相应的目的:

    val listOf = arrayOf("Denny", "Deng")
    println(StringUtils.connect(*listOf))  //打印Denny,Deng

1.8 Unchecked Exception

在Kotlin中,所有的异常都是Unchecked Exception,也就是说编译器不会强迫你捕获任何的异常,但是在Java中你是需要捕获Checked Exception。如下:


Java Checked Exception

我们需要try catch保护起来。


image.png

但是在Kotlin中不需要try catch,只是路过抛出来你没有try catch的话,程序还是会挂。

1.9 java.lang.Object方法使用

当 Java 类型导入到 Kotlin 中时,类型 java.lang.Object 的所有引用都成了 Any。 而因为 Any 不是平台指定的,它只声明了 toString()、hashCode() 和 equals() 作为其成员, 所以为了能用到 java.lang.Object 的其他成员,你需要如下手段:

1.9.1 wait() / notify()

需要把对象转成java.lang.Object来使用:

    val p = Person()
    (p as java.lang.Object).wait()
1.9.2 getClass()

要取得对象的 Java 类,我们可以在类引用上使用 java 扩展属性,它是Kotlin的反射类kotlin.reflect.KClass的扩展属性。

val fooClass = foo::class.java

上面的代码使用了自 Kotlin 1.1 起支持的绑定类引用。我们也可以使用 javaClass 扩展属性。

val fooClass = foo.javaClass
1.9.3 clone()

要覆盖 clone(),需要继承 kotlin.Cloneable

class Example : Cloneable {
    override fun clone(): Any { …… }
}

1.10 Kotlin与Java 的反射

我们可以使用 instance::class.javaClassName::class.java 或者 instance.javaClass 通过 java.lang.Class 来进入 Java 的反射类java.lang.Class, 之后我们就可以使用Java中的反射的功能特性了。

1.11 SAM转换

SAM = single abstract method,在Java中被称为SAM类型。例如:Runnable接口。在Kotlin中我们可以这样创建SAM接口实例:

val runnable = Runnable { println("SAM") } 

1.12 Java使用了Kotlin的关键字

一些 Kotlin 关键字在 Java 中是有效标识符:in、 object、 is等等。
如果一个 Java 库使用了 Kotlin 关键字作为方法,我们可以通过反引号(`)字符转义它来调用该方法。例如我们有个Java类,其中有个is方法:

public class StringUtils {

    public static boolean is(String value, String value1) {
        return value.equals(value1);
    }

}

我们需要在Kotlin中需要这样调用:

println(StringUtils.`is`("a", "b"))  //打印false

2. Java调用Kotlin

Kotlin最终还是会编译成class,所以要怎么调Kotlin其实就是要看Kotlin翻译成的class是怎样的。Kotlin可以通过一些annotation来调整Kotlin翻译成的class结果。

2.1 Java访问Kotlin属性

假如我们有下面一个Kotlin的类:

class Student{
    var id: Long = -1L
    var name: String = "Denny"
    var isFemale: Boolean = false
}

我们通过Intellij IDEA的Tools->Kotlin->Show Kotlin Bytecode->Decompile可以看到生成的Java的代码如下:

public final class Student {
   private long id = -1L;
   @NotNull
   private String name = "Denny";
   private boolean isFemale;

   public final long getId() {
      return this.id;
   }

   public final void setId(long var1) {
      this.id = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public final boolean isFemale() {
      return this.isFemale;
   }

   public final void setFemale(boolean var1) {
      this.isFemale = var1;
   }
}

看到Java代码,你应该知道怎么调用了吧。

2.2 Java调用Kotlin包级函数

package com.dengyin2000.kotlintest1包里面的StringUtil.kt文件中生命的所有函数、属性、都将编译成一个名为com.dengyin2000.kotlintest1.StringUtilKt的Java类的静态方法。假如我们有一个下面的Kotlin文件:

package com.dengyin2000.kotlintest1

fun sayHello() {
    println("Hello ${name}")
}

val name:String = "Denny"

fun String.firstChar() :Char{
    return this[0]
}

fun main(args: Array<String>) {
    println("Denny".firstChar())
}

通过上面的方式可以看到生成的Java代码如下:

package com.dengyin2000.kotlintest1;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

public final class StringUtilKt {
   @NotNull
   private static final String name = "Denny";

   public static final void sayHello() {
      String var0 = "Hello " + name;
      System.out.println(var0);
   }

   @NotNull
   public static final String getName() {
      return name;
   }

   public static final char firstChar(@NotNull String $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.charAt(0);
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      char var1 = firstChar("Denny");
      System.out.println(var1);
   }
}

可以看到String的扩展函数第一个参数变成了接受者。如果我们想要修改生成的Java对象的名称,我们可以使用@file:JvmName注解,如下:

@file:JvmName("Strings")

package com.dengyin2000.kotlintest1

fun sayHello() {
    println("Hello ${name}")
}

val name:String = "Denny"

fun String.firstChar() :Char{
    return this[0]
}

fun main(args: Array<String>) {
    println("Denny".firstChar())
}

这样生成的类名变成了Strings。

2.3 实例字段

如果我们不希望某个属性生成getter setter方法,我们希望生成一个实例字段的话,我们可以使用@JvmField注解标注Kotlin的属性。如下:

class Student{
    var id: Long = -1L
    var name: String = "Denny"
    var isFemale: Boolean = false
    
    @JvmField
    var grade: Int = 0
}

生成的Java如下:

public final class Student {
   private long id = -1L;
   @NotNull
   private String name = "Denny";
   private boolean isFemale;
   @JvmField
   public int grade;

   public final long getId() {
      return this.id;
   }

   public final void setId(long var1) {
      this.id = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public final boolean isFemale() {
      return this.isFemale;
   }

   public final void setFemale(boolean var1) {
      this.isFemale = var1;
   }
}

2.4 静态字段

在伴生对象和命名对象(object 类)的属性上使用@JvmField的区别如下:

class Student{
    var id: Long = -1L
    var name: String = "Denny"
    var isFemale: Boolean = false

    @JvmField
    var grade: Int = 0

    companion object {
        var teachName = "Sally"

        @JvmField
        var schoolMaster = "Noah"
    }
}

生成的Java代码如下:

public final class Student {
   private long id = -1L;
   @NotNull
   private String name = "Denny";
   private boolean isFemale;
   @JvmField
   public int grade;
   @NotNull
   private static String teachName = "Sally";
   @JvmField
   @NotNull
   public static String schoolMaster = "Noah";
   public static final Student.Companion Companion = new Student.Companion((DefaultConstructorMarker)null);

   public final long getId() {
      return this.id;
   }

   public final void setId(long var1) {
      this.id = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public final boolean isFemale() {
      return this.isFemale;
   }

   public final void setFemale(boolean var1) {
      this.isFemale = var1;
   }
   public static final class Companion {
      @NotNull
      public final String getTeachName() {
         return Student.teachName;
      }

      public final void setTeachName(@NotNull String var1) {
         Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
         Student.teachName = var1;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

调用方式如下:

        String schoolMaster = Student.schoolMaster;
        String teachName = Student.Companion.getTeachName();

2.5 静态方法

类似的只要将伴生对象或者命名对象的的方法用@JvmStatic注释,这样就能生成Java的静态方法。Kotlin代码如下:

class Student{
    var id: Long = -1L
    var name: String = "Denny"
    var isFemale: Boolean = false

    @JvmField
    var grade: Int = 0

    companion object {
        var teachName = "Sally"

        @JvmField
        var schoolMaster = "Noah"

        @JvmStatic
        fun sayHello() {
            println("Hello world")
        }
    }
}

生成的Java代码如下:

public final class Student {
   private long id = -1L;
   @NotNull
   private String name = "Denny";
   private boolean isFemale;
   @JvmField
   public int grade;
   @NotNull
   private static String teachName = "Sally";
   @JvmField
   @NotNull
   public static String schoolMaster = "Noah";
   public static final Student.Companion Companion = new Student.Companion((DefaultConstructorMarker)null);

   public final long getId() {
      return this.id;
   }

   public final void setId(long var1) {
      this.id = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public final boolean isFemale() {
      return this.isFemale;
   }

   public final void setFemale(boolean var1) {
      this.isFemale = var1;
   }

   @JvmStatic
   public static final void sayHello() {
      Companion.sayHello();
   }

   public static final class Companion {
      @NotNull
      public final String getTeachName() {
         return Student.teachName;
      }

      public final void setTeachName(@NotNull String var1) {
         Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
         Student.teachName = var1;
      }

      @JvmStatic
      public final void sayHello() {
         String var1 = "Hello world";
         System.out.println(var1);
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

这样调用

Student.sayHello();

2.6 生成重载方法

通常,如果你写一个有默认参数值的 Kotlin 函数,在 Java 中只会有一个所有参数都存在的完整参数签名的方法可见,如果希望向 Java 调用者暴露多个重载,可以使用 @JvmOverloads 注解。

该注解也适用于构造函数、静态方法等。它不能用于抽象方法,包括在接口中定义的方法。

Kotlin的类如下:

class Animal @JvmOverloads constructor(name: String, type: Int = 0){
    
    fun talk(name: String, by: Int = 1) {

    }

    @JvmOverloads
    fun talkTo(name: String, by: Int = 1) {
        
    }
}

生成的Java代码如下:

public final class Animal {
   public final void talk(@NotNull String name, int by) {
      Intrinsics.checkParameterIsNotNull(name, "name");
   }

   // $FF: synthetic method
   // $FF: bridge method
   public static void talk$default(Animal var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 2) != 0) {
         var2 = 1;
      }

      var0.talk(var1, var2);
   }

   @JvmOverloads
   public final void talkTo(@NotNull String name, int by) {
      Intrinsics.checkParameterIsNotNull(name, "name");
   }

   // $FF: synthetic method
   // $FF: bridge method
   @JvmOverloads
   public static void talkTo$default(Animal var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 2) != 0) {
         var2 = 1;
      }

      var0.talkTo(var1, var2);
   }

   @JvmOverloads
   public final void talkTo(@NotNull String name) {
      talkTo$default(this, name, 0, 2, (Object)null);
   }

   @JvmOverloads
   public Animal(@NotNull String name, int type) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
   }

   // $FF: synthetic method
   @JvmOverloads
   public Animal(String var1, int var2, int var3, DefaultConstructorMarker var4) {
      if ((var3 & 2) != 0) {
         var2 = 0;
      }

      this(var1, var2);
   }

   @JvmOverloads
   public Animal(@NotNull String name) {
      this(name, 0, 2, (DefaultConstructorMarker)null);
   }
}

生成了两个构造方法,talkTo也生成了两个方法,talk就只有一个;


重载构造方法
重载方法

2.7 可见性

Kotlin 的可见性与Java的可见性的映射关系如下表所示:


可见性

2.8 Kotlin中异常

在Kotlin中是不需要显示的try catch Checked Exception的,比如下面这个throwException方法:

class Animal @JvmOverloads constructor(name: String, type: Int = 0){

    fun talk(name: String, by: Int = 1) {

    }

    @JvmOverloads
    fun talkTo(name: String, by: Int = 1) {

    }

    fun throwException() {
        throw Exception("hahaha")
    }
}

在Java中调用不需要try catch。

no try catch

如果你想让Java调用时需要主动try catch的话,那你需要使用@Throws(Exception::class)注解。
注解

这时候Java调用方就需要try catch了。
try catch

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容