Kotlin was designed with Interoperability with Java in mind. So Java and Kotlin can easily call each other. Just because Kotlin is fully Java compatible does not mean that Kotlin is Java, but there are some details that need to be paid attention to when they call each other.

One, Kotlin tune Java

First, almost all Java code can be called in Kotlin without any problems. For example, using a collection class in Kotlin:

import java.util.*

fun demo(source: List<Int>) {
    val list = ArrayList<Int> ()// The "for" -loop is used for Java collections:
    for (item in source) {
        list.add(item)
    }
    // Operator conventions are equally valid:
    for (i in 0..source.size - 1) {
        list[i] = source[i] // Call get and set}}Copy the code

It’s just that there are more concise ways to use it when creating objects and using object methods.

Here are some details:

1. Access attributes

If you want to access a private property of a Java object, the Java object provides getters and setters, and you get the value of the property through the associated Getter and Setter methods.

If a Java class provides getters and setters for a member property, then Kotlin can use the property name directly without calling the corresponding Getter and Setter methods. For example:

lateinit var tvHello: TextView

override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    tvHello = findViewById(R.id.tvHello)

	  // Set the display content for TextView
    tvHello.text = "Hello, world!"
		// Get the display content of the TextView
    Log.i(TAG, "onCreate: ${tvHello.text}")}Copy the code

Note that if the Java class has only one setter method and does not provide a getter method, it will not be visible as a property in Kotlin, because Kotlin does not currently support set-only properties.

At this point, the only way to assign a value to a property is through its setter method.

2. Escape Java identifiers that are keywords in Kotlin

Some Kotlin keywords are valid identifiers in Java: in, object, is, and so on. If a Java library uses the Kotlin keyword as a method/property, you can still invoke the method by escaping it with the backquote (‘) character:

public class User {

    public Object object;
    
    public void is() {}}fun test(a){
    val user = User()
    user.`is` ()// To call the is method, it needs to be backquoted
    user.`object` = Object() // To access the attribute name, put it in backquotes
}
Copy the code

3, air safety and platform type

Platform type: In Java, all references can be null, but in Kotlin, there are strict checks and restrictions on null, which makes a reference from Java unsuitable in Kotlin.

For this reason, in Kotlin, declaration types from Java are called platform types.

For this type (platform type), Kotlin’s null checks are somewhat mitigated and become less stringent. This makes the semantic requirements for empty security Java consistent.

When we call a method referenced by a platform type, Kotlin does not impose null security checks during compilation, allowing the compilation to pass. At run time, however, an exception may be thrown because the platform type reference value may be null.

Such as:

Java classes:

public class User {
    public String name;// The name attribute may be null if no value is assigned
}
Copy the code

Kotlin class:

When using the Java User class, the attributes in the User class are treated by Kotlin as being of the platform type, meaning that even if the name attribute is empty, the related method of the attribute can be called directly, possibly resulting in a null pointer.


fun test(a) {
    val user = User() 

    if (user.name.equals("Bill")) { 
        Log.i(TAG, "The test: the bad guys")
        return}}Copy the code

In the preceding code, the User object is created without assigning a value to the name attribute, and then the name comparison method is called directly. The compilation is fine, but it will run with a null pointer exception.

Solution:

To avoid the possibility of calling null Pointers to Java code, we can use the “? . “triggers the Kotlin assertion mechanism to prevent null Pointers, such as:

fun test(a) {
    val user = User()

    / / by? Kotlin checks if the object method is null. If it is, it does not call the object method, thus avoiding null Pointers
    if (user.name?.length == 2) {
      println("The test: the bad guys")}// Compile time allows, run time may fail, or may occur null pointer, is not essential difference from direct call
    // If the name is null, then the assignment will be null at runtime
    val userName2:String = user.name 
}
Copy the code

If we use a non-nullable type, the compiler generates an assertion on assignment that prevents Kotlin’s non-nullable variable from holding null values; Similarly, this applies to Kotlin method parameter passing, where we also generate an assertion when passing a platform type value to a non-null parameter of the method.

In general, Kotlin does everything he can to prevent null assignments from spreading to the rest of the program, and instead fixes problems with assertions as soon as they occur.

Note: Use the question mark declaration, i.e.

 val userName: String? = user.name 
Copy the code

4. Mapped type

Kotlin deals specifically with a subset of Java types. Such types are not loaded “as-is” from Java, but are mapped to the corresponding Kotlin types. Mapping occurs only at compile time, and the runtime representation remains unchanged.

  • Java’s underlying data types map to the corresponding Kotlin types

    Java type Kotlin type
    byte kotlin.Byte
    short kotlin.Short
    int kotlin.Int
    long kotlin.Long
    char kotlin.Char
    float kotlin.Float
    double kotlin.Double
    boolean kotlin.Boolean
  • Some non-native built-in types are also mapped:

    Java type Kotlin type
    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’s boxed primitive type maps to a nullable Kotlin type:

    Java type Kotlin type
    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’s arrays are mapped as follows:

    Java type Kotlin type
    int[] kotlin.IntArray!
    String[] kotlin.Array<(out) String>!

5. Java arrays

On the Java platform, arrays use native data types to avoid the overhead of boxing/unboxing. Because Kotlin hides these implementation details, a workaround is needed to interact with Java code. There is a special class for each array of native type (IntArray, DoubleArray, CharArray, and so on) to handle this case. They are independent of the Array class and are compiled into arrays of Java native types for best performance.

Suppose we have a Java method that takes an index of an int array:

public class JavaArrayExample {

    public void removeIndices(int[] indices) {// 
        // Code here...}}Copy the code

In Kotlin you can pass an array of a native type like this:

val javaObj = JavaArrayExample()
val array = intArrayOf(0.1.2.3)// Build an int array
javaObj.removeIndices(array)  // Pass int[] to the method
Copy the code

or

val javaObj = JavaArrayExample()
val array = IntArray(10)// Construct an int array of size 10
javaObj.removeIndices(array)  // Pass int[] to the method
Copy the code

Such declared array, or represents the underlying data type of the array, there is no basic data type boxing and unboxing operation, performance is very high. Kotlin provides the following array of native types:

Java type Kotlin type
int[] IntArray!
long[] LongArray!
float[] FloatArray!
double[] DoubleArray!
char[] CharArray!
short[] ShortArray!
byte[] ByteArray!
boolean[] BooleanArray!
String[] Array<(out) String>!

6, Java variable parameters

Kotlin uses the expansion operator * to pass an array argument when calling a Java method with variable arguments:

public class User {
  
  // Variable parameters
    public void setChildren(String... childrenName) {
        for (int i = 0; i < childrenName.length; i++) {
            System.out.println("child name="+ childrenName[i]); }}}Copy the code
fun test2(a) {
    val user = User()
    user.setChildren("tom") // Pass a parameter manually

    user.setChildren("tom"."mike")// Pass two parameters

    val nameArray = arrayOf("Zhang"."Bill"."Fifty")
    // User.setchildren (nameArray
    user.setChildren(*nameArray)// Pass the array argument
	  user.setChildren(null) // Pass null.
}
Copy the code

When you pass null, you create a String array containing a single null element. For example:

@Test
public final void test2(a) {
   User user = new User();
   user.setChildren(new String[]{(String)null});
}
Copy the code

7, abnormal test

In Kotlin, all exceptions are unchecked, which means the compiler doesn’t force you to catch any of them. So when you call a Java method that declares a checked exception, Kotlin doesn’t force you to do anything:

public class User {

    public void setChildren(String... childrenName) throws Exception {
        for (int i = 0; i < childrenName.length; i++) {
            System.out.println("child name="+ childrenName[i]); }}}fun test2(a) {
    val user = User()
    user.setChildren("tom") // The compilation will pass
}
Copy the code

If you call setChildren in Java, you have to try catch the exception, or you have to throw it up, otherwise it won’t compile, but Kotlin doesn’t force you to catch the exception.

Specific reference: Kotlin Checked Exception mechanism

8. Object methods

When a Java type is imported into Kotlin, all references to the type Java.lang.object become Any. Since Any is not platform-specific, it only declares toString(), hashCode(), and equals() as its members, so Kotlin needs to use the extension function in order to use the other members of java.lang.object.

8-1, wait ()/notify ()

References to type Any do not provide wait() and notify() methods. Their use is generally discouraged and java.util.concurrent is recommended instead. If you do need to call these methods, you can convert the reference to java.lang.object:

(user as java.lang.Object).wait()
Copy the code

8-2. GetClass () gets the Class object of the Class

To get the Java class of an object, use the Java extension property on the class reference:

val intent1 = Intent(this, MainActivity::class.java)
Copy the code

You can also use an extended property: javaClass, such as:

val intent2 = Intent(this, MainActivity.javaClass)
Copy the code

8-3, clone ()

The Any base class does not declare the **clone()** method. If you want to override clone(), you need to inherit from kotlin.cloneable:

class Example : Cloneable {
    override fun clone(a): Any {... }}Copy the code

8-4, finalize ()

To override Finalize (), all you need to do is simply declare it without the override keyword:

class C {
    protected fun finalize(a) {
        // Terminate the logic}}Copy the code

According to Java rules, Finalize () cannot be private.

9. SAM conversion

9-1, SAM conversion detailed explanation

Here are two concepts:

  • Functional interface: An interface with only one abstract method is called a functional interface. It is also called a single abstract method interface.

  • The conversion of a Single Abstract Method interface into a lambda expression is called Single Abstract Method Conversions.

    That is, functional interfaces can be replaced by lambda expressions.

For example, on Android, if we wanted to set a tap listener event for a View, we would do this:

view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        System.out.println("click"); }});Copy the code

This is essentially passing an anonymous inner class object of type OnClickListener to the View’s setOnClickListener method.

In Kotlin, you can do something similar with anonymous inner classes:

tvHello.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?). {
        println("click"); }})Copy the code

