8.7 版本发布—WinterCG 兼容性第一部分
了解更多

NativeScript 无缝处理 JavaScript 和 Java/Kotlin 之间的数据类型转换,利用类型推断和专用包装器,确保跨平台开发中的平滑集成和类型安全。

字符串转换

将 JavaScript 字符串转换为 Java 字符串类型

JavaScript String 映射到 java.lang.String

js
var context = ...;
var button = new android.widget.Button(context);
var text = "My Button";
button.setText(text);

button.setText(text) - text 转换为 java.lang.String

将 JavaScript 字符串转换为 Kotlin 字符串类型

JavaScript String 映射到 kotlin.String

js
const kotlinClass = new com.example.KotlinClassWithStringProperty()
var text = 'My Button'
kotlinClass.setStringProperty(text) /

kotlinClass.setStringProperty(text) - JavaScript text 转换为 kotlin.String

将 Java 字符串类型转换为 JavaScript 字符串

java.lang.Stringjava.lang.Character 类型都投影为 JavaScript String

js
var file = new java.io.File('/path/to/file')
var path = file.getPath()

getPath() - 返回 java.lang.String,转换为 JS String

将 Kotlin 字符串类型转换为 JavaScript 字符串

kotlin.Stringkotlin.Char 类型都投影为 JavaScript String

kotlin
package com.example

class KotlinClassWithStringAndCharProperty {
  val stringProperty: String = "string property"
  val charProperty: Char = 'c'
}
js
var kotlinClass = new com.example.KotlinClassWithStringAndCharProperty()
var str1 = kotlinClass.getStringProperty() // returns kotlin.String, converted to JS String
var str2 = kotlinClass.getCharProperty() // returns kotlin.Char, converted to JS String
  • getStringProperty()- 返回 kotlin.String,转换为 JS String
  • getCharProperty() - 返回 kotlin.Char,转换为 JS String

布尔值转换

JavaScript 布尔值到 Java 布尔值类型

JavaScript Boolean 映射到 Java 基本布尔值

js
var context = ...;
var button = new android.widget.Button(context);
var enabled = false;
button.setEnabled(enabled);

button.setEnabled(enabled) - JavaScript 布尔值 enabled 转换为 Java 基本布尔值。

JavaScript 布尔值到 Kotlin 布尔值类型

JavaScript Boolean 映射到 Kotlin 类 Boolean

从 Java 布尔值转换为 JavaScript 布尔值

基本 boolean 和引用 java.lang.Boolean 类型都投影为 JavaScript Boolean

js
var context = ...
var button = new android.widget.Button(context);
var enabled = button.isEnabled();

isEnabled() - 返回 基本布尔值,转换为 JS Boolean

从 Kotlin 布尔值转换为 JavaScript 布尔值

Kotlin 的布尔值类型 kotlin.Boolean 映射到 JavaScript Boolean

kotlin
package com.example

class KotlinClassWithBooleanProperty {
  val booleanProperty: Boolean = false
}
js
var kotlinClass = new com.example.KotlinClassWithBooleanProperty()
var enabled = kotlinClass.getBooleanProperty() // returns Kotlin Boolean, converted to JS Boolean

getBooleanProperty() - 返回 Kotlin Boolean,转换为 JS Boolean

数值数据类型

将 JavaScript Number 转换为 Java/Kotlin 数值类型

Java 和 Kotlin 有几种基本数值类型,而 JavaScript 只有 Number 类型。此外,与 JavaScript 不同,Java 和 Kotlin 支持 方法重载,这使得数值转换更加复杂。

考虑以下示例

java
class MyObject extends java.lang.Object {
    public void myMethod(byte value){
    }

    public void myMethod(short value){
    }

    public void myMethod(int value){
    }

    public void myMethod(long value){
    }

    public void myMethod(float value){
    }

    public void myMethod(double value){
    }
}
kotlin
class MyObject : Any() {
    fun myMethod(value: Byte) {}

    fun myMethod(value: Short) {}

    fun myMethod(value: Int) {}

    fun myMethod(value: Long) {}

    fun myMethod(value: Float) {}

    fun myMethod(value: Double) {}
}

从 JavaScript 调用 myObject 实例上的 myMethod 时,应用以下逻辑

js
var myObject = new MyObject()

隐式转换

  • 整数转换

当你调用

js
myObject.myMethod(10)

运行时会将 JavaScript 10(Number) 隐式转换为 Java/Kotlin Int,然后调用 myMethod(Int) 方法。

注意

如果没有 myMethod(Int) 实现,Android 运行时将尝试选择转换损失最小的最佳重载。如果找不到这样的方法,将引发异常。

  • 浮点数转换
