Kotlin学习笔记(五) Java互操作

Java互操作

一、Kotlin中调用Java

  • Getter和Setter
public class Person {
    private String name;
    private String gender;
    private int wage = 1000;

    public String getName() { return name; }

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

    public String getGender() { return gender; }

    public void setGender(String gender) { this.gender = gender; }

    public void setWage(int wage) { this.wage = wage; }
}
fun main() {
    val person = Person()
    person.name = "yao"
    person.gender = "male"
    println(person.wage)
    // 无法访问,因为wage只有setter,它在Kotlin中不会作为属性可见。
    // Kotlin目前不支持只写属性。
}
  • 返回void的方法

如果一个Java方法返回void,从Kotlin调用时返回Unit

val person = Person()
person.gender = "male"
println(person.setWage(100))  // kotlin.Unit
  • 空安全

Java中声明的类型在Kotlin中称为平台类型。调用平台类型变量的方法时,Kotlin不会在编译时报告可空性错误,但运行时调用可能会失败。

val person = Person()
person.gender = "male"
println(person.name.substring(1))  // Exception
  • 平台类型

T!表示T或者T?

(Mutable)Collection!表示可以可变或不可变、可空或不可空的T的Java集合。

Array<(out) T>!表示可空或者不可空的T(或T的子类型)的Java数组。

  • 已映射类型

原生类型:

Java类型 Kotlin类型
byte kotlin.Byte
short kotlin.Short
int kotlin.Int
long kotlin.Long
char kotlin.Char
float kotlin.Float
double kotlin.Double
Boolean Kotlin.Boolean

非原生类型:

Java类型 Kotlin类型
java.lang.Object kotlin.Any!
java.lang.Cloneable kotlin.Cloneable!
java.lang.Comparable kotlin.Comparable!
java.lang.Enum kotlin.Enum!
java.lang.Annotation kotlin.Annotation!
java.lang.CharSequence kotlin.CharSequence!
java.lang.String kotlin.String!
java.lang.Number kotlin.Number!
java.lang.Throwable kotlin.Throwable!

装箱原始类型:

Java类型 Kotlin类型
java.lang.Byte kotlin.Byte?
java.lang.Short kotlin.Short?
java.lang.Integer kotlin.Int?
java.lang.Long kotlin.Long?
java.lang.Character kotlin.Char?
java.lang.Float kotlin.Float?
java.lang.Double kotlin.Double?
java.lang.Boolean Kotlin.Boolean?

集合类型:

Java类型 Kotlin只读类型 Kotlin可变类型 加载的平台类型
Iterator Iterator MutableIterator (Mutable)Iterator!
Iterable Iterable MutableIterable (Mutable)Iterable!
Collection Collection MutableCollection (Mutable)Collection!
Set Set MutableSet (Mutable)Set!
List List MutableList (Mutable)List!
ListIterator ListIterator MutableListIterator (Mutable)ListIterator!
Map Map MutableMap (Mutable)Map!
Map.Entry Map.Entry MutableMap.Entry (Mutable)Map.(Mutable)Entry!

数组类型

Java类型 Kotlin类型
int[] kotlin.IntArray!
String[] kotlin.Arrat<(out) String>!
  • Kotlin中的Java泛型

Foo转换成Foo!

Foo转换成Foo!

List转换成List<*>!,即List!

Kotlin在运行时不保留泛型, Kotlin只允许is检测星投影的泛型类型:

if (a is List) // 错误:无法检测它是否真的是一个 Int 列表
if (a is List<*>)   // OK:不保证列表的内容
  • Java可变参数
public class JavaArrayExample {
    public void removeIndicesVarArg(int... indices) {
        // 在此编码……
    }
}

使用展开运算符*来传递IntArray,无法传递null。

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)
  • 受检异常

在Kotlin中,所有异常是非受检的。

fun render(list: List<*>, to: Appendable) {
    for (item in list) {
        to.append(item.toString()) // Java 会要求我们在这里捕获 IOException
    }
}
  • 对象方法

Java中的类型 java.lang.Object 的所有引用都成了Kotlin中的 AnyAny只声明了toString()hashCode()equals()作为其成员。

wait() / notify():需要将引用转换为java.lang.Object

(foo as java.lang.Object).wait()

getClass()

val fooClass = foo::class.java
val fooClass = foo.javaClass

clone():需要继承kotlin.Cloneable

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

finalize()

class C {
    protected fun finalize() {
        // 终止化逻辑
    }
}

根据 Java 的规则,finalize() 不能是 private 的。

  • 静态成员

要访问已映射到 Kotlin 类型的 Java 类型的静态成员,需要使用 Java 类型的完整限定名:java.lang.Integer.bitCount(foo)

  • SAM转换

SAM = single abstract method

val runnable = Runnable { println("This runs in a runnable") }
val executor = ThreadPoolExecutor()
// Java 签名:void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }
  • 标识符转义

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

foo.`is`(bar)
  • JNI

要声明一个在本地(C 或 C++)代码中实现的函数,需要使用 external 修饰符来标记它:

external fun foo(x: Int): Double

二、Java中调用Kotlin

  • 属性

var name: String编译成以下Java声明:

private String name;

public String getName() {
    return name;
}

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

如果属性名称以is开头,例如isOpen,其getterisOpen()settersetOpen()

  • 包级函数

org.example包内app.kt文件中声明的所有的函数和属性,包括扩展函数, 都编译成一个名为org.example.AppKt的Java类的静态方法。

// app.kt
package org.example
class Util
fun getTime() { /*……*/ }
// Java
new org.example.Util();
org.example.AppKt.getTime();

可以使用@JvmName注解修改生成的Java类的类名。

@file:JvmName("DemoUtils")
package org.example
class Util
fun getTime() { /*……*/ }
// Java
new org.example.Util();
org.example.DemoUtils.getTime();

如果多个文件中生成了相同的 Java 类名(包名相同并且类名相同或者有相同的@JvmName注解),编译器能够生成一个单一的 Java 外观类,它具有指定的名称且包含来自所有文件中具有该名称的所有声明。需要所有相关文件中使用@JvmMultifileClass注解。

// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package org.example

fun getTime() { /*……*/ }
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package org.example

fun getDate() { /*……*/ }
// Java
org.example.Utils.getTime();
org.example.Utils.getDate();
  • 实例字段

如果一个属性有幕后字段、非私有、没有open / overrideconst修饰符并且不是被委托的属性,可以用@JvmField注解该属性。

class User(id: String) {
    @JvmField val ID = id
}
// Java
class JavaClient {
    public String getID(User user) {
        return user.ID;
    }
}
  • 静态字段

在具名对象或伴生对象中声明的 Kotlin 属性会在该具名对象或包含伴生对象的类中具有静态幕后字段。

通常这些字段是私有的,但是可以通过以下方式之一暴露出来。

@JvmField注解;

lateinit 修饰符;

const 修饰符。

使用@JvmField标注这样的属性使其成为与属性本身具有相同可见性的静态字段。

class Key(val value: Int) {
    companion object {
        @JvmField
        val COMPARATOR: Comparator = compareBy { it.value }
    }
}
// Java
Key.COMPARATOR.compare(key1, key2);
// Key 类中的 public static final 字段

在具名对象或者伴生对象中的一个延迟初始化的属性具有与属性 setter 相同可见性的静态幕后字段。