But a look at the source code for OnClickListener shows that the OnClickListener interface is a functional interface, and since it is a functional interface, you can use lambda expressions prefixed with the interface type instead of manually creating classes that implement the functional interface. Such as:

view.setOnClickListener(View.OnClickListener {
 	 System.out.println("click");
})
Copy the code

With SAM conversion, Kotlin can transform any lambda expression whose signature matches the signature of a single abstract method on an interface into an instance of the class that implements that interface, so the above code can be further simplified by SAM as follows:

view.setOnClickListener({
 	 System.out.println("click");
})
Copy the code

And because of Kotlin’s higher-order functions, if the lambda expression is the last argument to a method, then the lambda expression can be moved outside the method’s parentheses, i.e.

view.setOnClickListener() {
 	 System.out.println("click");
}
Copy the code

If the method has only one argument and is a lambda expression, the parentheses for the method call can also be omitted, so the final call could look like this:

view.setOnClickListener {
 	 System.out.println("click");
}
Copy the code

9-2. Disambiguation of SAM transformations

Suppose you have a Java class that declares two overloaded methods, each taking a functional interface as an argument, such as:

public class SamInterfaceTest {

  // Function interface 1
    public interface SamInterface1 {
        void doWork(int value);
    }

  // Function interface 2
    public interface SamInterface2 {
        void doWork(int value);
    }