js
myObject.myMethod(10.5) // myMethod(Double) will be called.

JavaScript 10.5 Number 将转换为 Java/Kotlin double,然后调用 myMethod(ouble)。

注意

在不存在 myMethod(double) 实现的情况下,运行时将尝试选择转换损失最小的最合适重载。如果找不到这样的方法,将抛出异常。

显式转换

要显式地将 JavaScript Number 转换为 Java/Kotlin 数值数据类型,并调用特定的方法重载,NativeScript 在全局范围内提供以下函数

  • byte(number) → (Java 基本 byte | Kotlin Byte )
    - 数值将被截断,只使用其整数部分的第一个字节。
  • short(number) → Java 基本 short

将 Java 数值类型转换为 JavaScript Number

以下 Java 类型将转换为 JavaScript Number

js
var byte = new java.lang.Byte('1')
var jsByteValue = byte.byteValue() // returns primitive byte, converted to Number
js
var short = new java.lang.Short('1')
var jsShortValue = short.shortValue() // returns primitive short, converted to Number
js
var int = new java.lang.Integer('1')
var jsIntValue = int.intValue() // returns primitive int, converted to Number

intValue() - 返回 基本 int,转换为 Number

js
var float = new java.lang.Float('1.5')
var jsFloatValue = float.floatValue() // returns primitive float, converted to Number

floatValue() 返回 基本 float,转换为 Number

js
var double = new java.lang.Double('1.5')
var jsDoubleValue = double.doubleValue() // returns primitive double, converted to Number

doubleValue() 返回 基本 float,转换为 Number

  • Long & 基本 long

java.lang.Long 及其基本等价物是特殊类型,通过应用以下规则投影到 JavaScript