object Singleton {
    lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// 在 Singleton 类中的 public static 非-final 字段

(在类中以及在顶层)以const声明的属性在 Java 中会成为静态字段:

// example.kt
object Obj {
    const val CONST = 1
}
class C {
    companion object {
        const val VERSION = 9
    }
}
const val MAX = 239
// java
int const = Obj.CONST;
int max = ExampleKt.MAX;
int version = C.VERSION;
  • 静态方法

为具名对象或伴生对象中定义的函数生成静态方法,可以将这些函数标注为@JvmStatic

伴生对象:

class C {
    companion object {
        @JvmStatic fun callStatic() {}
        fun callNonStatic() {}
    }
}
// java
C.callStatic(); // 没问题
C.callNonStatic(); // 错误:不是一个静态方法
C.Companion.callStatic(); // 保留实例方法
C.Companion.callNonStatic(); // 唯一的工作方式

具名对象:

object Obj {
    @JvmStatic fun callStatic() {}
    fun callNonStatic() {}
}
// java
Obj.callStatic(); // 没问题
Obj.callNonStatic(); // 错误
Obj.INSTANCE.callNonStatic(); // 没问题,通过单例实例调用
Obj.INSTANCE.callStatic(); // 也没问题

自 Kotlin 1.3 起,@JvmStatic也适用于在接口的伴生对象中定义的函数。 这类函数会编译为接口中的静态方法。

interface ChatBot {
    companion object {
        @JvmStatic fun greet(username: String) {
            println("Hello, $username")
        }
    }
}
  • 接口中的默认方法

自JDK1.8起,Java重的借口可以包含默认方法,如需将一个成员声明为默认,需要使用@JvmDefault注解。

interface Robot {
    @JvmDefault fun move() { println("~walking~") }
    fun speak(): Unit
}
//java
public class C3PO implements Robot {
    // 来自 Robot 的 move() 实现隐式可用
    @Override
    public void speak() {
        System.out.println("I beg your pardon, sir");
    }
}

接口的实现者可以覆盖默认方法。

为了让@JvmDefault生效,编译该接口必须带有-Xjvm-default参数。 根据添加注解的情况,指定下列值之一:

-Xjvm-default=enabled只添加带有@JvmDefault 注解的新方法时使用。

-Xjvm-default=compatibility@JvmDefault添加到以往 API 中就有的方法时使用。

如果将带有 @JvmDefault 的方法的接口用作委托, 即使实际的委托类型提供了自己的实现,也会调用默认方法的实现。

interface Producer {
    @JvmDefault fun produce() {
        println("interface method")
    }
}

class ProducerImpl: Producer {
    override fun produce() {
        println("class method")
    }
}

class DelegatedProducer(val p: Producer): Producer by p {
}

fun main() {
    val prod = ProducerImpl()
    DelegatedProducer(prod).produce()  // interface method
}
  • 可见性
Kotlin Java
private private
protected protected
internal public
public public
  • KClass

调用有KClass类型参数的 Kotlin 方法:

kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
  • 签名冲突
fun List.filterValid(): List
fun List.filterValid(): List

这两个函数不能同时定义。如果我们真的希望它们在 Kotlin 中用相同名称,我们需要用@JvmName 去标注其中的一个(或两个),并指定不同的名称作为参数。

fun List.filterValid(): List

@JvmName("filterValidInt")
fun List.filterValid(): List

在Kotlin中它们可以用相同的名称 filterValid 来访问,而在Java中,它们分别是filterValidfilterValidInt

同样的技巧也适用于属性 x 和函数 getX() 共存。

val x: Int
    @JvmName("getX_prop")
    get() = 15

fun getX() = 10

如需在没有显式实现 getter 与 setter 的情况下更改属性生成的访问器方法的名称,可以使用@get:JvmName@set:JvmName

@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23
  • 生成重载

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

class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) {
    @JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*……*/ }
}
// 构造函数:
Circle(int centerX, int centerY, double radius)
Circle(int centerX, int centerY)

// 方法
void draw(String label, int lineWidth, String color) { }
void draw(String label, int lineWidth) { }
void draw(String label) { }
  • 受检异常

通常Kotlin函数的Java签名不会声明抛出异常。

package demo
fun writeToFile() {
    /*……*/
    throw IOException()
}
// Java
try {
  demo.Example.writeToFile();
}
catch (IOException e) { // 错误:writeToFile() 未在 throws 列表中声明 IOException
  // ……
}

因为writeToFile()没有声明IOException,Java 编译器得到一个报错消息。 为了解决这个问题,要在Kotlin中使用 @Throws 注解。

@Throws(IOException::class)
fun writeToFile() {
    /*……*/
    throw IOException()
}
  • 型变的泛型
class Box(val value: T)

interface Base
class Derived : Base

fun boxDerived(value: Derived): Box = Box(value)
fun unboxBase(box: Box): Base = box.value
// java

// 作为返回类型——没有通配符
Box boxDerived(Derived value) {  }
 
// 作为参数——有通配符
Base unboxBase(Box box) {  }

在默认不生成通配符的地方需要通配符,可以使用 @JvmWildcard 注解:

fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// java
Box boxDerived(Derived value) {  }

如果不需要默认的通配符转换,可以使用@JvmSuppressWildcards注解:

fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// java
Base unboxBase(Box box) {  }

你可能感兴趣的:(Kotlin学习笔记(五) Java互操作)