    private SamInterface1 samInterface1;//
    private SamInterface2 samInterface2;

    public void setSamInterface(SamInterface1 samInterface1) {
        this.samInterface1 = samInterface1;
    }

    public void setSamInterface(SamInterface2 samInterface2) {
        this.samInterface2 = samInterface2; }}Copy the code

SetSamInterface: setSamInterface: setSamInterface: setSamInterface: setSamInterface: setSamInterface: setSamInterface

The reason for this is that the only abstract method of SamInterface1 and SamInterface2 is of type (Int)->Unit, and the same is true of the function type of a lambda expression that converts a functional interface to SAM: (Int)->Unit; this causes the Kotlin compiler to be unsure which method to call.

Although this situation is quite strange, it can be encountered, and we need to disambiguate. The methods of disambiguation are as follows:

  1. A lambda expression prefixed with the interface type
  2. The lambda expression is strongly translated
  3. An anonymous class that implements the interface

Code implementation is as follows:

fun testSam(a) {
    val sam = SamInterfaceTest()
    // Option 1, a lambda expression prefixed with the interface type
    sam.setSamInterface(SamInterfaceTest.SamInterface1 {
        println("do something 1")})// In method 2, the lambda expression is strongly translated
    sam.setSamInterface({
        println("do something 2")}as SamInterfaceTest.SamInterface2)

    // Mode 3 implements the anonymous class of the interface
    sam.setSamInterface(object : SamInterfaceTest.SamInterface1 {
        override fun doWork(value: Int) {
            println("do something 3")}}}Copy the code

In the above three ways, it is clear which method to call, thus disambiguating.

Recommended use: Method 1, the code is elegant, elegant is important.

9-3. Kotlin functional interfaces

Before Kotlin 1.4, for Java’s functional interfaces, Kotlin can directly use SAM conversion, but for Kotlin’s functional interfaces, there is no SAM conversion, only through the anonymous inner class to achieve the interface parameter passing.

The official explanation is that Kotlin already has support for function types and higher-order functions, so there is no need to convert. If you want to use similar operations that take lambda arguments, you should define higher-order functions that need to specify the type of the function.

For example: before Kotlin1.4:

After Kotlin 1.4 (including 1.4), Kotlin began to support SAM conversions for functional interfaces, but with some restrictions on the declared interfaces. Interfaces must be declared using the fun keyword, such as:

// An interface that uses the fun keyword and has only one abstract method is a functional interface that can perform SAM transformations
fun interface SamInterface {
    fun test(value: Int)
}
Copy the code

Conversion for Kotlin functional interfaces:

class SamInterfaceTestKt {
    fun testSam(obj: SamInterface) {
        print("$obj")}}/ / test
fun testKtSam(a){
    val samKt = SamInterfaceTestKt()
    samKt.testSam(SamInterface { // A lambda expression prefixed with the interface type

    })

    samKt.testSam {// Lambda expression}}Copy the code

So after Kotlin1.4, both Java’s functional interfaces and Kotlin’s functional interfaces can do SAM transformations.

9-4. SAM conversion restrictions

There are two main limitations to SAM conversion:

  1. Only Java interfaces are supported

    After Kotlin1.4, this limitation does not exist

  2. Only interfaces are supported. Abstract classes are not supported

    The official gave no further explanation. I think it’s to avoid confusion, since there are too many things that need to be strengthened to support abstract classes. And the abstract class itself allows a lot of logic to be written inside, which makes it much more difficult to locate the error if something goes wrong.

Use JNI in Kotlin

Kotlin uses external to represent functions that are implemented in native(C/C++) code.

external fun foo(x: Int): Double
Copy the code

Two, Java tune Kotlin

1, properties,

A Kotlin attribute is compiled into a three-part Java element

  • agetterMethod, whose name is prefixedgetCalculate the
  • asetterMethod, whose name is prefixedsetWork out (only applies tovarAttribute);
  • A private field with the same name as the Kotlin property name

If the Kotlin property name starts with is, then the naming convention changes a bit:

  • The getter method is the same as the property name
  • The setter method is to replace is with set
  • A private field with the same name as the Kotlin property name

For example:

Kotlin class:

class TestField {
    val age: Int = 18

    var userName: String = "Tom"

    var isStudent: String = "yes"
}
Copy the code

The Java file for the compiled bytecode:

public final class TestField {
  // Final properties have getters and no setters
   private final int age = 18;
   @NotNull
   private String userName = "Tom";
   @NotNull
   private String isStudent = "yes";

   public final int getAge(a) {
      return this.age;
   }

   @NotNull
   public final String getUserName(a) {
      return this.userName;
   }

   public final void setUserName(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "
      
       "
      ?>);
      this.userName = var1;
   }

   @NotNull
   public final String isStudent(a) {
      return this.isStudent;
   }

   public final void setStudent(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "
      
       "
      ?>);
      this.isStudent = var1; }}Copy the code

As you can see from the above code:

  • A property declared by val is final, and a property of a final type has getters, not setters
  • For properties that start with is, the getter method is the same as the property name, and the setter method replaces is with set. Note: This rule applies to any type, not just Boolean types.
  • The Kotlin property corresponds to a private field in Java.

Package-level functions

2-1. Basic Introduction

We know that in Kotlin, you can declare a class in a Kotlin file, you can declare properties, you can declare methods, all of that is allowed. The Kotlin compiler generates a Java class that corresponds to the Kotlin file. The class name is Kotlin filename +Kt

  • Properties declared in the file become static private properties in the class and provide getter and setter methods
  • Methods declared in the file become static public methods in the class
  • Java classes are generated for classes declared in the file

For example:

Kotlin file: kotlinfile.kt

package com.mei.ktx.test

/** * declare a class */ in the Kotlin file
class ClassInFile

/** * declare a method */ in the Kotlin file
fun checkPhone(num: String): Boolean {
    println("No.$num")
    return true
}

In the Kotlin file, declare a variable */
var appName: String = "KotlinTest"
Copy the code

The corresponding Java class generated by the Kotlin compiler: kotlinFilekt.java

public final class KotlinFileKt {
  
   // Properties declared in the Kotlin file become private static properties in the Java class
   @NotNull
   private static String appName = "KotlinTest";

   // Methods declared in the Kotlin file become common static methods in the Java class
   public static final boolean checkPhone(@NotNull String num) {
      Intrinsics.checkNotNullParameter(num, "num");
      String var1 = "Number:" + num;
      boolean var2 = false;
      System.out.println(var1);
      return true;
   }

   @NotNull
   public static final String getAppName(a) {
      return appName;
   }

   public static final void setAppName(@NotNull String var0) {
      Intrinsics.checkNotNullParameter(var0, "
      
       "
      ?>); appName = var0; }}Copy the code

A class declared in the Kotlin file generates a separate Java class with the same name.

public final class ClassInFile {}Copy the code

So when Java calls a method or property in a Kotlin file, it needs to be called by the corresponding Java class name:

public static void main(String[] args) {
  // Called directly by the class name
    KotlinFileKt.checkPhone("123456");
    System.out.println(KotlinFileKt.getAppName());
}
Copy the code

Note here that the Kotlin compiler automatically generates classes ending in Kt, such as KotlinFileKt, which cannot create objects with the new keyword because there is no constructor declaration in the generated bytecode.

2-2. Change the generated class name

The Java class name generated by the Kotlin file, in addition to the default generated by the compiler, can be specified by itself with the annotation @jvmName

For example, the Kotlin file:

@file:JvmName("AppUtils") // Specify the class name, which needs to be specified before the package name declaration

package com.mei.ktx.test


/** * declare a class */ in the Kotlin file
class ClassInFile

/** * declare a method */ in the Kotlin file
fun checkPhone(num: String): Boolean {
    println("No.$num")
    return true
}

In the Kotlin file, declare a variable */
var appName: String = "KotlinTest"
Copy the code

The generated Java class is: AppUtils

public final class AppUtils {
   @NotNull
   private static String appName = "KotlinTest";

   public static final boolean checkPhone(@NotNull String num) {
      Intrinsics.checkNotNullParameter(num, "num");
      String var1 = "Number:" + num;
      boolean var2 = false;
      System.out.println(var1);
      return true;
   }

   @NotNull
   public static final String getAppName(a) {
      return appName;
   }

   public static final void setAppName(@NotNull String var0) {
      Intrinsics.checkNotNullParameter(var0, "
      
       "
      ?>); appName = var0; }}Copy the code

Note:

  • Use the annotation: @file:JvmName(” class name “)
  • Specify the class name before the package name declaration

2-3. Class name conflict resolution

From the above, we know that you can specify a class name for a Kotlin file, but if multiple Kotlin files with the same package name specify the same class name, this will result in duplicate class definitions, resulting in poor compilation. At this point you can use the @jvmMultiFileclass annotation to merge multiple identical classes into one.

KotlinFile1.kt

@file:JvmName("LoginUtils")
@file:JvmMultifileClass

package com.mei.ktx.test

fun checkPwd(password: String): Boolean {
    println("Password:$password")
    return true
}
Copy the code

KotlinFile2.kt

@file:JvmName("LoginUtils")
@file:JvmMultifileClass

package com.mei.ktx.test

fun checkName(phone: String): Boolean {
    println("No.$phone")
    return true
}
Copy the code

In this way, there is no conflict, and the declared method can be called directly from the LoginUtils class:

public static void main(String[] args) {
    LoginUtils.checkName("abc");
    LoginUtils.checkPwd("123456");
}
Copy the code

Note:

  • Add the annotation @file:JvmMultifileClass to all Kotlin files that specify the same class name

  • It is generally not recommended to specify the class name yourself.

3. Instance fields

The @jVMField annotation indicates that a property in Kotlin is an instance field. The Kotlin compiler does not generate a setter and getter for the property at compile time, but can access the property directly. This property is declared public.

Kotlin class:

class Person {

    var name: String = "Zhang"

    @JvmField
    var age: Int = 18
}
Copy the code

Java USES:

public static void main(String[] args) {
    Person person = new Person();
    System.out.println("name=" + person.getName() + "; age=" + person.age);
}
Copy the code

Since age is annotated with @jvmfield, the age field is treated as public in a Java class, accessible by itself, and no corresponding getter and setter methods are generated.

You can also see from the generated Java classes:

public final class Person {
   @NotNull
   private String name = "Zhang";
  
   @JvmField
   public int age = 18; // Common attributes

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

   public final void setName(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "
      
       "
      ?>);
      this.name = var1; }}Copy the code