- If the value is in the interval `(-2^53, 2^53)` then it is converted to [Number](http://www.w3schools.com/jsref/jsref_obj_number.asp)
- Else a special object with the following characteristics is created:
- Has Number.NaN set as a prototype
- Has value property set to the string representation of the Java long value
- Its `valueOf()` method returns NaN
- Its `toString()` method returns the string representation of the Java long value
java
public class TestClass {
	public long getLongNumber54Bits(){
		return 1 << 54;
	}
	public long getLongNumber53Bits(){
		return 1 << 53;
	}
}
js
var testClass = new TestClass()
var jsNumber = testClass.getLongNumber53Bits()
var specialObject = testClass.getLongNumber54Bits()

jsNumber 是 JavaScript Number,specialObject 是上面讨论的特殊对象。

将 Kotlin 数值类型转换为 JavaScript Number

与从 Java 数值数据类型到 JavaScript Number 的转换类似,以下 Kotlin 数值数据类型将转换为 JavaScript Number 类型

kotlin
package com.example

class KotlinClassWithByteProperty {
  val byteProperty: Byte = 42
}
js
var kotlinClass = new com.example.KotlinClassWithByteProperty()
var jsByteValue = kotlinClass.getByteProperty() // returns Kotlin Byte, converted to Number
kotlin
package com.example

class KotlinClassWithShortProperty {
  val shortProperty: Short = 42
}
js
var kotlinClass = new com.example.KotlinClassWithShortProperty()
var jsShortValue = kotlinClass.getShortProperty()

getShortProperty() - 返回 Kotlin Short,转换为 Number

kotlin
package com.example

class KotlinClassWithIntProperty {
  val intProperty: Int = 42
}
js
var kotlinClass = new com.example.KotlinClassWithIntProperty()
var jsIntValue = kotlinClass.getIntProperty()

getIntProperty() - 返回 Kotlin Int,转换为 Number

kotlin
package com.example

class KotlinClassWithFloatProperty {
  val floatProperty: Float = 42.0f
}
js
var kotlinClass = new com.example.KotlinClassWithFloatProperty()

getFloatProperty() - 返回 Kotlin Float,转换为 Number

kotlin
package com.example

class KotlinClassWithDoubleProperty {
  val doubleProperty: Double = 42.0
}
js
var kotlinClass = new com.example.KotlinClassWithDoubleProperty()
var jsDoubleValue = kotlinClass.getDoubleProperty()

getDoubleProperty() - 返回 Kotlin double,转换为 Number

  • Kotlin 的 long 类型 kotlin.Long 是一种特殊类型,通过应用以下规则投影到 JavaScript

    • 如果值在区间 (-2^53, 2^53) 内,则将其转换为 Number

    • 否则,会创建一个具有以下特征的特殊对象
    • 将 Number.NaN 设置为原型
    • 将 value 属性设置为 Kotlin 长整型值的字符串表示形式
    • 其 valueOf() 方法返回 NaN
    • 其 toString() 方法返回 Kotlin 长整型值的字符串表示形式
kotlin
package com.example

class KotlinClassWithLongProperties {
  val longNumber54Bits: Long
    get() = (1 shl 54).toLong()
  val longNumber53Bits: Long
    get() = (1 shl 53).toLong()
}
js
var kotlinClass = new com.example.KotlinClassWithLongProperties()
var jsNumber = kotlinClass.getLongNumber53Bits()
var specialObject = kotlinClass.getLongNumber54Bits()

jsNumber 是 JavaScript Number,specialObject 是上面讨论的特殊对象。

转换 Undefined & Null

JavaScript Undefined & Null 映射到 Java 和 Kotlin null 字面量(或空指针)。

js
var context = ...;
var button = new android.widget.Button(context);
button.setOnClickListener(undefined);

在上面的示例中,Java 调用将使用 null 关键字进行。

数组转换

JavaScript 数组 会隐式转换为 Java 数组Kotlin 数组,使用上面描述的数组元素类型转换规则。例如

js
var items = ['One', 'Two', 'Three']
var myObject = new MyObject()
myObject.myMethod(items)
java
class MyObject extends java.lang.Object {
  public void myMethod(java.lang.String[] items){
  }
}
kotlin
class MyObject : Any() {
    fun myMethod(items: Array<String>) {}
}

从 Java/Kotlin 数组转换为 JavaScript 数组

Java/Kotlin 中的数组是一个特殊的 java.lang.Object,它有一个隐式的关联类。Java/Kotlin 数组在 JavaScript 中被投影为一个具有以下特征的特殊 JavaScript 代理对象

  • 具有 length 属性
  • 已注册索引 getter 和 setter 回调,它们
    • 如果数组包含可转换为 JavaScript 类型的元素,则访问第 i 个元素将返回转换后的类型
    • 如果数组包含不可转换为 JavaScript 类型的元素,则访问第 i 个元素将返回 Java/Kotlin 类型的代理对象,请参阅 访问 API
js
var directory = new java.io.File('path/to/myDir')
var files = directory.listFiles() // files is a special object as described above
var singleFile = files[0] // the indexed getter callback is triggered and a proxy object over the java.io.File is returned
kotlin
package com.example

class KotlinClassWithStringArrayProperty {
  val stringArrayProperty: Array<String> = arrayOf("element1", "element2", "element3")
}
js
var kotlinClass = new com.example.KotlinClassWithStringArrayProperty()
var kotlinArray = kotlinClass.getStringArrayProperty() // kotlinArray is a special object as described above
var firstStringElement = kotlinArray[0] // the indexed getter callback is triggered and the kotlin.String is returned as a JS string

注意

出于性能考虑,Java/Kotlin 数组有意不转换为 JavaScript 数组,尤其是在处理大型数组时。

对象数组

在必须从 JavaScript 创建 Java/Kotlin 数组的场景中。在给定的场景中,我们通过添加一个名为 create 的自定义方法扩展了内置的 JavaScript 数组对象。通过这样做,我们用自己的实现增强了 Array 对象的默认功能,以满足内置 JavaScript Array 对象 中的特定需求。以下是一些关于如何使用 Array.create 方法的示例

js
// the following statement is equivalent to byte[] byteArr = new byte[10];
var byteArr = Array.create('byte', 10)

// the following statement is equivalent to String[] stringArr = new String[10];
var stringArr = Array.create(java.lang.String, 10)

以下是 Array.create 方法的完整规范

js
Array.create(elementClassName, length)
js
Array.create(javaClassCtorFunction, length)

第一个签名接受 elementClassNamestring 类型。当需要创建 Java 基本类型数组(例如 charbooleanbyteshortintlongfloatdouble)时,此选项将变得很有用。当需要创建 Java 不规则数组时,这也适用。对于这种情况,elementClassName 必须是标准的 JNI 类表示法。以下是一些示例

js
// equivalent to int[][] jaggedIntArray2 = new int[10][];
var jaggedIntArray2 = Array.create('[I', 10)

// equivalent to boolean[][][] jaggedBooleanArray3 = new boolean[10][][];
var jaggedBooleanArray3 = Array.create('[[Z', 10)

// equivalent to Object[][][][] jaggedObjectArray4 = new Object[10][][][];
var jaggedObjectArray4 = Array.create('[[[Ljava.lang.Object;', 10)

第二个签名要求您提供 javaClassCtorFunction,它必须是表示所需 Java 类型的 JavaScript 构造函数。以下是一些示例

js
// equivalent to String[] stringArr = new String[10];
var stringArr = Array.create(java.lang.String, 10)

// equivalent to Object[] objectArr = new Object[10];
var objectArr = Array.create(java.lang.Object, 10)

基本类型数组

在处理基本类型数组时,不支持自动编组。要将它们作为参数传递给方法,您需要使用包装类(例如 Integer、Double)将基本类型转换为对象,从而允许自动编组。

java
public static void myMethod(int[] someParam)

然后你需要按如下方式调用它

js
let arr = Array.create('int', 3)
arr[0] = 1
arr[1] = 2
arr[2] = 3

SomeObject.myMethod(arr) // assuming the method is accepting an array of primitive types

但是,我们还可以使用一些其他有用的类来创建一些其他基本类型数组

js
const byteArray = java.nio.ByteBuffer.wrap([1]).array()
const shortArray = java.nio.ShortBuffer.wrap([1]).array()
const intArray = java.nio.IntBuffer.wrap([1]).array()
const longArray = java.nio.LongBuffer.wrap([1]).array()
const floatArray = java.nio.FloatBuffer.wrap([1]).array()
const doubleArray = java.nio.DoubleBuffer.wrap([1]).array()
二维基本类型数组

上述场景在二维数组中变得更加复杂。考虑一个 Java 方法,它接受一个二维数组作为参数

java
public static void myMethod(java.lang.Integer[][] someParam)

编组后的 JavaScript 代码将如下所示

js
let arr = Array.create('[Ljava.lang.Integer;', 2)
let elements = Array.create('java.lang.Integer', 3)
elements[0] = new java.lang.Integer(1)
elements[1] = new java.lang.Integer(2)
elements[2] = new java.lang.Integer(3)
arr[0] = elements

SomeObject.myMethod(arr) // assuming the method is accepting a two-dimensional array of primitive types
kotlin
interface Printer {
    fun print(content: String)
    fun print(content: String, offset: Int)
}

interface Copier {
    fun copy(content: String): String
}

interface Writer {
    fun write(arr: Array<Any>)
    fun writeLine(arr: Array<Any>)
}

实现接口

java
public class MyVersatileCopywriter implements Printer, Copier, Writer {
    public void print(String content) { ... }

    public void print(String content, int offset) { ... }

    public String copy(String content) { ... }

    public void write(Object[] arr) { ... }

    public void writeLine(Object[] arr) { ... }
}
kotlin
class MyVersatileCopywriter: Printer, Copier, Writer{

    override fun print(content: String) { ... }

    override fun print(content: String, offset: Int) { ... }

    override fun copy(content: String): String { ... }

    override fun write(arr: Array<Any>) { ... }

    override fun writeLine(arr: Array<Any>) { ... }
}

在 NativeScript 中可以通过扩展继承 Java Object 的任何有效对象来实现相同的结果。

  • 在 JavaScript 中 - 在实现中声明一个 interfaces 数组
  • 使用 Typescript 语法 - 将 decorator 应用于扩展类(注意 @Interfaces([...])

使用 Javascript 语法 - 将 interfaces 数组附加到 extend 调用的实现对象

js
let MyVersatileCopyWriter = java.lang.Object.extend({
    interfaces: [com.a.b.Printer, com.a.b.Copier, com.a.b.Writer], /* the interfaces that will be inherited by the resulting class */
    print: function() { ... }, /* implementing the 'print' methods from Printer */
    copy: function() { ... }, /* implementing the 'copy' method from Copier */
    write: function() { ... }, /* implementing the 'write' method from Writer */
    writeLine: function() { ... }, /* implementing the 'writeLine' method from Writer */
    toString: function() { ... } /* override `java.lang.Object's` `toString */
});
ts
@Interfaces([com.a.b.Printer, com.a.b.Copier, com.a.b.Writer]) /* the interfaces that will be inherited by the resulting MyVersatileCopyWriter class */
class MyVersatileCopyWriter extends java.lang.Object {
    constructor() {
        super();
        return global.__native(this);
    }

    print() { ... }
    copy() { ... }
    write() { ... }
    writeLine() { ... }
}

警告

  • 实现具有相同方法签名的两个接口将只生成 1 个方法。实现者有责任定义该方法对这两个接口的行为。

  • 实现具有相同方法名称参数数量不同返回类型void a()boolean a())的两个接口将导致编译错误。

注意

Java/Kotlin 方法重载由开发人员通过显式检查调用的函数的参数数量来处理。

ts
class MyVersatileCopyWriter extends ... {
    constructor() {
        super();
        return global.__native(this);
    }
    ...
    print() {
        let content = "";
        let offset = 0;

        if (arguments.length == 2) {
            offset = arguments[1];
        }

        content = arguments[0];

        // do stuff
    }
    ...
}

注意

在 OOP 中,当一个类扩展另一个类(即继承)时,新类不仅可以访问它必须实现的接口方法,还可以覆盖扩展类中的方法。此外,它可以引入特定于新类功能的新方法。这使新类能够扩展和增强其父类的行为,同时提供其自身的其他功能。

NativeScript 中的 Java 嵌套类型

java
public class Outer {
    public class Inner {
      // inner and nested class
    }

    public static class Nested {
      // nested but not inner class
    }
}
java
//Instantiate nested types
Outer outer = new Outer();
Outer.Inner inner1 = outer.new Inner();

Outer.Inner inner2 = new Outer().new Inner();

Outer.Nested nested = new Outer.Nested()
ts
var outer = new Outer()

var inner1 = new outer.Inner()

var inner2 = new new Outer().Inner()

var nested = new Outer.Nested()

Kotlin 类型

所有 Kotlin 类型都使用包和类代理投影到 JavaScript,如中所述

Kotlin 伴生对象

您可以通过 Companion 字段访问 Kotlin 的 伴生对象

kotlin
package com.example

class KotlinClassWithCompanion {
  companion object {
    fun getDataFromCompanion() = "some data"
  }
}
js
var companion = com.example.KotlinClassWithCompanion.Companion
var data = companion.getDataFromCompanion()

Kotlin 对象

要访问 Kotlin 的 对象,请使用 INSTANCE 字段

kotlin
package com.example

object KotlinObject {
  fun getDataFromObject() = "some data"
}
js
var objectInstance = com.example.KotlinObject.INSTANCE
var data = objectInstance.getDataFromObject()

访问 Kotlin 属性

要访问 Kotlin 的 属性,请使用其编译器生成的 get/set 方法。非布尔 Kotlin 属性也可以在 NativeScript 应用程序中用作 JS 字段。

kotlin
package com.example

class KotlinClassWithStringProperty(var stringProperty: kotlin.String)
js
var kotlinClass = new com.example.KotlinClassWithStringProperty()

var propertyValue = kotlinClass.getStringPropert()
kotlinClass.setStringProperty('example')

propertyValue = kotlinClass.stringProperty
kotlinClass.stringProperty = 'second example'

访问 Kotlin 包级函数

为了使用 Kotlin 包级函数,需要知道其定义所在的类。让我们看一个例子

kotlin
package com.example

fun getRandomNumber() = 42
js
var randomNumber = com.example.FunctionsKt.getRandomNumber()

在上面的示例中,类 FunctionsKt 是由 Kotlin 编译器自动生成的,其名称基于定义函数的文件的名称。Kotlin 支持注释文件以使用用户提供的名称,这简化了使用包级函数的过程

js
var randomNumber = com.example.UtilityFunctions.getRandomNumber()
kotlin
@file:JvmName("UtilityFunctions")
package com.example

fun getRandomNumber() = 42

访问 Kotlin 扩展函数

为了使用扩展函数,需要知道其定义所在的类。此外,在调用此函数时,第一个参数应该是为其定义函数的类型的实例。让我们看一个例子

kotlin
package com.example

import java.util.ArrayList

fun ArrayList<String>.switchPlaces(firstElementIndex: Int, secondElementIndex: Int) {
  val temp = this[firstElementIndex]
  this[firstElementIndex] = this[secondElementIndex]
  this[secondElementIndex] = temp
}
js
var arrayList = new java.util.ArrayList()
arrayList.add('firstElement')
arrayList.add('secondElement')
com.example.Extensions.switchPlaces(arrayList, 0, 1)

在上面的示例中,类 ExtensionsKt 是由 Kotlin 编译器自动生成的,其名称基于定义函数的文件的名称。Kotlin 支持注释文件以使用用户提供的名称,这简化了使用包级函数的过程

kotlin
@file:JvmName("ExtensionFunctions")
package com.example

import java.util.ArrayList

fun ArrayList<String>.switchPlaces(firstElementIndex: Int, secondElementIndex: Int) {
  val temp = this[firstElementIndex]
  this[firstElementIndex] = this[secondElementIndex]
  this[secondElementIndex] = temp
}
js
var arrayList = new java.util.ArrayList()
arrayList.add('firstElement')
arrayList.add('secondElement')
com.example.ExtensionFunctions.switchPlaces(arrayList, 0, 1)