kotlin在设计之初就考虑到了与java的互操作性,故这俩语言可以互相调用,进行一个混合的编


项目组织


Kotlin调用Java

类的定义与使用

java定义的类

package com.xlyotut;
​
public class JavaStudent {
    public String name;
    public int age;
​
    public JavaStudent() {
    }
​
    public JavaStudent(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    public static void show() {
        System.out.println("Hello World");
    }
​
    @Override
    public String toString() {
        return "JavaStudent{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

kotlin使用java定义的类

package com.xlyotut
​
fun main() {
    val java = JavaStudent("流莹", 18)
    println(java)       // JavaStudent{name='流莹', age=18}
    JavaStudent.show()  // Hello World
}

如果java定义了一个变量,其名称为kotlin中的关键字,则需要使用该符号`来进行区分,类似于mysql的关键字区分

如kotlin的关键字in,java定义了in名称的变量或函数

public class JavaStudent {
    public double in;
}

则kotlin使用方法为:

fun main() {
    val java = JavaStudent("流莹", 18)
    java.`in` = 23.98
}

kotlin还可以直接继承java类

class KtStudent(name: String, age: Int): JavaStudent(name, age) {
}

Getter和Setter

默认在kotlin定义的类都会默认生成getter和setter的函数

class KtStudent(val name: String, var age: Int) {
}

编译为java后

public final class KtStudent {
   @NotNull
   private final String name;
   private int age;
​
   public KtStudent(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }
​
   @NotNull
   public final String getName() {
      return this.name;
   }
​
   public final int getAge() {
      return this.age;
   }
​
   public final void setAge(int var1) {
      this.age = var1;
   }
}

空安全处理

因为java没有原生的空和可空类型,所以kotlin在处理java的类型时需要特殊的处理,称为平台类型,对于这种类型,空检查是放宽的,因此它们的安全保证与java相同,也就是说部分情况下不会进行空检查

比如如下的java定义类

public class JavaStudent {
    public String name;     // 默认情况下为null
}

如下在kotlin直接进行使用

fun main() {
    val java = JavaStudent()
    println(java.name.uppercase())  // 不会进行空安全检查,可能会是null
}

在获取name时idea会提示该类型为String!,这个!意思为可以是String也可以是String?

某些情况,如使用特定注释,则不会视为平台类型,例如@NotNull,不为空注解

import org.jetbrains.annotations.NotNull;
​
public class JavaStudent {
    @NotNull public String name = "abc";
}

kotlin里就可以断定为非空类型了

fun main() {
    val java = JavaStudent()
    var name: String = java.name
}

对于java中的数组

public class JavaStudent {
    String[] exams;
}

kotlin对应的是Array

fun main() {
    val java = JavaStudent()
    val ex1: Array<String> = java.exams
    val ex2: Array<out String> = java.exams
    val ex3: Array<out String?> = java.exams
    val ex4: Array<out String?>? = java.exams
}

类型对照表

java与kotlin的类型转换对照表

Java类型

Java包装类型

Kotlin类型

byte

Byte

kotlin.Byte

short

Short

kotlin.Short

int

Integer

kotlin.Int

long

Long

kotlin.Long

char

Character

kotlin.Char

float

Float

kotlin.Float

double

Double

kotlin.Double

boolean

Boolean

kotlin.Boolean

数组映射

Java类型

Kotlin类型

int[]

kotlin.IntArray!

String[]

kotlin.Array<(out) String>!


泛型使用

java类型定义了上下界的泛型

import java.util.List;
​
public class JavaStudent {
    List<? extends Number> data;    // 上界为Number
    List<? super Integer> data2;    // 下界为Integer
}

此时会有如下转换

  • List<? extends Number> data成为List<out Number!>!

  • List<? super Integer> data2成为List<in Integer!>!

fun main() {
    val java = JavaStudent()
    // java上界对应kotlin协变
    val d1: MutableList<out Number> = java.data
    // java下届对应kotlin逆变
    val d2: MutableList<in Int> = java.data2
}

java中泛型的原始使用

import java.util.List;
​
public class JavaStudent {
    List data;
}

kotlin中使用可空的Any类型

fun main() {
    val java = JavaStudent()
    val d1: MutableList<Any?>? = java.data
}

或者是*星型投影

fun main() {
    val java = JavaStudent()
    val d1: MutableList<*> = java.data
}

运算符重载

如下面的kotlin类进行运算符重载

fun main() {
    val stu = KtStudent(30)
    val stu2 = KtStudent(18)
    println(stu + stu2)     // age = 48
}
​
class KtStudent(var age: Int) {
    operator fun plus(another: KtStudent): KtStudent {
        return KtStudent(age + another.age)
    }
​
    override fun toString(): String {
        return "age = $age"
    }
}

main函数编译为java后

public final class MainKt {
   public static final void main() {
      KtStudent stu = new KtStudent(30);
      KtStudent stu2 = new KtStudent(18);
      System.out.println(stu.plus(stu2));
   }
​
   // $FF: synthetic method
   public static void main(String[] args) {
      main();
   }
}

可以看到主要就是调用plus方法,知道这些我们只需要在java中定义函数签名相同的函数

public class JavaStudent {
    int age;
​
    public JavaStudent(int age) {
        this.age = age;
    }
​
    public JavaStudent plus(JavaStudent other) {
        return new JavaStudent(age + other.age);
    }
​
    @Override
    public String toString() {
        return "age = " + age;
    }
}

在kotlin中也可以实现同样的效果

fun main() {
    val stu = JavaStudent(30)
    val stu2 = JavaStudent(18)
    println(stu + stu2)     // age = 48
}

异常检查

在java中如果有方法可能会抛出异常

import java.io.IOException;
​
public class JavaStudent {
    public void make() throws IOException {
       // 。。。 
    }
}

则在kotlin中可以不捕获(如果你确保代码执行不会抛出异常)

fun main() {
    val stu = JavaStudent()
    stu.make()
}

也可以捕获

import java.io.IOException
​
fun main() {
    val stu = JavaStudent()
    try {
        stu.make()
    } catch (e: IOException) {
        // 。。。
    }
}

Object类型

java的Object类型有多个方法在kotlin的Any中是没有定义的

public final native void notify();
public final native void notifyAll();
public final native void wait(long timeoutMillis) throws InterruptedException;
// 。。。

如果在kotlin想使用java的Object方法可以使用类型转换

fun main() {
    val stu = JavaStudent()
    (stu as Object).wait()
}

不过,kotlin不推荐使用notifywait等线程相关的方法,推荐使用JUC提供的类型

如果需要在kotlin获取某个类的java对象

fun main() {
    val stu = JavaStudent()
    val clazz: Class<JavaStudent> = stu.javaClass
}

函数式接口

kotlin支持java的SAM转换,只要是java中满足要求的函数式接口,都可以开箱即用

package java.lang;
​
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
fun main() {
    val run: Runnable = Runnable { 
        println("Hello world!")
    }
}

包括在函数中一样可以使用

import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
​
fun main() {
    val executor = ThreadPoolExecutor(3, 4, 5L, TimeUnit.SECONDS, LinkedBlockingQueue())
    executor.execute { println("This running in thread ${Thread.currentThread().name}") }   // This running in thread pool-1-thread-1
}

Java调用Kotlin

对象属性

在kotlin中定义类

class KtStudent(var age: Int) {
    lateinit var name: String
​
    override fun toString(): String {
        return "${name}'s age is $age"
    }
}

在java中可以直接使用

public class Main {
    public static void main(String[] args) {
        KtStudent student = new KtStudent(18);
        student.name = "青衣";
        student.setAge(3000);
        System.out.println(student);    // 青衣's age is 3000
    }
}

属性不得是openoverrideconst其中一种,也不能是委托属性

由于java中不存在空类型处理,所以在java中可以直接使用


静态属性

直接在.kt文件里写一个函数

fun test() {
    println("test!")
}

这看起来不属于任何类,那java怎么调用呢

在kotlin编译后,会生成MainKt的类

public final class MainKt {
   public static final void test() {
      System.out.println("test!");
   }
}

所以在java里可以直接通过MainKt来调用

public class Main {
    public static void main(String[] args) {
        MainKt.test();
    }
}

在kotlin中的文件顶部使用注解可以自定义字节码文件的名称

@file:JvmName("MainClass")
package com.xlyotut
​
​
fun test() {
    println("test!")
}

JVM注释

使用JVM注释生成特定的代码

@JvmStatic

@JVMStatic 是一个用于对象声明(object declarations)和伴生对象(companion objects)中的成员的注解。它的主要用途是生成静态方法:在 Kotlin 中,对象声明和伴生对象的成员默认不是静态的。如果你想在 JVM 上使用静态方法,你可以使用 @JVMStatic 注解来生成一个静态方法。这样,你就可以直接通过类名来调用该方法,而不需要创建对象实例。

如下面的kotlin单例工具类

object Utils {
    fun printMessage(message: String) {
        println(message)
    }
}

在java中进行调用需要附带INSTANCE,不好看

Utils.INSTANCE.printMessage("Hello World!");

使用该注解可以直接生成静态函数

object Utils {
    @JvmStatic
    fun printMessage(message: String) {
        println(message)
    }
}

java中直接进行调用

Utils.printMessage("Hello World!");

Utils的编译后java类如下

public final class Utils {
   @NotNull
   public static final Utils INSTANCE = new Utils();
​
   private Utils() {
   }
​
   @JvmStatic
   public static final void printMessage(@NotNull String message) {
      Intrinsics.checkNotNullParameter(message, "message");
      System.out.println(message);
   }
}

@JvmField

@JVMField 是一个用于属性的注解,它的主要用途是生成字段:在 Kotlin 中,属性默认是通过 getter 和 setter 方法来访问的,而不是直接的字段。如果你想在 JVM 上直接访问属性的字段,你可以使用 @JVMField 注解来生成一个实际的字段。这在某些情况下很有用,比如当你需要与其他 Java 代码或者库进行互操作时。

如下kotlin的类:

class DataClass {
    val readOnlyProperty: String = "read-only"
    var readWriteProperty: String = "read-write"
}

使用java访问,需要调用get和set方法

DataClass data = new DataClass();
String readOnlyProperty = data.getReadOnlyProperty();
data.setReadWriteProperty("new value");
String readWriteProperty = data.getReadWriteProperty();

而加上该注解后

class DataClass {
    @JvmField
    val readOnlyProperty: String = "read-only"
​
    @JvmField
    var readWriteProperty: String = "read-write"
}

java可以直接访问成员属性了

DataClass data = new DataClass();
String readOnlyProperty = data.readOnlyProperty;
data.readWriteProperty = "new value";
String readWriteProperty = data.readWriteProperty;

DataClass的编译后java类如下

public final class DataClass {
   @JvmField
   @NotNull
   public final String readOnlyProperty = "read-only";
   @JvmField
   @NotNull
   public String readWriteProperty = "read-write";
}

规则,就是用来打破的( ̄へ ̄)!