Use limits: You can annotate the property with @jvmField if it has a backing field, is non-private, doesn’t have an open/Override or const modifier, and is not a delegate

It doesn’t feel useful.

4. Static fields

4-1 Kotlin static field declaration

Kotlin Static Field Declarations: A Kotlin property declared in a named or associated object is a static field. It will have static behind-the-scenes fields in the named object or in the class that contains the companion object.

For example, companion objects

class Person {
    companion object {
        var aliasName = "People" // Declare a static field}}Copy the code

The corresponding Java class:

public final class Person {
   private static String aliasName;
   @NotNull
   public static final Person.Companion Companion = new Person.Companion((DefaultConstructorMarker)null);

   public static final class Companion {

      @NotNull
      public final String getAliasName(a) {
         return Person.aliasName;
      }

      public final void setAliasName(@NotNull String var1) {
         Intrinsics.checkNotNullParameter(var1, "
      
       "
      ?>); Person.aliasName = var1; }}}Copy the code

As you can see from the Java code above, static fields so declared are private static fields, and calls to use such static fields in Java need to be obtained and assigned through the generated Companion class: Companion object, because getter and setter methods are automatically generated.

public static void main(String[] args) {
  	System.out.println("alias=" + Person.Companion.getAliasName());
}
Copy the code

4-2. Static fields to public

The static fields declared above are private by default, but we can make private fields public by:

  • Use the @jVMField annotation to decorate the field
  • Use the lateinit modifier to modify the field
  • Use the const modifier to modify the field

Such as:

class Person {
    companion object {
      // Use const
        const val TAG = "Person"

      // Lateinit
        lateinit var aliasName: String

      // Use annotations
        @JvmField
        var age: Int = 18}}Copy the code

Static fields decorated in the above three ways are all public static fields. When accessed, they can be accessed directly by the class name without the Companion class: Companion. Such as:

 public static void main(String[] args) {
     System.out.println("alias=" + Person.TAG);
     Person.aliasName = "People";
     System.out.println("alias=" + Person.aliasName);
     System.out.println("alias=" + Person.age);

     System.out.println("alias=" + Person.Companion.getAliasName());
 }
Copy the code

Static fields modified by lateinit are public static fields, but in the companion object, the corresponding setter and getter methods are generated.

The difference between:

  • Static fields decorated by const and ** @jVMField ** are not accessible through companion objects and do not generate corresponding setter and getter methods.
  • Static fields with the Lateinit modifier can be accessed either by the accompanying object or directly by the class name. The accompanying class also generates the corresponding setter and getter methods.

5. Static methods

As mentioned above, Kotlin represents package-level functions as static methods.

Functions defined by Kotlin in a named or associated object are not static by default. If you want to declare a static function, you can decorate the method with the @jVMStatic annotation. The declared method is static.

Call mode:

  • Can be called directly by the class name
  • It can also be called from a companion object.

In Kotlin, we declare static methods in companion objects:

class Person {

    companion object {

        fun notStaticMethod(a) {
            println("Not a static method.")}@JvmStatic
        fun staticMethod(a) {
            println("Is a static method.")}}}Copy the code

In the preceding code, two methods are declared in the companion object. The @jVMstatic annotation decorates the static method, which can be called in Java directly from the class name or from the companion object.

@Test
public void test2(a){
    Person.Companion.staticMethod(); // Call static methods with companion objects
    Person.staticMethod();// Calls static methods by class name
    Person.Companion.notStaticMethod();// Call non-static methods from the version object
}
Copy the code

The @jVMStatic annotation can also be applied to a property of an object or a companion object so that the property also has static getter and setter methods in the class.

6. The signature conflicts

With the annotation: @jvmname, you can solve the problem of function signature conflict.

6-1. Signature conflicts caused by generic erasure

The most prominent example is caused by type erasure:

fun List<String>.filterValid(a): List<String> {
    return arrayListOf("hello"."world")}fun List<Int>.filterValid(a): List<Int> {
    return arrayListOf(1.2.3)}Copy the code

In the Kotlin file, the two extension functions defined above cannot be compiled, and an error will be generated:

This means that when Kotlin is encoded into bytecode, generics are erased, so that both methods have the same method signature from the JVM’s perspective: filterValid(Ljava/util/List;) Ljava/util/List;

The JVM will then assume that the two methods are the same method, but have been repeatedly defined.

The solution is to give the method a new name by annotating @jvmName, such as:

fun List<String>.filterValid(a): List<String> {
    return arrayListOf("hello"."world")}@JvmName("filterValidInt") // Respecify the method name
fun List<Int>.filterValid(a): List<Int> {
    return arrayListOf(1.2.3)}val list=list(1.2.3)
list.
Copy the code

This will compile through.

In Kotlin they can be accessed using the same name filterValid, whereas in Java they are filterValid and filterValidInt, respectively.

Call in Java:

@Test
public void test3(a) {
    List<String> stringList = new ArrayList<>();
    System.out.println(ListExternalKt.filterValid(stringList));
    List<Integer> integerList = new ArrayList<>();
    System.out.println(ListExternalKt.filterValidInt(integerList));
}
Copy the code

Call in Kotlin:

fun main(a) {
    val stringList = arrayListOf<String>()
    println(stringList.filterValid())

    val intList = arrayListOf<Int>()
    println(intList.filterValid())// When Kotlin calls, you can use the method name directly, not the redefined method name
}
Copy the code

Output:

6-2. Getter and setter methods for properties conflict with existing methods in the class

The same technique applies to the coexistence of the attribute x and the function getX() :

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

fun getX(a) = 10
Copy the code

To change the name of the accessor method generated by a property without explicitly implementing the getter and setter, use **@get:JvmName** and @set:JvmName:

class Person {
    @get:JvmName("getXValue")
    @set:JvmName("setXValue")
    var x: Int = 20
}
Copy the code

Call in Java:

@Test
public void test3(a) {
    Person person = new Person();
    person.setXValue(20);
}
Copy the code

7. Generate overloads

In general, if you write a Kotlin function with default parameter values, and the bytecode generated by the Kotlin compiler is only going to have one full parameter, then Java will need to pass the full parameter when calling the method.

For example, Kotlin defines a Fruit class with a main constructor with two arguments, one of which has a default value

class Fruit constructor(var name: String, var type: Int = 1) { // Constructor with default arguments

  // Methods with default arguments
    fun setFuture(color: String, size: Int = 1){}}Copy the code

If you are creating the Fruit object in Kotlin, you can pass only one parameter, and the default parameter may not be passed.

But if you were creating the Fruit object in Java, you would have to pass both arguments because Java does not support default arguments. This constructor is also the only bytecode generated by Fruit. Such as:

If only one parameter is passed, Java will not compile.

Is it possible for the compiler to generate multiple overloaded methods for us? Of course it is. This can be done using the @jvMoverloads annotation.

Such as:

** @jvmoverloads **

class Fruit @JvmOverloads constructor(var name: String, var type: Int = 1) {

    @JvmOverloads
    fun setFuture(color: String, size: Int = 1){}}Copy the code

Create a Fruit object in Java and call the setFuture method, passing only one argument:

Essentially, the Kotlin compiler adds multiple overloads to the @jvMoverloads annotation method when generating bytecode. For example, look at the Java code for the Fruit class:

public final class Fruit {
   @NotNull
   private String name;
   private int type;

  // A two-argument setFuture method
   @JvmOverloads
   public final void setFuture(@NotNull String color, int size) {
      Intrinsics.checkNotNullParameter(color, "color");
   }

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

      var0.setFuture(var1, var2);
   }

  // One parameter setFuture method
   @JvmOverloads
   public final void setFuture(@NotNull String color) {
      setFuture$default(this, color, 0.2, (Object)null);
   }


  // The constructor with two arguments
   @JvmOverloads
   public Fruit(@NotNull String name, int type) {
      Intrinsics.checkNotNullParameter(name, "name");
      super(a);this.name = name;
      this.type = type;
   }

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

      this(var1, var2);
   }

  // The constructor with one argument
   @JvmOverloads
   public Fruit(@NotNull String name) {
      this(name, 0.2, (DefaultConstructorMarker)null); }}Copy the code

It is precisely because the Kotlin compiler helps us generate the overloaded method that we can call.

8, abnormal test

We know that Kotlin has no checked exceptions, so the Java signature of the Kotlin function does not declare that an exception is thrown. So if we have a Kotlin function like this:

FileUtils file:

fun writeToFile(a) {
    println("Write to file")
    throw IOException() // In the Kotlin method, an IO exception is thrown
}
Copy the code

Then we want to call it in Java and catch the exception:

If we try to catch this IO exception, Java will report an error. The cause is that writeToFile() does not declare IOException in the throws list. So no IOException can be caught when this method is called.

To solve the problem that Kotlin exceptions cannot be thrown up, Kotlin provides an annotation: @throws to solve the problem

Use the following:

Add a ** @throws annotation to a method that Throws an exception. The annotation specifies the type of the exception. In this case, it is KClass**.

@Throws(IOException::class)
fun writeToFile(a) {
    println("Write to file")
    throw IOException() // In the Kotlin method, an IO exception is thrown
}
Copy the code

Throws an exception up with the ** @throws ** annotation. This Throws an exception up when Java calls Kotlin methods, such as:

