Skip to content

proposal: expose Java API to gomobile bind programs #16876

@eliasnaur

Description

@eliasnaur

Abstract

Today, gomobile bind can take a set og Go packages and expose their public API to Java or ObjC apps. The proposal is to support the reverse, exposing Java API to the bound Go packages.

Motivation

Even though mobile apps can access Go code already, there are still large parts of a typical app that is impossible or awkward to implement in Go. The most notable case is UI code, which interacts most with the platform APIs.
Platform APIs can be accessed from Go in an indirect way already. Creating a Java (or ObjC) class wrapping the desired API and passing it to Go does work. However, this is not nearly as convenient as writing the code directly in Java.
To improve support for writing platform specific code in Go, direct access to the platform is needed.

Proposed features

Importing Java classes and interfaces from Go

The Go wrappers for all Java API are generated each time gomobile bind is called. To access a Java package, use import statements on the form:

    import "Java/some/pkg"

To access the static methods or constants on a Java class or interface, use

    import "Java/some/pkg/SomeClass"

or

    import "Java/some/pkg/SomeClass/InnerClass"

for an inner class.

Static methods and constants

After importing, the resulting packages SomeClass and InnerClass will contain the static methods and static final constants from their Java classes. For example

import "Java/java/lang/Float"

will expose (among others) the constant Float.MIN_VALUE and the function Float.ParseFloat.

Java classes and interfaces

The package "Java/some/pkg" contains Go interfaces wrapping every referenced Java type in some.pkg. The wrapper types are used to represent their wrapped Java types across the language barrier and to call methods on wrapped instances. For example, with the following Go function is now possible:

import "Java/java/lang"

func FloatDoubleValue(f lang.Float) float64 {
    return f.DoubleValue()
}

Creating new Java instances

To create a new instance of a Java class, use the New function defined in the class package. For example:

import (
    "Java/java/lang/Object"
    "Java/java/lang"
)

func NewObject() lang.Object {
    return Object.New()
}

Errors and exceptions

Exceptions are normally translated to explicit Go errors, but since we don't control the platform API, we don't know which Java methods can result in an exception worth catching. Instead, a simple heuristic is used: If a Java method is declared to throw one or more exceptions, its Go function or method will return an error. If no exception is declared, any exception thrown will be converted to a panic with the exception as argument.

In addition, any Java class which inherits from java.lang.Throwable will satisfy the error interface. Its Error method will delegate to the toString() method.

Extending or implementing Java types from Go

Gomobile already exposes exported Go structs to Java; this proposal adds support for constructing Go structs directly from Java. In addition, Go structs will be able to extend Java classes and implement Java interfaces.

To declare a Go struct that extends or implements Java types, use the form:

import "Java/some/pkg/Class"
import "Java/some/pkg/Inner"
import "Java/another/pkg2/Interface"

type S struct {
    pkg.Class // extends Class
    pkg2.Interface // implements Interface
    Class.Inner // implements (or extends) inner interface (or class)
}

Java constructors

To allow Java to create instances of a Go struct, S, add one or more constructor on the form:

func NewS(...) *S {
    ...
}

For each such Go constructor a Java constructor will be added taking the same arguments. The Java constructor calls its super constructor with its arguments before calling calling NewS. For example:

package gopkg

import (
    "Java/java/lang"
)

type GoObject struct {
    lang.Object
}

func NewGoObject() *GoObject {
    return &GoObject{}
)

will allow Java to construct instances of GoObject:

import go.gopkg.GoObject;

...

    GoObject o = new GoObject();

Overriding Java methods

To implement or override a method from a super class or interface, declare a Go method with the same name and its first letter capitalized. For example, to override the toString method in GoObject:

func (o *GoObject) ToString() string {
    ...
}

Exposing this

Whenever an foreign object is passed across the language barrier, a proxy is created to represent it. In the example above, there is a GoObject Java instance created in Java, and it contains a reference to its counterpart GoObject Go instance in Go. That means that when a Go method is called from Java, its method receiver contains the Go instance, while the Java instance is only accessible to Java.
To access the Java instance (for passing back to other Java APIs), any Go method can declare a this argument with one of the Java types the enclosing class extends or implements. For example, to access the this from the ToString method, use:

func (o *GoObject) ToString(t lang.Object) string {
    ...
}

The t variable will behave just as if it were a pure Java Object, and if passed to Java, it will have the same identity as the Java reference.

Calling super

In Go, delegation is achieved through delegation, but in Java, the keyword super is needed to access overridden methods. To call a super method from Go, use the Super() method on the this variable:

func (o *GoObject) ToString(t lang.Object) string {
    return t.Super().ToString()
}

Overloaded methods and constructors

Java supports overloading; Go doesn't. To access or override overloaded methods and cosntructors from Go, a mangling scheme is used:

  • For any overloaded method where the number of arguments uniquely identifies the method, the argument count is appended to its name, except if the methods takes no arguments. For example, the Java methods
void m();
void m(int i);

are called M and M2, respectively, in Go.

  • If multiple methods have the same name and the same number of arguments, their names have an underscore and the JNI mangled argument descriptor appended. For example, the Java methods:
void m(int i);
void m(String s)

are called M_I and M_Ljava_lang_String_2 in Go.

The JNI name mangling scheme is ugly. In particular, Java constructors are only distinguished by their arguments and are therefore often mangled. Suggestions for improved schemes are most welcome!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions