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>!表示可以可变或不可变、可空或不可空的T的Java集合。

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

  • 已映射类型

原生类型:

Java类型Kotlin类型
bytekotlin.Byte
shortkotlin.Short
intkotlin.Int
longkotlin.Long
charkotlin.Char
floatkotlin.Float
doublekotlin.Double
BooleanKotlin.Boolean

非原生类型:

Java类型Kotlin类型
java.lang.Objectkotlin.Any!
java.lang.Cloneablekotlin.Cloneable!
java.lang.Comparablekotlin.Comparable!
java.lang.Enumkotlin.Enum!
java.lang.Annotationkotlin.Annotation!
java.lang.CharSequencekotlin.CharSequence!
java.lang.Stringkotlin.String!
java.lang.Numberkotlin.Number!
java.lang.Throwablekotlin.Throwable!

装箱原始类型:

Java类型Kotlin类型
java.lang.Bytekotlin.Byte?
java.lang.Shortkotlin.Short?
java.lang.Integerkotlin.Int?
java.lang.Longkotlin.Long?
java.lang.Characterkotlin.Char?
java.lang.Floatkotlin.Float?
java.lang.Doublekotlin.Double?
java.lang.BooleanKotlin.Boolean?

集合类型:

Java类型Kotlin只读类型Kotlin可变类型加载的平台类型
IteratorIteratorMutableIterator(Mutable)Iterator!
IterableIterableMutableIterable(Mutable)Iterable!
CollectionCollectionMutableCollection(Mutable)Collection!
SetSetMutableSet(Mutable)Set!
ListListMutableList(Mutable)List!
ListIteratorListIteratorMutableListIterator(Mutable)ListIterator!
Map<K, V>Map<K, V>MutableMap<K, V>(Mutable)Map<K, V>!
Map.Entry<K, V>Map.Entry<K, V>MutableMap.Entry<K, V>(Mutable)Map.(Mutable)Entry<K, V>!

数组类型

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

Foo<? extends Bar>转换成Foo<out Bar!>!

Foo<? super Bar>转换成Foo<in Bar!>!

List转换成List<*>!,即List<out Any?>!

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

if (a is List<Int>) // 错误:无法检测它是否真的是一个 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<Key> = compareBy<Key> { 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
}
  • 可见性
KotlinJava
privateprivate
protectedprotected
internalpublic
publicpublic
  • KClass

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

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

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

fun List<String>.filterValid(): List<String>

@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>

在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<out T>(val value: T)

interface Base
class Derived : Base

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

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

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

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

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

fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// java
Base unboxBase(Box<Base> box) {  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值