After adding annotations, Java can now catch IOExceptions normally.

9. Air safety

When Java calls Kotlin functions, there is no way to prevent null being passed to the function as a non-empty argument. So Kotlin generates runtime checks for all public functions that expect non-empty arguments. This causes an immediate NullPointerException in your Java code.

fun checkPhone(num: String): Boolean {
    println("No.$num")
    return true
}
Copy the code

The checkPhone method in Kotlin has non-null arguments. If null is passed when calling this method in Java, a null pointer exception will be reported at runtime. For example:

You can see that when it’s running, it’s throwing an exception. At the same time, the compiler also notifies us that when null is passed, it is yellow.

If the Kotlin method is defined as a nullable type, then a null is passed when the method is called in Java, and the runtime does not declare a null pointer exception:

// Declare parameters as nullable types
fun checkPhone(num: String?).: Boolean {
    println("No.$num")
    return true
}
Copy the code

3. Use Android KTX

1, a brief introduction

Android KTX is a set of Kotlin extensions included in Android Jetpack and other Android libraries. The KTX extension provides concise, idiomatic Kotlin code for Jetpack, the Android platform, and other apis. To do this, these extensions take advantage of a variety of Kotlin language features, including:

  • Extension function
  • Extended attributes
  • Lambda
  • Named parameters
  • Parameter Default values
  • coroutines

With the extension API in KTX, you can implement complex functionality with less code, just like the utility classes, which help you write less repetitive code and focus on your core code implementation.

For example: Usually with SharedPreferences, you have to create an editor before you can modify the preferences data. After completing the changes, you must also apply or commit the changes, as shown in the following example:

sharedPreferences
        .edit()  // create an Editor
        .putBoolean("key", value)
        .apply() // write to disk asynchronously
Copy the code

For developers, getting the Editor object and committing the final operation is a repetitive operation for each save/fetch. This is redundant code that should be avoided by developers. What really matters is the save/fetch operation.

So can this code be omitted? Of course, in Java, we do this by encapsulating a utility class, in a single line of code.

In Kotlin, you can use the KTX library provided by Google, such as:

sharedPreferences.edit { putBoolean("key", value) }
Copy the code

The above edit method is an extension function for SharedPreferences in the Android KTX Core library. When calling this extension function, you need to pass a lambda expression. In this lambda expression, You can directly call the put* method in the Editor class, and save the data without any other operation, which saves the code and improves the development efficiency.

Let’s look at the source code for the SharedPreferences extension edit in the Android KTX Core library:

@SuppressLint("ApplySharedPref")
inline fun SharedPreferences.edit(
    commit: Boolean = false.// Whether to commit data through the commit method. By default, data is committed through the apply method
    action: SharedPreferences.Editor. () - >Unit / / expression
) {
    val editor = edit()// Get the Editor object
    action(editor) // Execute the lambda expression, i.e. execute the user's code
    if (commit) {
        editor.commit()// Submit data
    } else {
        editor.apply()
    }
}
Copy the code

As you can see from the above source code, the Edit method helps us to implement the code that needs to be written repeatedly, allowing developers to focus only on their own functional implementation, thus reducing the amount of code and improving efficiency.

2. Use Android KTX in the project

The above SharedPreferences extension function is defined in the Android KTX Core library, but Google provides different extension libraries for each feature library to better serve each feature library. For example:

Extension library name Rely on describe
Core KTX implementation “Androidx. Core: the core – KTX: 1.3.2.” “ Core extension Library
Collection KTX implementation “Androidx. Collection: collection – KTX: 1.1.0.” “ Collection extension library
Fragment KTX Implementation “androidx fragments: fragments – KTX: 1.3.1.” “ Fragments extension libraries
Lifecycle KTX Implementation “androidx lifecycle: lifecycle – runtime – KTX: 2.3.0.” “ Declare cycle extension library
LiveData KTX Implementation “androidx lifecycle: lifecycle – livedata – KTX: 2.3.0.” “ LiveData extension libraries
ViewModel KTX Implementation “androidx lifecycle: lifecycle – viewmodel – KTX: 2.3.0.” “ The ViewModel extension libraries

Some of the common extension libraries are listed above. There are other extension libraries that are not listed. If you want to check them, you can go to the official website: Android KTX

Here are some common uses of common extension libraries:

2-1 Android KTX Core library

The SharedPreferences extension function is defined in the Android KTX Core library. If you want to use it in a project, you need to add a dependency to the Build. gradle file of the Module project:

dependencies {
    implementation "Androidx. Core: the core - KTX: 1.3.2." "
}
Copy the code

Once the library is introduced, you can use the associated extension API.

(1) Animation related

We added some extension functions for listening for animation, avoiding implementing interfaces and implementing methods, using the following:

fun startAnimation(a) {
    val animation = ValueAnimator.ofFloat(0f.360f)
        .setDuration(500)

    animation.doOnCancel { // Listen for cancellation callbacks

    }
    animation.doOnEnd {// Listen for the end of the animation

    }
    animation.start()
}
Copy the code

Each extension function adds a listening object to the animation. For example, if two extension functions are called above, two listening objects are added to the animation object, which is not cost-effective and increases the cost of callback.

(2) Relevant to Context

The list of methods: developer.android.com/kotlin/ktx/…

More practical: Parsing custom attributes

Context.withStyledAttributes(set: AttributeSet? = null, attrs: IntArray, @AttrRes defStyleAttr: Int = 0.@StyleRes defStyleRes: Int = 0, block: TypedArray.() -> Unit)
Copy the code

Use:

class CusTextView @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
    defStyleAttr: Int = 0
) : TextView(context, attrs, defStyleAttr) {

    init {
      // Using this method, you can directly parse custom attributes in a lambda expressioncontext? .withStyledAttributes(attrs, R.styleable.ActionBar) { cusBgColor = getColor(R.styleable.CusTextView_cusBgColor,0)}}}Copy the code

Using the withStyledAttributes method of Context, you can parse custom attributes directly in lambda expressions, which is quite practical.

(3) Canvas related

Extension function Functional description
Canvas.[withClip](Developer.android.com/reference/k…, kotlin.Function1))(clipRect: Rect, block: Canvas. () – >Unit) Crop the canvas to the specified size. Before executing the block,

1. Call Canvas. Save and Canvas.

2. Next call block,

3. Finally, executeCanvas.restoreToCountmethods

The Kotlin compiler helps us save, crop and restore the canvas, so developers only need to worry about drawing
Canvas.withRotation Rotate the canvas, then perform a block draw, and finally restore the canvas state.
Canvas.withScale Scale the canvas, then perform a block draw, and finally restore the canvas state.
Canvas.withTranslation Pan the canvas, then perform a block draw, and finally restore the canvas state.
Canvas.withSkew Pull the canvas, then perform a block draw, and finally restore the canvas state.
Canvas.withSave Save the original layer, then perform a block draw, and finally restore the canvas state
Canvas.withMatrix The canvas performs the proof transformation, then performs the block draw, and finally restores the canvas state

Canvas, a series of extension functions, helps us save the state of the Canvas and restore, and perform corresponding operations, so that developers only focus on the drawing itself, very practical.

class CusTextView @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
    defStyleAttr: Int = 0
) : TextView(context, attrs, defStyleAttr) {

    override fun onDraw(canvas: Canvas?). {
        super.onDraw(canvas)
      // Crop the canvas to a square of 100, and then paint the square with a green background color. We only care about the color itself, not the canvas
      // To cut, save and restore the canvas statecanvas? .withClip(Rect(0.0.100.100)) {
            drawColor(Color.GREEN)
        }
    }
}
Copy the code

In the above code, the canvas is first cropped into a square with a size of 100, and then the square is painted with a green background color. We only focus on the drawing color itself, and do not care about the canvas clipping. It is very simple to save and restore the state of the canvas.

These methods are very helpful when customizing a View.

(4) SparseArray set

KTX Core adds a number of extension functions to sparsearray-related classes, such as:

  • Sparsearray. forEach(action: (key: Int, value: T) -> Unit)

  • Get element, has a default value: SparseArray. [getOrDefault] (developer.android.com/reference/k… , androidx.core.util.android.util.SparseArray.getOrDefault.T))(key: Int, defaultValue: T)

  • Set null: sparselongArray.isEmpty ()

Such as:

fun test(a) {
    val map = SparseArray<String>()
    if (map.isNotEmpty()) {
      map.forEach { key, value ->
          println("key=$key,value=$value")}}}Copy the code

With extension functions, it is convenient to facilitate the SparseArray collection.

(5), View and ViewGroup

View extension function:

  • UpdateLayoutParams: view.updatelayoutparams (block: LayoutParams.() -> Unit), so you don’t have to get LayoutParams and set the value every time you modify it
  • DrawToBitmap (config: config = bitmap.config.argb_8888)
  • Listen for declared cycle methods, such as:
    • DoOnAttach (Crossinline Action: (View: View) -> Unit)
    • View.doondetach (Crossinline Action: (View: View) -> Unit)
    • View.doOnLayout(crossinline action: (view: View) -> Unit)
    • View.doOnNextLayout(crossinline action: (view: View) -> Unit)
    • View.doOnPreDraw(crossinline action: (view: View) -> Unit)

ViewGroup extension function:

  • Contains the specified View: viewgroup. contains(View: View)
  • ForEach (Action: (View: View) -> Unit)
  • Does not contain any child View: viewGroup.isEmpty ()

Other will not introduce, you can go to the official website, there is a detailed list of the extension API under each package, forget when you can also go to check, the official website address is:

List of KTX extension apis